关于Java多态
在面向对象的语言中,有三个基本特征:封装,继承,多态。封装是通过将某些特征和行为合并起来创建新的数据类型;继承是子类通过 extends 关键字来获取父类的方法和属性,同时继承允许将对象视为它自己本身的类型或子类类型来处理;多态的作用就是消除类型之间的耦合关系,多态方法调用允许一种类型表现出与其他类型的区别,只要他们是从同一个父类中继承而来的即可。
向上转型
我们知道对象既可以作为它自己本身的类来使用,也可以作为它的父类类型来使用,像这种把对某个对象的引用作为其父类类型的引用的做法,被称之为向上转型。我们看看下面这个例子:
public class Ball {
public void play() {
System.out.println("play ball");
}
/**
* 该方法需要传入一个 Ball 类型的对象
*
* @param ball
*/
public static void fun(Ball ball) {
ball.play();
}
public static void main(String[] args) {
//创建一个 baseball 对象
Baseball baseball = new Baseball();
fun(baseball);
fun(new Basketball());
}
}
class Baseball extends Ball {
/**
* 重写 play 方法
*/
public void play() {
System.out.println("play baseball");
}
}
class Basketball extends Ball {
/**
* 重写 play 方法
*/
public void play() {
System.out.println("play basketball");
}
}
在 fun() 方法中需要传入一个 Ball 类型的对象,同时也接受任何继承自 Ball 类的对象,例子中一个 Baseball 类的对象,这样做是允许的,因为 Baseball 继承自 Ball,所以 Ball 的接口必定存在于 Baseball 中。
绑定
但是这样做也带来一个问题,由于 fun() 方法接受一个 Ball 引用,那么编译器要怎样才能知道这个 Ball 引用指向的是 Baseball 对象还是 Basketball 对象呢,编译器是无从得知的,所以我们在此引入一个概念叫做绑定。先说下概念,绑定就是将一个方法调用和一个方法主体关联起来,分为前期绑定和后期绑定,上述做法就是前期绑定,当只告诉编译器要传入一个 Ball 引用的时候,编译器无法知道究竟要调用那个方法才对。所谓后期绑定,就是在运行的时候根据对象的类型进行绑定,后期绑定也叫做动态绑定和运行时绑定。
Java 中除了 static 方法和 final 方法之外,其它所有的方法都是后期绑定。将方法声明为 final,不仅可以防止被覆盖,还能有效关闭动态绑定,这样编译器就可以为 final 方法调用生成更加有效的代码。但是这样做对整体性能不会带来什么改观,我们前面已经说过了,要谨慎使用 final,根据设计来判断是否使用。
当我们知道 Java 中所有方法都是通过动态绑定来实现多态之后,我们可以只需要与父类打交道了。那么上面的例子中 fun() 方法的传递的参数可以这么初始化:
//创建一个 Ball 对象
Ball ball = new Baseball();
fun(ball);
这里我们创建了一个 Baseball 对象,并且把得到的引用直接赋值给 Ball 的对象,这种将一种类型赋值给另一种类型的做法看似错误,但实际上是没有问题的,因为通过继承,Baseball 就是一种 Ball。
我们再来看看下面这个例子:
public class Ball {
public void play() {
System.out.println("play ball");
}
/**
* 该方法需要传入一个 Ball 类型的对象
* @param ball
*/
public static void fun(Ball ball) {
ball.play();
}
/**
* 随机返回 Ball 的子类对象
* @return
*/
public static Ball next(){
//产生 0 到 1 的随机数
int random = new Random().nextInt(2);
if(random == 0){
return new Baseball();
}else{
return new Basketball();
}
}
public static void main(String[] args) {
//调用 play 方法
next().play();
}
}
class Baseball extends Ball {
/**
* 重写 play 方法
*/
public void play() {
System.out.println("play baseball");
}
}
class Basketball extends Ball {
/**
* 重写 play 方法
*/
public void play() {
System.out.println("play basketball");
}
}
我们通过 next() 方法随机产生 Ball 的引用(向上转型是在 return 语句中发生的),然后通过该引用来调用 play() 方法,编译器是无法知道具体的类型到底是什么的。因此,在编译时,编译器不需要获得任何具体的类型信息就能进行正确的调用,对 play() 方法的调用都是通过动态绑定进行的。
这种只与父类接口通信的程序是可扩展的,因为可以从通用的父类继承出新的子类,从而可以添加新的功能,那些调用父类接口方法不需要任何改动就可以作用与新类。
还有一点,就是如果某个方法是静态的,那么它的行为就不具有多态性,因为静态方法是与类关联的而不是与某个对象相关联的。