Java中将一个方法调用同一个方法主体关联起来被称作绑定。绑定分为前期绑定和后期绑定。前期绑定是在编译器决定的,而后期绑定是在程序运行时决定的。Java中除了static方法和final方法(private方法也是final方法,只不过是隐式的为final)之外,其他所有的方法都是后期绑定。Java类的多态指的是当将子类向上转型为父类型并调用父类型中相应的方法时,多态机制会根据动态绑定自动判断出调用相应的子类的方法。也就是说多态机制的存在的基础是子类实现了对父类中相应方法的函数覆盖。比如有一个Shape类,该类有一个draw方法,并有Circle、Triangle、Square这三个类均继承自Shape类,并且都重写了父类的draw方法,代码如下所示:


class Shape{
	void draw(){
		System.out.println("Draw Shape");
	}
}

class Circle extends Shape{
	void draw(){
		System.out.println("Draw Circle");
	}
}

class Triangle extends Shape{
	void draw(){
		System.out.println("Draw Triangle");
	}
}

class Square extends Shape{
	void draw(){
		System.out.println("Draw Square");
	}
}

public class Test {
	
	 public static void main(String[] args) {
		 Shape[] shapes = {new Circle(), new Triangle(), new Square()};
		 for(Shape s : shapes){
			 s.draw();
		 }
	 }
}



输出结果为:

Draw Circle
Draw Triangle
Draw Square

我们创建了一个Shapes数组,里面分别是Circle、Triangle和Square的实例,我们在遍历该Shapes数组时其实已经对其进行了向上转型,即已经模糊了其实际类型,在遍历Shapes数组时,只知道每个元素都是一个Shape类型,然后依次调用元素的draw方法。结果没有调用基类Shape的draw方法,而是调用的对象实际子类型中的darw方法,这种现象就称之为多态。那此处的多态究竟是怎么发生的呢?前面说过只要类中的方法不是static和final的,那么该方法是后期绑定也就是在运行时才决定调用主体。在执行s.draw()这句代码时,Java知道了要调用darw方法了,s虽然表面看起来是Shape类型,但是它能判断出s实际上是一个Circle/Triangle/Square类型,这样就将方法的调用主体设置为更为具体的子类,这样就执行了具体子类的draw方法而非父类的draw方法。


我们在看如下一段Java代码:

class Shape{
	void draw(){
		System.out.println("Draw Shape");
	}
	
	void show(){
		draw();
	}
}

class Circle extends Shape{
	void draw(){
		System.out.println("Draw Circle");
	}
}

public class Test {	
	 public static void main(String[] args) {
		 Shape s = new Circle();
		 s.show();
	 }
}


执行结果为:Draw Circle

基类Shape中新增了一个show方法,在show方法中会调用draw方法,当执行代码Shape s = new Circle()时,我们创建了一个Circle类型的实例,并将其向上转型为Shape类型,然后调用基类的show方法,结果基类Shape的show方法调用了子类Circle的draw方法而非基类Shape的draw方法。出现这种情况的原因还是多态机制。当执行基类Shape中的show方法时,show方法内部要执行darw方法,darw方法是要执行的方法名,由于draw方法既不是static的,又不是final的,所以draw方法是后期绑定,也就是在运行时判断调用主体。Java知道s实际上是Circle类型的实例,所以会在基类Shape的show方法中会将子类Circle作为调用主体去调用子类Circle中的draw方法而非基类Shape的draw方法。


那么我们再对上面的例子进行一处修改,我们将基类Shape的draw方法设置为private私有的,代码如下:

class Shape{
	private void draw(){
		System.out.println("Draw Shape");
	}
	
	void show(){
		draw();
	}
}

class Circle extends Shape{
	void draw(){
		System.out.println("Draw Circle");
	}
}

public class Test {	
	 public static void main(String[] args) {
		 Shape s = new Circle();
		 s.show();
	 }
}

执行结果为:Draw Shape

我们再来分析一下原因。此处还是将子类Circle类型的实例向上转型为基类Shape,在执行基类Shape的show方法时,show方法内要执行draw方法,由于基类Shape中的draw方法被定义为private私有的,而private修饰的方法都实际是final方法(只不过是隐式地修饰为final),所以基类的draw方法是final的,由于final方法的调用都是前期绑定,也就是final方法的调用是在编译器决定的。所以此处不会发生后期绑定,从而自然执行了基类的draw方法而非子类Circle的draw方法。也可以这样认为,在我们编写完这个Java文件用IDE对其编译生成class文件时,由于private方法的前期绑定特性,编译器会将Shape中的draw方法的代码都copy到show方法内部。如下所示:

class Shape{
	private void draw(){
		System.out.println("Draw Shape");
	}
	
	void show(){
		//编译时将基类draw方法内的代码都copy到show方法中
		System.out.println("Draw Shape");
	}
}



可以这样理解,在生成的class文件中在show方法中就抹去了draw,只留下copy过来的基类中的draw代码。

此处代码没有执行多态还有一个原因是,多态机制的基础是子类对父类进行了函数覆盖。但是在上面的例子中父类Shape中的draw方法被修饰为private的,子类虽然也有一个draw方法,但是这不属于函数覆盖。因为父类中的draw方法为private的,对子类是完全屏蔽的,只有子类覆写了能够访问的父类中的方法时,才存在函数覆盖一说。所以压根就不存在子类覆盖父类中的private方法一说。既然Circle和Shape之间不存在函数覆盖,那么在在基类Shape的show方法执行draw时就不存在多态调用了。


还有一点需要说明的是只有普通的方法调用可以是多态的,字段不是多态的。字段的访问操作是前期绑定,由编译器解析,所以不是多态的。此处就不举例了。