多态的概念

多态在Java中是一个很重要的概念,其成立的条件有三个:

  1. 子类继承父类;
  2. 子类重写父类的方法;
  3. 父类引用指向子类对象。

       前两条大家都清楚,第三条父类引用指向子类对象我多说一句。Java中数据分为基本类型和引用类型,引用类型有引用和对象,存放在栈中的为引用或者有时候叫句柄,存放在堆中的我们称为对象。


下面我们用例子来说明Java中多态的概念:

我们定义两个类,Person 和 Student 类,让Student 继承Person类

public class Person {
            int var = 100;

            public void a() {
                System.out.println("class Person , method a()");
            }
        }
public class Student extends Person {
            int var = 200;

            /*
             * public void a() {
             * System.out.println("class Student , method a()"); }
             */

            public void b() {
                System.out.println("class Student , method b()");
            }
        }

这里我们先不重写父类的 a() 方法,接下来我们让父类(Person)的引用指向子类(Student)的对象

public class Test {
            public static void main(String[] args) {
                Person p = new Student(); // 相当于基本类型的隐式转换
                p.a();
            }
        }

 

在这里我们用 p 调用 a() 方法时,执行的显然是父类的 a() 方法,因为子类没有 a() 方法,输出

class Person , method a()

如果我们将Student类中 a() 方法的注释去掉(第5行至第8行),在 p 调用 a() 方法的时候执行的为子类重写之后的 a() 方法,输出

class Student , method a()

但是,如果这里我们在 main() 方法中调用 p.b() 方法,答案是编译器会报错。那为什么 p 可以调用子类重写的 a() 方法,却不能调用子类的 b() 方法呢?我们可以用天一时代老师的一个形象的例子来说明这个问题。


       我们开头说引用类型分为引用对象两个部分,可以把引用比喻成电视的遥控器,把对象就比作成电视节目。那么 Person p = new Student(); 这句话是不是就相当于用一个父类的或者说是先前的,旧的遥控器来控制一台子类的或者说是新的电视啊。对,就是这样。这么一来,如果说原来的旧电视只有一个 a() 节目的话,我们旧的遥控器就只需要一个按钮就可以控制这台旧电视了,如果说在新电视中不去更新 a() 这个旧的节目,即不重写 a() 方法,那么显然遥控器一打开旧的电视看到的就是旧的 a() 节目,如果说新电视更新了旧的 a() 节目那么自然父类的 a() 节目就被取代了。这时,如果这里我们在新的电视中增加了一个节目 b() 节目,我们想看 b() 节目,能不能看呢,当然不能,因为我们用的是旧的遥控器啊,它只有一个按钮,换不了台啊!

 

       那现在如果我们一定要看新增加的节目怎么办呢?答案就是我们需要换新的遥控器,即 Student s = (Student)p; 这样我们把旧遥控器换成了新遥控器,当然可以看新增加的节目了。

public class Test {
            public static void main(String[] args) {
                Person p = new Student(); // 相当于基本类型的隐式转换
                // p.a();
                Student s = (Student) p; // 向下转型(强制转换)
                s.b();
            }
        }

现在,我们增加一个类

public class TestBinding {
            public void test(Person p) {
                p.a();
            }
        }

这样我们在 main() 方法中这样调用时,能否知道执行的 a() 方法是父类的方法还是子类的方法?

public class Test {
            public static void main(String[] args) {
                TestBinding t = new TestBinding();
                t.test(p);
            }
        }

答案是不知道,因为我们不知道传进去的是父类的对象还是子类的对象(不知道是新电视还是旧电视)。这里,如果我们传进去父类对象

public static void main(String[] args) {
        TestBinding t = new TestBinding();
        t.test(new Person());
    }

       执行的就是父类的 a() 方法,因为是旧电视,如果传进去的是子类对象,则执行子类的 a() 方法。就是说我们要用哪个方法不是在编译期的时候决定的,而是在运行期间决定的,这种运行期绑定就叫动态绑定,或者叫后期绑定,或者叫多态


最后,我们来简单说一下另一种绑定------前期绑定。我们在TestBinding类中增加一行代码,来打印类中的属性

public class TestBinding {
        public void test(Person p) {
            p.a();
            System.out.println(p.var); // 新增
        }
    }

       那么在main() 方法中传入Person对象和Student对象的时候打印出来的属性值会是多少呢?试过之后我们知道打印的都是 100, 即父类的属性值。原因是Java中属性和静态方法都是在编译期绑定的,即前期绑定。至于静态方法的情况,读者可以自己验证一下。

原文地址:https://www.cnblogs.com/yuxiaoqi/p/2754185.html