【Java基础】-【面向对象】-多态

在面向对象程序设计语言中,多态是三种基本特性之一(其他两种分别是 抽象、继承)。

多态又称动态绑定、后期绑定、运行时绑定。

我们先来看一个多态的例子:

public class Test {
public static void main(String[] args) {
play(new Cat());
play(new Dog());
}
public static void play(Animal animal) {
animal.enjoy();
}
}

class Animal {
public Animal() {}
public void enjoy() {
System.out.println("叫声...");
}
}

class Cat extends Animal{
public Cat() {}
@Override
public void enjoy() {
System.out.println("喵...");
}
}

class Dog extends Animal {
public Dog() {}
@Override
public void enjoy() {
System.out.println("汪...");
}
}

我们可以看到,在main方法里面我们调用的是play()方法,这个方法的参数是一个Animal类型的对象,但是实际上我们传入的是Animal的子类的对象,而程序运行实际上调用的就是子类的enjoy()方法。像这种 父类引用指向子类对象的 现象就是多态。

多态机制可以有效的消除数据类型之间的耦合度增加代码的可扩展性与于可读性改善代码的组织结构,为什么这么说呢,因为在你使用基类接口实现多态机制后,我们只需要编写与基类相关联的代码就可以了,无需关心具体实现的子类以及以后有可能增加的子类,并且这些代码对于所有子类都是可以正确运行的,因为最终程序调用的是你传入的子类所重写的方法。

接下来我们开看看Java在内部是如何实现这种机制的吧,首先你需要知道什么是“绑定”,所谓绑定,就是 将一个‘方法调用’和一个‘方法主体’关联起来,绑定分为两种:

在程序执行前进行绑定,通常是由编译器和连接程序实现,叫做前期绑定,对于上面的程序,当编译器只有两个Animal类型的引用的时候,它无法知道调用的是哪个play()方法。此时,就需要后期绑定,所谓后期绑定,也称动态绑定,它的含义就是在运行过程中根据对象的类型进行方法绑定,后期绑定的机制可以在运行的时候判断对象的类型,从而调用恰当的方法,也就是说,编译器一直不知道对象的实际类型,他所掌握的只是一个基类的引用。

多态通过分离“做什么”与“怎么做”,来将接口与实现分离开,有了多态,就可以很好的提高程序的扩展性、降低类型之间的耦合度,在主业务逻辑代码中(main),我们根本不用考虑传入play()方法中的是什么,只要它是Animal类型,那么就可以正确运行,在以后业务中如果增加一些新的Animal子类型,也无需修改现有的基类方法!

多态的三个重要特征:

1. 继承(或接口实现)

2. 重写(动态绑定子类方法)

3. 父类引用指向子类对象(向上转型)

多态的缺陷:

1. 对于“假覆盖”:

基类中的private方法是不对子类可见的,只有非private方法才能被覆盖,子类中的“覆盖”private方法,实际上是一个全新的、与基类没有关系的方法,此时如果使用多态进行调用,编译器不会报错,但是也不是我们所期望的结果,因为你需要知道,对于子类扩展方法,它们不会暴露在基类的引用视野中

2. 访问“域”和静态方法

我们先来看这个例子:

public class Test {
    public static void main(String[] args) {
Super s = new Sub();
System.out.println(s.getN());
System.out.println(s.n);
}
}
class Super {
public int n = 0;

public int getN() {
return n;
}
}
class Sub extends Super {
public int n = 1;

public int getN() {
return n;
}
public int getSuperN() {
return super.n;
}
}
我们看上面的程序,按照多态机制,我们很容易想象对于子类对象s它所调用的方法在执行期会自动转成自己的方法,而不是父类引用的方法,但是调用域(成员变量)的时候,则不存在多态,因此s.n所调用的依然是父类中的变量,程序执行结果是:1  0,即:只有普通的方法调用可以使多态的

另外,静态方法是不存在多态的,因为多态本质是基于解决降低型耦合度的一种机制,而静态的方法不是针对于某一个对象,它属于整个类的,因此,静态方法不存在多态。

3. 构造器内部的多态
首先,这其实并不属于一种缺陷,这是一个很有意思的程序,来自于thinking in java :

public class Test{
public static void main(String[] args) {
new Teacher(10);
}
}

class People {
public People() {
System.out.println("before people draw...");
draw();
System.out.println("after people draw...");
}
void draw() {
System.out.println("people draw...");
}
}

class Teacher extends People {
private int r = 1;
public Teacher(int n) {
super();
r = n;
System.out.println("Teacher ... r = "+r);
}
public void draw() {
System.out.println("Teacher draw ... r = "+r);
}
}

 

 

 

我们可以看到Teacher类重写了People类的draw方法,而在父类构造器中调用了draw方法,由于实例化子类必须首先调用父类构造器,因此,隐式的存在了多态现象,我们期望的结果就是:

1>. 调用基类构造器:

1.1>. 输出before people draw...

1.2>. 多态:动态绑定到子类重写的draw方法:Teacher draw ... r = 1

1.3>. 输出after people draw...

2>. 初始化自己的成员

2.1>.赋值

2.2>. 输出Teacher ... r = 100

但是,实际当我们运行程序的时候,其结果却是:

before people draw...
Teacher draw ... r = 0
after people draw...
Teacher ... r = 10

区别在于构建父类对象的时候子类对象的成员变量r的值是0,这就涉及到了初始化的顺序:

首先:在其他任何事物初始化之前,对象的存储空间初始化成二进制0

随后:逐步向下调用基类构造器,上述例子则是调用People构造器,由于上一步的原因,多态所调用的draw方法中的r初始化成了0

然后:按照成员声明顺序进行初始化

最后:执行子类的构造器主体

对于构造器安全的做法:在构造器内,为以安全调用的那些方法是基类中的final方法(当然包括peivate方法,private方法是隐式的final),因为这些方法不能被覆盖!

 

多态,是一项让程序员“将改变的事物与未变的事物分离开”的重要技术!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章