多态这一章很多是说的是设计思想。多态的好处就不说了,网上有太多的文章来描述。下面主要对书里面一些抽象的文字进行解释。
如果一种语言想实现动态绑定,就必须拥有某种机制,以便在运行时能够判断对象的类型,从而调用恰当的方法。也就是说编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。
我们知道通过一个对象头能够找到该对象所属的类的信息,这样在运行的时候,我们找到该类的方法区中的方法,然后进行调用。值得注意的是,这部分是在运行阶段进行的,在编译阶段,我们是不能够得到一个对象的准确的类型的。
Java
中除了static
方法和final
方法(private
方法属于final
方法)之外,其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑定——它会自动发生。
首先说static
方法,在Java
中static
方法指的是类的静态方法。这些方法存放在方法区中。子类继承父类之后,如果重写了该方法,前面父类的方法只是被隐藏了,并没有消失。static
方法不是动态绑定的,就是说我们调用某个对象的静态方法时,其在编译阶段就确定下来是哪一个方法了。
public class Test {
static void printAOrB(A a) {
a.print();
}
public static void main(String[] args) {
A b = new B();
//输出A
printAOrB(b);
}
}
class A {
static void print() {
System.out.println("输出A");
}
}
class B extends A {
static void print() {
System.out.println("输出B");
}
}
然后说下final
(private
包括在内)方法。当父类中有final
方法的时候,子类时认为这个final
方法不能够被重写,只能够使用父类中的该方法。所以编译器能够确定这个方法一定是属于父类的,就会直接调用。
只有非
private
方法才能够被覆盖,但是还需要密切注意覆盖private
方法的现象。这是虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切的说,在导出类中,对于基于基类中的private
方法,最好采用不同的名字。
private
方法,要么子类认为其不存在(private
),要么父类告知子类不能够进行重写(final
),这个时候多态性就会消失,统一使用父类中的方法。所以在子类中,我们想要真正实现各自的方法,应该采用一个与父类中方法不同的名字。
class A {
private void print() {
System.out.println("输出A");
}
public static void main(String[] args) {
A a = new B();
//输出A
a.print();
}
}
class B extends A {
void print() {
System.out.println(输出B");
}
}
在任何构造器内部,整个对象可能只是部分形成——我们知道其父类对象肯定已经完成初始化了。如果构造器只是在构建对象过程中的一个步骤,并且该对象所属的类是从这个构造器所属的类导出的,那么导出部分在当前构造器正在被调用的时候仍旧是没有被初始化的。然而,一个动态绑定的方法调用却会向外深入到继承层次结构内部,它可以调用导出类中的方法。如果我们是在构造器内部这样做,那么就可能会调用某个方法,而这个方法所操纵的成员可能还未初始化——这肯定会导致灾难。
Emmm,上面这段话是在是太拗口难以理解了。简单来说就是在执行某个类的构造器时,其父类的构造器肯定已经执行完成了。如果在父类构造器执行的时候调用了父类的方法,而这个父类的方法在子类中又有被重写过,那就可能会有问题。因为这个时候不能保证所有的子类属性都能够被正确的初始化。举个简单的例子吧:
class A {
A() {
System.out.println("start init A");
print();
System.out.println("complete init A");
}
void print() {
System.out.println("输出A");
}
}
class B extends A {
private int x = 1;
B(int a) {
x = a;
print();
}
void print() {
System.out.println(x);
}
}
/**
* Test
*/
public class Test {
public static void main(String[] args) {
A a = new B(11);
}
}
输出的值为:
start init A
0
complete init A
11
这个输出结果我一直有一个点不太理解,希望有朋友能够帮我解惑,为什么父类构造器执行的时候会调用子类的方法。
而至于在父类中输出0
则是因为在父类构造器在执行的时候,子类的构造器还没有执行,其中所有的变量都是0
值。
在编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他的方法”。在构造器内唯一能够安全调用的那些方法是基类中的
final
方法(也适用于private
方法,它们自动属于final
方法)。这些方法不能被覆盖,也就不会出现上述令人惊讶的问题。
网友评论