Thinking in java 第7章 复用类
7.1 组合语法
1. 初始化引用的位置:
- 定义对象的地方。这意味着它们总能够在构造器被调用之前被初始化。
- 在类的构造器中。
- 正要使用这些对象之前,这种被称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
- 使用实例初始化。
7.2 继承语法
1. 当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。区别在于,后者来自与外部,而基类的子对象被包装在导出类对象内部。
2. 构建过程是从基类向外扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
7.3 代理
1. 代理是继承与组合之间的中庸之道,避免基类的方法在导出类中全部暴露出来(在导出类中创建基类的private对象,并通过导出类的方法调用基类方法)。
7.8 final关键字
1. 对于基本类型,final使数值恒定不变;对于对象引用,final使引用恒定不变,然而对象其自身是可以修改的。
2. 对于空白final(即定义时未初始化),要在构造函数中进行初始化。
3. final参数,意味着无法在方法中更改参数引用所指向的对象。
4. final方法,锁定方法,以防任何继承类修改它的含义。过去还可能因为效率,不过一部分被优化了。
5. private方法都隐式地指定为是final,而private方法可以被覆盖。
6. final类,太监类。
习题
练习1:创建一个简单的类。在第二个类中,将一个引用定义为第一个类的对象。运用惰性初始化来实例化这个对象。
package Chapter7;
public class E1 {
E1temp e;
void func() {
e = new E1temp();
e.setS("aaa");
System.out.println(e.getS());
}
public static void main(String[] args) {
E1 e = new E1();
e.func();
}
}
class E1temp{
private String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
练习2:从Detergent中继承产生一个新的类。覆盖scrub()并添加一个名为sterilize()的新方法。
extend再@override一下即可。略。
练习3:证明前面这句话(指构建过程是从基类向外扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化)。
即P129的代码。略。
练习4:证明基类构造器:(a)总是会被调用;(b)在导出类构造器之前被调用。
同上。略。
练习5:创建两个带有默认构造器(空参数列表)的类A和类B。从A类中继承产生一个名为C的新类,并在C内创建一个B类成员。不要给C编写构造器。创建一个C类对象并观察其结果。
package Chapter7;
public class E5 {
public static void main(String[] args) {
E5C e = new E5C();
}
}
class E5A{
E5A() {
System.out.println("E5A has been built");
}
}
class E5B{
E5B() {
System.out.println("E5B has been built");
}
}
class E5C extends E5A{
E5B e = new E5B();
}
/*
E5A has been built
E5B has been built
*/
练习6:证明前一段话(调用基类构造器必须是你在导出类构造器中要做的第一件事)。
若尝试在非第一句调用基类构造器或基类构造器的默认构造器被覆盖且未被重写且在子类构造器中没有添加时,会发生编译错误。
练习7:修改练习5,使A和B以带参数的构造器取代默认的构造器。为C写一个构造器,并在其中执行所有的初始化。
package Chapter7;
public class E5 {
public static void main(String[] args) {
E5C e = new E5C();
}
}
class E5A{
E5A(int i) {
System.out.println("E5A has been built " + i );
}
}
class E5B{
E5B(int i) {
System.out.println("E5B has been built " + i);
}
}
class E5C extends E5A{
E5B e;
E5C() {
super(1);
e = new E5B(2);
}
}
/*
E5A has been built 1
E5B has been built 2
*/
练习8:创建一个基类,它仅有一个非默认构造器;再创建一个导出类,它带有默认构造器和非默认构造器。在导出类的构造器中调用基类的构造器。
同上,导出类中每个构造器都要调用基类的非默认构造器。
练习9:创造一个Root类,令其含有名为Component1,Component2,Component3的类的各一个实例。从Root中派生一个类Stem,也含有上述各“组成成分”。所有的类都应带有可打印的相关信息的的默认构造器。
package Chapter7;
public class E9 {
public static void main(String[] args) {
E9Stem stem = new E9Stem();
}
}
class E9Root{
E9Component1 e1 = new E9Component1(1);
E9Component2 e2 = new E9Component2(1);
E9Component3 e3 = new E9Component3(1);
public E9Root() {
System.out.println("Root ahs been built");
}
}
class E9Stem extends E9Root{
E9Component1 e1 = new E9Component1(2);
E9Component2 e2 = new E9Component2(2);
E9Component3 e3 = new E9Component3(2);
E9Stem() {
System.out.println("Stem has been built");
}
}
class E9Component1{
E9Component1(int i) {
System.out.println("E91 has been built " + i);
}
}
class E9Component2{
E9Component2(int i) {
System.out.println("E92 has been built " + i);
}
}
class E9Component3{
E9Component3(int i) {
System.out.println("E93 has been built " + i);
}
}
/*
E91 has been built 1
E92 has been built 1
E93 has been built 1
Root ahs been built
E91 has been built 2
E92 has been built 2
E93 has been built 2
Stem has been built
*/
练习10:修改练习9,使每个类都仅具有非默认的构造器。
同上。略。
练习11:修改Detergent.java,让它使用代理。
即用私有对象取代继承,并通过公共方法调用私有对象方法。略。
练习12:将一个适当的dispose()方法的层次结构添加到练习9的所有类中。
略。相当于方法的调用顺序,先调用导出类的再调用基类。
练习13:创建一个类,它应带有一个被重载了三次的方法。继承产生了一个新类,并添加一个该方法的新的重载定义,展示这四个方法在导出类中都是可以使用的。
略。重载后可以直接使用。
练习14:在Car.java中给Engine添加一个service()方法,并在main()中调用该方法。
car.engine.service();
练习15:在包中编写一个类,类应具备一个protected方法。在包外部,试着调用该protected方法并解释其结果。然后,从你的类中继承产生一个类,并从该导出类的方法内部调用该protected方法。
略。在包外部非子类不能调用protected方法,子类可以。
练习16:创建一个名为Amphibian的类。由此集成产生一个称为Frog的类。在基类中设置适当的方法。在main()中,创建一个Frog向上转型至Amphibian,然后说明所有方法都可以工作。
package Chapter7;
public class E16 {
public static void main(String[] args) {
E16Amphibian frog = new E16Frog();
frog.sound();
frog.crawl();
frog.swim();
//frog.eatFly();
}
}
class E16Amphibian{
void crawl() {
System.out.println("pa");
}
void swim() {
System.out.println("you");
}
void sound() {
System.out.println("not unique");
}
}
class E16Frog extends E16Amphibian {
void sound() {
System.out.println("gua");
}
void eatFly() {
System.out.println("Eat");
}
}
/*
gua
pa
you
*/
向上转型后不能调用子类特有方法,但可以调用重写方法。
练习17:修改练习16,使Frog覆盖基类中的方法的定义(令新定义使用相同的方法特征签名【即方法的定义】)。请留心main()中都发生了什么。
同上。略。
练习18:创建一个含有static final域和final域的类,说明二者间的区别。
package Chapter7;
public class E18 {
public static void main(String[] args) {
E18temp e1 = new E18temp("111");
E18temp e2 = new E18temp("111");
System.out.println(e1.j);
System.out.println(e2.j);
System.out.println(e1.i == e2.i);
System.out.println(e1.j == e2.j);
}
}
class E18temp{
final String i;
static final String j = "jjj";
E18temp(String a) {
this.i = new String(a);
}
}
/*
jjj
jjj
false
true
*/
static final类共享同一个地址,而final各用各的;static final类在第一次使用该类时就会被初始化,早于final。
即两者都有final的特性,区别等于static和非static的区别。
练习19:建一个含有指向某对象的空白final引用的类。在所有构造器内部都执行空白final的初始化动作。说明Java确保final在使用前必须初始化,且一旦被初始化即无法改变。
同上。略。
练习20:展示@Override注解可以解决本节问题。
@Override若未正确重写会报错。略。
练习21:创建一个带final方法的类。由此继承产生一个类并尝试覆盖该方法。
略。
练习22:创建一个final类并试着继承它。
略。
练习23:请证明加载类的动作仅发生一次。证明该类的第一个实体的创建或者对static成员的访问都有可能引起加载。
package Chapter7;
public class E23 {
public static void main(String[] args) {
E23temp2 e1 = new E23temp2();
E23temp1 e2 = new E23temp2();
}
}
class E23temp1{
static{
System.out.println("AAA");
}
E23temp1() {
System.out.println("BBB");
}
}
class E23temp2 extends E23temp1{
static{
System.out.println("CCC");
}
E23temp2() {
System.out.println("DDD");
}
}
/*
AAA
CCC
BBB
DDD
BBB
DDD
*/
练习24:在Beetle.java中,从Beetle类继承产生一个具体类型的“甲壳虫”。其形式与现有类相同,跟踪并解释其输出结果。
意义不明。略。