Think in Java 第七章(复用类)
复用代码是Java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。
---继承和组合两种代码重用机制
7.1 组合语法
将对象引用置于新类中。
7.2 继承语法
没有直接用extends明确的继承类时,会隐式继承根类Object。
- 7.21 初始化基类
Java会在导出类的构造器中插入对基类构造器的调用
继承不只是复制基类的接口的新类,当创建一个导出类对象时,该类包含了一个基类的子对象。这个子对象与你用基类直接创建的对象时一样的。二着区别在于后者来自于外部,而基类的子对象被包装在导出类的对象外部。
class Art{
Art(){print("Art constructor");}
}
class Drawing extends Art{
Drawing (){print("Drawing constructor");}
}
public class Cartoon extends Drawing {
public Cartoon (){print("Cartoon constructor");}
public stataic void main(String[] args){
Cartoon x = new Cartoon();
}
}
结果
Art constructor
Drawing constructor
Cartoon constructor
构建过程是从基类向外扩散,基类在导出类构造器可以访问它之前,已经完成了初始化。(如果没有默认的构造器,或者想调用带参的构造器,必须用super显示地编写调用基类构造器语句)
7.3 代理
Java中没有直接支持代理。我认为的代理是两个没有直接关系的类同过代理类得到协调(可以更好的设计类 功能明确 清晰)
7.4 结合使用组合和继承
- 7.41 确保正确清理
我以为垃圾回收机制很智能,其实不然,不能依赖垃圾回收机制处理任何事情,需要你自己写方法,但不要使用finalize()。 - 7.42 名称屏蔽
如果Java的基类拥有某个已被多次重载的方法名称,那么导出类中重新定义该方法名称并不会屏蔽在基类中的任何版本。无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。
7.5 在组合与继承之间选择
组合是想在新类中使用现有类的功能而非他的接口。在新类中嵌入某个对象,让其实现需要的功能。新类用户看到的只是新类定义的接口,而非所嵌入对象的接口。这时需要在新类中嵌入一个private对象。
class Engine{
public void start(){}
public void rev() {}
public void stop(){}
}
class Wheel{
public void inflate(int psi){}
}
class Window{
public void rollup(){}
public void rolldown(){}
}
class Door{
public Window window = new Window()
public void open{}
public void close{}
}
public class Car{
public Engine engine = new Engine();
public Wheel[] wheel = new Wheel[4];
public Door left = new Door(),right = new Door();
public Car(){
for(int i=0;i<4;i++){wheel[i] = new Wheel();}
}
public static void main(String[] args){
Car car = new Car();
car.left.window.rollup();
car.wheel[0].inflate(77);
}
}
成员成为public将又处于客户端程序员了解怎样使用类,降低了类开发者面临的复杂度。但是,这只是一个特例,一般情况下应该使域成为private.
继承,使用某个类,开发一个通用特殊版本(通用类)。
is-a:继承
has-a:组合
7.6 protected 关键字
类用户而言,protected修饰后是private,对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却可以访问。
尽管可以创建protected域,但是最好的方式还是讲域保持为private,你应该一直保留“更改底层实现”的权利。然后通过protected方法来控制类的继承职者的访问权限。
7.7 向上转型
-
7.71 为什么称为向上转型
从一个专用类型向通用类型的转型。安全,导出类是基类的超集。(可能丢失方法) -
7.72 再论组合与继承
7.8 final关键字
表示无法改变。
- 7.81 final数据
一个即是static 又是final的域只占据一段不能改变的存储空间。(编译期常量)
修饰对象时,引用不能改变,对象自身可以修改。 - 7.82 final方法(设计类,明确禁止覆盖)
1.把方法锁定,防止任何继承类修改他的含义。(使其方法不会被覆盖)
2.效率,在Java早期实现中,就是同意编译器针对该方法的所有调用都转为内嵌调用。但是方法很大时,不太会带来性能的提高。在使用Java SE5/6 虚拟机会进行优化,因此不需要使用final方法进行优化了。
final和private关键字
类中所有的private方法都隐式指定为final. - 7.83 final类
不可以被继承,没有子类(final类中所有的方法都隐式指定为final) - 7.84 有关final忠告(没看懂)
7.9 初始化及类的加载
Java中每个类的编译代码都在于它自己的独立文件中。该文件在只在需要使用程序代码时才会被加载。一般来说,可以说“类的代码在初次使用才加载。”指的是加载发生于创建类的第一个对象时,但是当访问static域或方法时,也会发生加载。(定义为static的东西只会被初始化一次)
- 7.91 继承与初始化
class insect{
private int i = 9;
protected int j;
insect(){
print("i ="+i+",j="+j);
j = 39;
}
private static int x1 = printInt("staatic Insect.x1 initialized");
static int printInt(String s){
print(s);
return 47;
}}
public class Beetle extends Insect{
private int k = printInit("Beetle.k initialized");
public Beetle(){
print("k = " + k);
print("j = " + j);
}
private static int x2 = printInt("static Beetle.x2 initialized");
public static void main(String[] args){
print("Beetle constructor");
Bettle b = new Beetle();
}
}
//输出
static insect.x1 initialized
static beetle.x2 initialized
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
在Bettle上运行Java时,所发生的第一件事情就是试图访问Bettle.main()(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Bettle.class文件中)。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字extends得知),于是它在继续进行加载。不管你是否打算生成一个该基类对象,这都要发生。
如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的static初始化(在此例子insect)会被执行,然后是下一个导出类,以此类推,这种方式很重要,因为导出类的static初始化可能会依赖基类成员能否被正确初始化。
至此为止,必要的类都以加载完毕,对象就可以被创建了。首先,对象中所有的基本类型都会被设置为默认值,对象引用被设置为null(这是通过将对象内存设置为二进制零值而一举生成的,然后基类的构造器会被调用。在本例中,它是被自动调用的。但也是可以用super来指定对基类构造器的调用(正如在Bettle()构造其中的第一步操作))。基类构造器和导出类的构造器一样,以相同的顺序来经理相同的过程。在基类构造器完成之后,实例变量按其次被其次序初始化。最后,构造器 的其余部分被执行。