j2ee面试宝典翻译(3) j2ee job interview companion
Q9:如何让表达“是一个”和“有一个”关系?或者请解释下“继承”和“组合”。组合和聚合之间有什么区别?
A9:“是一个”的关系表示继承而“有一个”的关系是表示组合。继承和组合都允许你将子对象放入新类中。代码重用的两个主要技术便是类继承和对象组合。
继承是单向的。例如房子是一栋建筑,但建筑不是一个房子。继承使用extends关键字。
组合:用于表达房子有一个浴室。说房子是一个浴室就不准确了。组合简单地使用实例变量引用其他对象,如House类拥有一个实例变量,引用一个Bathroom对象。
Q:哪一个更好?组合还是继承?
A:指南是,仅当子类“是一个”父类时,才使用继承。
- 不要仅仅为了代码重用而使用继承。如果没有“是一个”的关系,就应该使用组合语法。过度使用实现继承(使用“扩展”关键字)会破坏所有的子类,特别在超类需要被修改的时候。
- 不要仅仅为了获得多态性而使用继承。如果没有“是一个”的关系,而你又想要获得多态性,那么,使用接口继承和组合语法。
Q:组合和聚合有什么区别?
A:它们都表达整体和局部的关系。聚合关系,局部可以独立于整体而存在。例如,一个行项目和产品是整体和局部的关系。如果一个行项目被删除,对应的产品不需要被删除。所以聚合是一种较弱的关系。组合关系,局部不可独立于整体而存在。如果一个整体被删除,那么所有零件也会被删除。例如,订单和项目是整体和局部的关系。如果一个订单被删除,那么相应的行项目也应该被删除。所以组合具有更强的关系。
Q10:你怎么理解继承、封装、多态和动态绑定?
A10:Polymorphism多态——描述了这样一种能力,一个给定类型的变量可以被用来引用多个类型不同的对象(当然,需要这些类型是给定类型的子类),而调用的却是这个变量引用的对象的具体类型上的方法。简而言之,多态是一种自下而上的方法调用。多态的好处是,很容易添加新的扩展类而不破坏原有的调用代码。当给一个对象发送一条消息(调用方法)时,你甚至不知道这个对象的具体类型,但是正确的行为会发生,这就是多态。
面向对象编程语言实现多态的过程称为动态绑定。(运行期类型推断。)
Inheritance继承——是将基类的行为(即方法)和状态(即变量)包含到派生类中,这样它们就可在派生类中被访问了。关键的好处是,它提供了代码重用的正式机制。
任何业务逻辑的公共部分都可以从派生类移至基类,在重构时这样做,可以避免代码重复而提高代码的可维护性。
现有的类被称为基类而派生类被称为子类。继承也可以被定义为一个过程,即对象获得一个或多个其他对象的特征的过程,就像孩子从父母那里获得特征一样。
有两种类型的继承:
1、实作继承:可以通过继承部分或全部父类中已经实现的功能来扩展程序。在Java中,您只可以从一个超类继承。实作继承提升了重用性,但是不正确的继承使用可能导致编程噩梦,因为它会破坏封装性并且为将来的变化带来问题。使用实作继承,子类变得和父类紧密耦合起来。这将使得设计变得脆弱,如果你想改变父类,就不得不了解子类的细节以免破坏他们。所以使用实作继承,确保子类只依赖父类的行为,而不是实际的实现。
2、接口继承:接口提供了一种机制,将无关的类联系起来——通过指定系列普通方法,这些实现类都必须包含。(实现类之间可以是互不相关的。)接口继承提升了“面向接口编程而不是面向实现编程”的原则。这样降低了系统之间的耦合。在Java中,你可以实现任意数量的接口。这比“实作继承”更灵活,因为它不会把你锁定在具体实现中,具体实现会使子类变得难以维护。也要小心,修改接口会破坏实现类。
Which one to use?优先选择接口,因为它符合“面向接口编程”的理念并且可以降低耦合。接口继承可以在对象组合的帮助下实现代码的重用。如果你看GOF设计模式,你会发现他们更偏爱接口继承而不是实作继承。
实作继承案例:
package ch08_extends3; /**
* 假设活期存款和定期存款在存取行为上有类型的行为,我们把这两个行为的实现定义在父类中。
* <p>但是活期存款和定期存款在计算利息这个行为上表现是不同的。
* @author zhengwei 2013-7-13
*/
public abstract class Account {
public void deposit (double amount) {
System.out.println("depositing " + amount);
} public void withdraw (double amount) {
System.out.println ("withdrawing " + amount);
} public abstract double calculateInterest (double amount);
} class SavingsAccount extends Account { public double calculateInterest (double amount) {
// calculate interest for SavingsAccount
return amount * 0.03;
} public void deposit (double amount) {
super.deposit (amount); // get code reuse
// do something else
} public void withdraw (double amount) {
super.withdraw (amount); // get code reuse
// do something else
}
} class TermDepositAccount extends Account { public double calculateInterest (double amount) {
// calculate interest for SavingsAccount
return amount * 0.05;
} public void deposit(double amount) {
super.deposit (amount); // get code reuse
// do something else
} public void withdraw(double amount) {
super.withdraw (amount); // get code reuse
// do something else
}
}
接口继承案例:
package ch08_extends3; /**
* 接口继承示例代码,使用组合来重用代码。
* <p>在下例中,deposite和withdraw方法共享了AccountHelper中的代码片段。
* <p>而calculateInterest方法在各自实现中有独特的实现
* @author zhengwei 2013-7-13
*/
public interface Account {
public abstract double calculateInterest(double amount); public abstract void deposit(double amount); public abstract void withdraw(double amount);
} interface AccountHelper {
public abstract void deposit(double amount); public abstract void withdraw(double amount);
} /**
* class AccountHelperImpl has reusable code as methods deposit (double amount)
* and withdraw (double amount).
* <p>AccountHelperImpl含有可重用代码:deposit方法和withdraw方法
*/
class AccountHelperImpl implements AccountHelper {
public void deposit(double amount) {
System.out.println("depositing " + amount);
} public void withdraw(double amount) {
System.out.println("withdrawing " + amount);
} } class SavingsAccountImpl implements Account {
// composed helper class (i.e. composition ).
AccountHelper helper = new AccountHelperImpl(); public double calculateInterest(double amount) {
// calculate interest for SavingsAccount
return amount * 0.03;
} public void deposit(double amount) {
helper.deposit(amount); // code reuse via composition
} public void withdraw(double amount) {
helper.withdraw(amount); // code reuse via composition
}
} class TermDepositAccountImpl implements Account { // composed helper class (i.e. composition ).
AccountHelper helper = new AccountHelperImpl(); public double calculateInterest(double amount) {
// calculate interest for SavingsAccount
return amount * 0.05;
} public void deposit(double amount) {
helper.deposit(amount); // code reuse via composition
} public void withdraw(double amount) {
helper.withdraw(amount); // code reuse via composition
} }
两种方式可以使用如下的测试代码:
package ch08_extends3; /**
*
* @author zhengwei 2013-7-13
*/
public class Test {
public static void main(String[] args) {
Account acc1 = new SavingsAccountImpl();
acc1.deposit(50.0); Account acc2 = new TermDepositAccountImpl();
acc2.deposit(25.0); acc1.withdraw(25);
acc2.withdraw(10); double cal1 = acc1.calculateInterest(100.0);
double cal2 = acc2.calculateInterest(100.0); System.out.println("Savings --> " + cal1);
System.out.println("TermDeposit --> " + cal2);
}
}
输出结果:
depositing 50.0
depositing 25.0
withdrawing 25.0
withdrawing 10.0
Savings --> 3.0
TermDeposit --> 5.0
问:为什么优先通过组合来重用代码而不是继承?
答:可以看到两种方式都可利用多态,并重用了代码,结果也是一致的,但是:
- 类继承的优点是,它的重用是在编译时静态地完成的,是易于使用的。类继承的缺点也是因为它是静态的,从父类继承而来的实现在运行期不能被改变。而在对象组合中,(组合进来的)功能是在运行期动态获得的,通过对象收集其他对象的引用来达成。这种方法的优点是,组合进来的“实作对象”在运行时是可以更换的。这是因为我们依赖的是对象的接口类型,调用对象也只有通过他们的接口,所以一个对象可以被替换为另一个,只要他们有相同的类型(接口)。例如:组合进来的类型AccountHelperImpl可以在有需要时被替换为一个更有效率的实现:
public class EfficientAccountHelperImpl implements AccountHelper {
public void deposit(double amount) {
System.out.println(" efficient depositing " + amount);
} public void withdraw(double amount) {
System.out.println(" efficient withdrawing " + amount);
}
}
译注:感觉这里没说透。我来说下这个问题,“父母是不可以动态替换的,但是朋友可以是动态替换的”。一旦继承了某类,你不可能替换这种继承关系,但是组合,我们可以通过向构造器传参或者setter方法临时改变组装进来的对象的类型,当然前提是这些对象的类型否和依赖的接口类型。
再进一步,重用代码要么用super.someMethod(),这个super指向父类对象,这个super你是没法换的。要么是通过brother.someMethod()重用代码,这个brother是组合语法中的域成员,它指向和我们协作的,或者说依赖的对象,这种对象只需一个set方法就可替换了。
- 另一个问题是,实作继承中,子类依赖父类实现。这使得子类难以被重用,特别是继承而来的实作不再令人满意并因此而破坏封装性(译注:没看懂!)另外,对父类的修改不仅会沿着继承层次影响子类,还会影响到单纯使用子类的其他代码,这种子类严重耦合父类的设计是非常脆弱的。但是改变组合对象的接口/实现是容易的。
还是我上面说的那点。
因为对象组合的灵活性和强大,大部分设计模式只要有可能,就优先强调对象组合而非继承。很多时候,一个设计模式使用组合就展示了一个聪明的办法来解决一类常见问题,而不是用标准的、不那么灵活的基于继承的解决方案。
Encapsulation封装——指的是保持所有相关成员(变量和方法)在一起,在一个对象中。指定成员变量为私有可以隐藏变量和方法。对象应该向外界隐藏他们的内部运作。好的封装提高代码模块化,通过防止对象以一种意想不到的方式相互作用,从而使未来的开发和重构工作更容易。
示例代码:
class MyMarks {
private int vmarks = 0;
private String name; public void setMarks(int mark) throws MarkException {
if (mark > 0)
this.vmarks = mark;
else {
throw new MarkException("No negative Values");
}
} public int getMarks() {
return vmarks;
}
// getters and setters for attribute name goes here.
}
能够封装类的成员对于安全性和完整性来说是极其重要的。我们可以保护变量接收不合法的值。上面的示例代码描述了如何通过封装来保护MyMarks不拥有负值。任何修改成员变量”vmarks”的行为必须通过setter方法setMarks(int)。这可以防止对象”MyMarks”拥有负值,调用者传入负值将得到一个异常。
j2ee面试宝典翻译(3) j2ee job interview companion的更多相关文章
- j2ee面试宝典翻译(1)
q1:给出一些使用Java的理由? a1:java是一个有趣的编程语言,让我找出一些理由来: 内建的多线程机制.套接字.内存管理(自动垃圾回收) 面向对象 跨平台 通过对标准API的扩展来支持基于we ...
- Java面试宝典2013版(超长版)
一. Java基础部分......................................................................................... ...
- Java面试宝典2014版
一. Java基础部分......................................................................................... ...
- Java 面试宝典-2017
http://www.cnblogs.com/nelson-hu/p/7190163.html Java面试宝典-2017 Java面试宝典2017版 一. Java基础部分........... ...
- Java面试宝典-2017
Java面试宝典2017版 一. Java基础部分........................................................................... ...
- Java面试宝典2018
转 Java面试宝典2018 一. Java基础部分…………………………………………………………………………………….. 7 1.一个“.java”源文件中是否可以包括多个类(不是内部类)?有什么限制 ...
- java 软件开发面试宝典
一. Java 基础部分........................................................................................ ...
- 最全的Java面试宝典
一. 前言部分 从享受生活的角度上来说:“程序员并不是一种最好的职业,我认为两种人可以做程序员,第一,你不做程序员,你就没有什么工作可做,或者说是即使有可以做的工作但是你非常不愿意去做:第二,你非常痴 ...
- java面试宝典2019(好东西先留着)
java面试宝典2019 1.meta标签的作用是什么 2.ReenTrantLock可重入锁(和synchronized的区别)总结 3.Spring中的自动装配有哪些限制? 4.什么是可变参数? ...
随机推荐
- sqlserver检测数据库是否能连接的小技巧
有时候可能需要检测下某台机器的服务是不是起来了,或者某台机器的某个库是不是能被连接又不能打开ssms也不想登陆服务器的话就可以用这个方法. 1.在桌面上右键创建个文本,然后改后缀名为udl以后保存(1 ...
- Android利用CountDownTimer类实现倒计时功能
public class MainActivity extends Activity { private MyCount mc; private TextView tv; @Override publ ...
- Ubuntu10.04中间Leach协议一键安装
半天后,尝试,引用网络上的零散资源,成品博客Leach协议ubuntu10.04在安装(12.04也可以在右侧安装,然而,实施效果的不,求解决~~),并制作了补丁. 一个关键的安装步骤如下面: 1.在 ...
- Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 循环队列
Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1 ...
- 小贴士——提高PHP程序在NGINX代理服务器的性能
NGINX本身就是面向最大性能的代理服务器,因此在使用NGINX,并没有性能调整的配置工作.但是却有很多选项可用于定制NGINX的行为,利用底层硬件和操作系统. 下面将介绍用于提供PHP在NGINX的 ...
- 【Android开发日记】妙用 RelativeLayout 实现3
段布局
在设计过程中,我们经常会遇到这样的需求: 把一条线3控制,左对齐左控制,右侧控制右对齐,中间控制,以填补剩余空间. 或者一列内放3个控件,上面的与顶部对齐,以下的沉在最底部,中间控件是弹性的.充满剩余 ...
- [原] 细说 NUMA
详说 NUMA 标签(空格分隔): Cloud2.0 测试条件 两台机器: CPU: Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz X 24 Intel(R) X ...
- [置顶] Android开发之Thread类分析
在我们Linux系统中创建线程函数为:pthread_create(),在Android中我们为线程封装了一个类Thread,实际调用的还是pthread_create() 当我们想创建线程的时候,只 ...
- 3DMax的OFusion插件的使用问题
使用OFusion将3D max导出到现场Ogre的Mesh该方法是经常使用的非.的一些问题,在这里为方便摘要. 1.OFusion得到: http://download.csdn.net/detai ...
- c#多线程随记回顾
C#多线程随记回顾 1.创建多线程方式知道的有三种: ---手动创建Thread.使用线程池.使用task任务 ---手动创建Thread,分两种带参数和不带参数的帮助委托器 eg: //帮助器委托 ...