继承,组合,代理
在《Thinking in Java》里面复用类有3种方式:
继承:
子类继承父类,子类可以使用父类的方法(public及protected修饰),子类也必须使用父类的构造器进行对象的初始化。一般父类在继承体系中是一个通用类,抽取出某类实体的公共特征,子类继承父类开发出特殊版本(比如List为父类接口,ArrayList及LinkedList为适用于不同场景的子类)
继承的目的是使用父类的接口,为了向上转型达到多态,在方法的调用中可以达到很好的扩展性。
组合:使用类的功能,不使用类的接口,一般将类设定为private,对外只展示新类的接口
代理:代理是继承和组合的中庸之道,在代理中可以使用类的功能(类似组合),并且在新类中暴露该类的成员对象的所有方法(类似继承),下面以一个例子来说明代理
飞机需要一个飞行控制模块:
public class AirplaneControls {
void up(int velocity) {}
void down(int velocity) {}
void turnLeft(int velocity) {}
void turnRight(int velocity) {}
void forward(int velocity) {}
}
第一种方式是直接使用继承来实现
public class Airplane extends AirplaneControls {
private String name;
public Airplane(String name) { this.name = name; }
public String toString() { return name; }
public static void main(String[] args) {
Airplane protector = new Airplane("Boston");
protector.forward(100);
}
}
但是飞机不单单是一个飞行控制模块,所以可以使用代理:
public class AirplaneDelegation{
private AirplaneControls controls;
public AirplaneDelegation(string name){
this.name = name;
}
public void up(int velocity){
//可以在执行前进行身份认证
controls.up(velocity);
//可以在执行后进行日志记录
}
//省略其他方法的代理
public static void main(String[] args) {
AirplaneDelegation protector = new AirplaneDelegation("Boston");
protector.forward(100);
}
}
如上所示:通过代理也可以服用类的方法,并且在方法的执行前及执行后可以加上一些操作,这个技巧在Java的框架如Spring AOP的事务性控制中有很多类似用法。
继承和组合的选择
关于继承和组合的选择,一个最清晰的判断方法是问一下自己是否需要从新类向基类进行向上转型。
作者建议在设计类的时候,优先选择组合(或者代理),组合更具有灵活性可以动态选择类型(行为),而继承在编译时就知道继承体系的确切类型了,必要时再选择继承。
下面使用Map例子来说明 继承与组合区别
import java.util.AbstractMap;
import java.util.Set;
//如果我想要使用Map类的接口,则可以使用继承,这样在方法中showAll里面就可以通过传入是Map的子类对象(利用了向上转型及多态)来保证代码的扩展性
public class ExtendMap extends AbstractMap<Integer, String> {
@Override
public Set<java.util.Map.Entry<Integer, String>> entrySet() {
return null;
}
public void showAll(AbstractMap<Integer, String> map){
for(Integer number : map.keySet()){
System.out.print(number+" : ");
System.out.println(map.get(number));
}
}
}
import java.util.HashMap;
//通过使用组合,在新类中使用类的功能
public class CombinationMap {
private HashMap<String, Integer> map = new HashMap<String, Integer>();
public int countString(String s){
int count = map.get(s);
map.put(s, count+1);
return count+1;
}
}
继承的初始化及清理
继承的初始化顺序:
- 初始化父类的static变量,static代码块
- 初始化父类的成员变量及构造器,构造父类对象,初始化父类的成员变量
- 初始化子类的static变量,static代码块
- 初始化子类的成员变量及构造器,构造子类对象
清理的顺序和初始化的顺序相反,先清理子类的再清理父类(以防止父类被清理了,子类的清理可能需要引用父类的某些方法或数据),除了内存以外,不能依赖垃圾回收器去做任何事情,所以对于对象的清理(如连接资源,IO流)需要你手动清理关闭某些资源。
标题final 关键字用法
- final修饰基本类型的变量为常量,赋值后无法修改
- final修饰的对象不可改变引用,但是可以改变其值
- final修饰的方法是不可被子类所覆盖,出于设计的考虑,保证该方法行为在继承体系里面保持不变
- final修饰的类是不能被继承的,该类的设计永远不需要改变,不需要有子类来特殊化
final例子:
import java.util.ArrayList;
public class FinalDemo {
final class Demo{
public int number = 0;
}
// class Demo2 extends Demo{
//error: The type Demo2 cannot subclass the final class FinalDemo.Demo
//不能继承Demo
// }
public static void changeFinal(final ArrayList<Integer> list,int number){
//无法改变final修饰的对象的引用
//list = new ArrayList();//error: The final local variable list cannot be assigned.
number += 1;//
list.add(number);//可行,可以改变final修饰的对象的值
//间接证明了方法传参里面 基本类型及对象的变量是不一样的:基本类型传入的时候会创造新的变量,对象传入的使用是传入对象的引用
}
public static void main(String[] args) {
final int i = 100;
//i ++;//error: The final local variable i cannot be assigned.
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(i);
changeFinal(list1, i);
System.out.println(list1);//[100, 101]
}
}