面向对象编程有三大特性:封装、继承、多态。
多态指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态:又叫动态绑定。多态分离做什么和怎么做,也就是说多态将接口与实现(行为)分离开来。
1.向上转型:
向上转型:把对某个对象的引用视为对其基类类型的引用。又叫父类引用指向子类对象。
class Fruit{ void eat(){} } class Apple extends Fruit{ void eat(){} } class Test{ Fruit fruit = new Apple(); //------向上转型(父类引用指向子类对象) }
2.动态绑定:
动态绑定:指在运行时根据对象的类型进行绑定。java中除了static方法、final方法(private也属于final)外,其他方法都是动态绑定。也就是说方法在使用时才会找到正确的方法体。
class Fruit{ void eat(){ System.out.println("Fruit eat"); } } class Apple extends Fruit{ void eat(){ System.out.println("Apple eat"); } } public class TestClass{ public static void main(String[] args){ Fruit fruit = new Apple(); fruit.eat(); //------动态绑定 } }output: Apple eat
3.多态的实现
实现条件:继承、向上转型 、重写。
自己感觉:继承不一定、还可以有接口。重写也可以不用,但若不用的话则多态的意义(使得接口有许多不同的实现)就不大了。
实现形式:继承、接口。
基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。
class Fruit{ void eat(){ System.out.println("Fruit eat"); } } class Apple extends Fruit{ /* 重写父类方法,实现多态 */ void eat(){ System.out.println("Apple eat"); } } abstract class Car{ abstract void drive(); } class Volvo extends Car{ /* 实现父类(抽象类)方法,实现多态 */ void drive(){ System.out.println("Volvo drive"); } } public class TestClass{ public static void main(String[] args){ Fruit fruit = new Apple(); fruit.eat(); Car car = new Volvo(); car.drive(); } }output: Apple eat Volvo drive
在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
interface People{ String sayName(); } class Lilei implements People{ /* 实现接口方法,实现多态 */ public String sayName(){ return "My name is Lilei"; } } class CreatePeople{ /* 匿名内部类也是实现接口多态的一种 */ public People getPeople(){ return new People() { @Override public String sayName() { return "My name is Wangfang"; } }; } } public class TestClass{ public static void main(String[] args){ People people1 = new Lilei(); People people2 = new CreatePeople().getPeople(); System.out.println(people1.sayName()); System.out.println(people2.sayName()); } }output: My name is Lilei My name is Wangfang
4.经典实例
public class A { public String show(D obj) { return ("A and D"); } public String show(A obj) { return ("A and A"); } } public class B extends A{ public String show(B obj){ return ("B and B"); } public String show(A obj){ return ("B and A"); } } public class C extends B{ } public class D extends B{ } public class Test { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); D d = new D(); System.out.println("1--" + a1.show(b)); System.out.println("2--" + a1.show(c)); System.out.println("3--" + a1.show(d)); System.out.println("4--" + a2.show(b)); System.out.println("5--" + a2.show(c)); System.out.println("6--" + a2.show(d)); System.out.println("7--" + b.show(b)); System.out.println("8--" + b.show(c)); System.out.println("9--" + b.show(d)); } }output: 1--A and A 2--A and A 3--A and D 4--B and A 5--B and A 6--A and D 7--B and B 8--B and B 9--A and D
这个案例很有意思,当时在网上看到这个例子时看不太懂,就放在这里。直到后面看了看《深入理解Java虚拟机》(自己就大概看了2遍,里面也有许多不懂的东西)后,对Java的编译以及运行有了更多的了解,对重载、重写、动态分派、静态分派等等
有了了解后在可以从原理上来理解并解释上面的例子了。注:后面自己会写更加详细的博客关于虚拟机的,这里就先大概的讲一下关于这道题的解释。
静态分派:所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,静态分派在编译期完成,它解决了该类"会做什么"的问题,静态分派指的是引用的虚类型。
动态分派:在运行期根据实际类型确定方法执行版本的分派过程称为动态分派,今天分派在运行期完成,它解决了该类具体“怎么做”的问题,动态分派指的是引用的实际类型。
重载(Overload):指的是在同一个类中可以有多个相同的方法名称,但方法签名(参数类型、参数个数、参数顺序;注:返回值不是方法签名)不同的方法。方法重载是静态分派。
重写(Override):指的是在父子类之间,如果子类有与父类相同的方法名称以及方法签名,则表明子类重写了父类的该方法。方法重写是动态分派。
从上面我们可以得出:A类有2个show()方法,这两个方法重载。B类有3个show()方法,其中1个方法是从A类中重写的,1个方法是重载,还有1个方法继承自A类。所以A、B类实际如下(注:Java中没有@Overload这个注解,自己填上去的,其代表重载):
1 class A { 2 @Overload 3 String show(D obj) { 4 return "A and D"; 5 } 6 @Overload 7 String show(A obj) { 8 return "A and A"; 9 } 10 } 11 12 class B extends A { 13 @Overload 14 String show(B obj) { 15 return "B and B"; 16 } 17 @Override 18 String show(A obj) { 19 return "B and A"; 20 } 21 22 @Override 23 String show(D obj) { 24 return super.show(obj); 25 } 26 }
现在我们来看4(System.out.println("4--" + a2.show(b))):a2的虚类型是A类型,实际类型是B类型;b的虚类型以及实际类型都是B类型。在编译时(静态指派)该方法调用首先是A类型的2个show()方法,而A类中没有show(B obj)这个方法,所以需要向上转型后
调用的是show(A obj)这个方法。在运行时(动态指派),a2的实际类型是B类型,所以调用B类中的show(A obj)这个方法,然后输出:B and A。
其他方法调用解释如上。
5.reference
http://www.cnblogs.com/chenssy/p/3372798.html
http://blog.csdn.net/thinkGhoster/article/details/2307001
《深入理解Java虚拟机》