0%

第六章 类再生

摘要:
1.合成与继承
2.final


6.1 合成的语法

对于非基本类型的对象来说,只需将句柄置入新类即可;对于基本类型来说,则需在自己的类中定义它们。
(类似于将一堆零件合成,制造出一辆汽车)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HelloWorld {
static final int a = 1;
public static void main(String[] args) {
Test x = new Test("abc");
System.out.println(x); //
}
}
class Test{
private String s; // 此处未调用构造器,new时调用
Test(String s){
this.s = s;
}
public String toString(){
return s;
} //重写了toString方法
}

P.S.

1.toString: 每种非基本类型的对象都有一个toString()方法。若编译器希望一个String(如上面的println()方法),但却得到了某个对象,就会自动调用这个方法。
Object中的toString

1
2
3
4
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//example out: pers.wxy.hello.test.Test@10f87f48

2.在实际使用某个对象之前初始化句柄,可减少不必要的开销:

1
2
3
4
String s;
if(s == null)
s = new String("abc");
System.out.println(s);

3.单元测试:
采用将main()置入每个类的做法,可方便的对每个类都进行单元测试。而且在完成测试后,不用将main()删去,可将它保留下来,用于以后的测试。

1
2
3
4
5
6
7
8
9
10
11
class TestClass {
public static void main(String[] args){
System.out.println("Game");
}
}
public class Access {
public static void main(String[] args){
System.out.println("Chesss");
TestClass.main(args);
}
}

6.2 继承的语法

  1. extends 关键字
  2. super() ,super(x)调用父类构造器, super.test()调用父类方法。在衍生类的构造器中,java会自动插入对基础类构造器(默认)的调用,但有参构造器需要自己添加。
  3. 如果构造器为private,可以防止继承
  4. 计划继承时,一个比较好的规则是将所有字段都设为private,并将所有方法都设为public
  5. protected成员可被衍生类访问
  6. 名字的隐藏:如果java基础类有一个方法名被“过载”使用多次,在衍生类里对这个方法的重新定义就不会隐藏任何基础类的版本。且无论方法是在哪一级中定义,过载都会生效。因此很少会用于基础类里完全一致的签名和返回类型来覆盖同名的方法,否则会使人感到迷惑。
  7. 选择合成还是继承:

    如果想利用新类内部一个现有类的特性,而不想使用它的接口,通常应选择合成。也就是说,我们可嵌入一 个对象,使自己能用它实现新类的特性。但新类的用户会看到我们已定义的接口,而不是来自嵌入对象的接 口。考虑到这种效果,我们需在新类里嵌入现有类的private 对象。
    有些时候,我们想让类用户直接访问新类的合成。也就是说,需要将成员对象的属性变为public。成员对象 会将自身隐藏起来,所以这是一种安全的做法。而且在用户知道我们准备合成一系列组件时,接口就更容易理解。

利用是否需要上溯造型判断:若需要上溯,就需要继承;但如果不需要上溯造型,应提醒自己防止继承的滥用。

上溯造型 Upcasting:

上溯造型是绝对安全的,因为我们是从一个更特殊的类型转型到一个更常规的类型。(衍生类是基础类的一个超类)

1
2
3
4
5
6
7
8
9
class Shape{
static void draw(Shape shape) {}
}
public class Triangle extends Shape{
public static void main(String[] args){
Triangle triangle = new Triangle();
Shape.draw(triangle);
}
}

final 关键字

  1. 在对一个常数(基本类型)定义时,必须给定初始值
  2. 对于对象句柄,final会将句柄变成一个常数,不能将句柄指向另一个对象,但对象本身是可以修改的。进行声明时,必须将句柄初始化到一个具体对象。(数组同理
  3. 无论static还是static字段,都只能储存一个数据,而且不得改变。static强调它们只有一个,而final表明它是一个常数。
  4. 一般static和final字段是编译期的值,但也有例外。
1
2
final double a = Math.random(); //运行期的值,该类的两个不同对象的a值不同
static final double b = Math.random(); //编译期的值,该类的两个不同对象的b值相同

P.S. static final 的基本类型,命名规则是全部大写。(上面的b值在编译期是未知的,所以没有大写)
5. 空白final:位于类内部的空白final字段对每一个对象都是不同的,同时又保持着不变的特性。但无论如何,空白final都必须在实际使用前得到正确的初始化(如在构造器中)。
6. final 自变量。(此时仍能为fianl自变量分配一个null句柄,同时编译器不会捕捉它)
7. final 方法。
①防止任何继承类覆盖或改写改方法;
②提高程序执行的效率。

采用final 方法的第二个理由是程序执行的效率。将一个方法设成 final 后,编译器就可以把对那个方法的 所有调用都置入“嵌入”调用里。只要编译器发现一个final 方法调用,就会(根据它自己的判断)忽略为 执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这 样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入 代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java 编译器能自动侦测这些情 况,并颇为“明智”地决定是否嵌入一个final 方法。然而,最好还是不要完全相信编译器能正确地作出所 有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为 final

P.S. 类内所有private方法都会自动成为final,因为永远不会被覆盖或改写。
8. final类。
禁止继承,但没有其他更多的限制,成员可以是final,也可以不是。
由于它禁止了继承,所以所以一个final类中所有方法都会默认为final。