多态:即不修改程序的代码就可以改变程序在运行时所绑定的具体代码,让程序可以选择多个运行状态,就是多态。


比如:
(Wine)酒 a = 剑南春(JNC)
(Wine)酒 b = 五粮液(WLY)
(Wine)酒 c = 酒鬼酒(JGJ)
这几种酒都是酒的子类,我们通过酒这一父类就能引用不同的子类,这就是多态;我们只有在运行的时候才会知道引用变量所指向的具体实例对象。(形象地说,我们只有喝下去才知道是什么酒)

上面的多态涉及到向上转型;

向上转型:

如果 JNC a = new JNC(); //实例化了一个剑南春的对象
那么 Wine a = new JNC();
那么我们理解为定义了一个Wine类型的a,让它指向JNC对象实例;
由于JNC是继承于Wine,所以JNC可以自动向上转型为Wine。这样使得我们可以通过a引用父类的共性外,还可以使用子类的功能。但是向上转型会导致子类的一些特有的方法和属性的丢失,a只能调用父类类型中所定义的所有属性和方法,对于子类特有的属性和方法只能望尘莫及。

  1. public class Wine {
  2. public void fun1(){
  3. System.out.println("Wine 的Fun.....");
  4. fun2();
  5. }
  6. public void fun2(){
  7. System.out.println("Wine 的Fun2...");
  8. }
  9. }
  10. public class JNC extends Wine{
  11. /**
  12. * @desc 子类重载父类方法
  13. * 父类中不存在该方法,向上转型后,父类是不能引用该方法的
  14. * @param a
  15. * @return void
  16. */
  17. public void fun1(String a){
  18. System.out.println("JNC 的 Fun1...");
  19. fun2();
  20. }
  21. /**
  22. * 子类重写父类方法
  23. * 指向子类的父类引用调用fun2时,必定是调用该方法
  24. */
  25. public void fun2(){
  26. System.out.println("JNC 的Fun2...");
  27. }
  28. }
  29. public class Test {
  30. public static void main(String[] args) {
  31. Wine a = new JNC();
  32. a.fun1();
  33. }
  34. }
  35. -------------------------------------------------
  36. Output:
  37. Wine Fun.....
  38. JNC Fun2...

分析:JNC重载了父类Wine的fun1()方法,重写了fun2()方法;而且重载后的fun1(String a)方法与父类中的fun1()方法并不是同一个方法,在向上转型后会被丢失,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。

多态总结总结:

指向子类的父类引用,它只能访问父类中拥有的方法;对于子类存在而父类不存在的方法,即使是重载方法也不能使用。但是当子类重写了父类中的某些方法后,在调用该些方法时,必定是使用子类中定义的这些方法。

ps:对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

Java实现多态的三个必要条件:

继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

实现形式:继承和接口

基于继承

  1. public class Wine {
  2. private String name;
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public Wine(){
  10. }
  11. public String drink(){
  12. return "喝的是 " + getName();
  13. }
  14. /**
  15. * 重写toString()
  16. */
  17. public String toString(){
  18. return null;
  19. }
  20. }
  21. public class JNC extends Wine{
  22. public JNC(){
  23. setName("JNC");
  24. }
  25. /**
  26. * 重写父类方法,实现多态
  27. */
  28. public String drink(){
  29. return "喝的是 " + getName();
  30. }
  31. /**
  32. * 重写toString()
  33. */
  34. public String toString(){
  35. return "Wine : " + getName();
  36. }
  37. }
  38. public class JGJ extends Wine{
  39. public JGJ(){
  40. setName("JGJ");
  41. }
  42. /**
  43. * 重写父类方法,实现多态
  44. */
  45. public String drink(){
  46. return "喝的是 " + getName();
  47. }
  48. /**
  49. * 重写toString()
  50. */
  51. public String toString(){
  52. return "Wine : " + getName();
  53. }
  54. }
  55. public class Test {
  56. public static void main(String[] args) {
  57. //定义父类数组
  58. Wine[] wines = new Wine[2];
  59. //定义两个子类
  60. JNC jnc = new JNC();
  61. JGJ jgj = new JGJ();
  62. //父类引用子类对象
  63. wines[0] = jnc;
  64. wines[1] = jgj;
  65. for(int i = 0 ; i < 2 ; i++){
  66. System.out.println(wines[i].toString() + "--" + wines[i].drink());
  67. }
  68. System.out.println("-------------------------------");
  69. }
  70. }
  71. OUTPUT:
  72. Wine : JNC--喝的是 JNC
  73. Wine : JGJ--喝的是 JGJ
  74. -------------------------------

在上面的代码中,由于JNC、JGJ都重写了父类的drink()、toString()方法,所以当指向子类实例的父类类型的引用(就是变量)调用这两个方法时,使用的都是子类中的方法。这就是多态的表现。

由于所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写:

  1. Object o = new JGJ();
  2. System.out.println(o.toString());

输出的结果是:Wine:JGJ;

对于抽象类来说,如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

基于接口在接口的多态中,接口的引用指向的必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

经典实例

  1. public class A {
  2. public String show(D obj) {
  3. return ("A and D");
  4. }
  5. public String show(A obj) {
  6. return ("A and A");
  7. }
  8. }
  9. public class B extends A{
  10. public String show(B obj){
  11. return ("B and B");
  12. }
  13. public String show(A obj){
  14. return ("B and A");
  15. }
  16. }
  17. public class C extends B{
  18. }
  19. public class D extends B{
  20. }
  21. public class Test {
  22. public static void main(String[] args) {
  23. A a1 = new A();
  24. A a2 = new B();
  25. B b = new B();
  26. C c = new C();
  27. D d = new D();
  28. System.out.println("1--" + a1.show(b));
  29. System.out.println("2--" + a1.show(c));
  30. System.out.println("3--" + a1.show(d));
  31. System.out.println("4--" + a2.show(b));
  32. System.out.println("5--" + a2.show(c));
  33. System.out.println("6--" + a2.show(d));
  34. System.out.println("7--" + b.show(b));
  35. System.out.println("8--" + b.show(c));
  36. System.out.println("9--" + b.show(d));
  37. }
  38. }


运行结果**

  1. 1--A and A
  2. 2--A and A
  3. 3--A and D
  4. 4--B and A
  5. 5--B and A
  6. 6--A and D
  7. 7--B and B
  8. 8--B and B
  9. 9--A and D

在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

首先我们分析5,a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。

总结:
所以多态机制遵循的原则概括为:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

参考链接:https://www.cnblogs.com/chenssy/p/3372798.html

补充:调用哪个方法动态决定(运行时决定),得看new是什么;参数静态决定(编译时),得看它是什么类型的参数。