wangjie-fourth 的个人博客

may be just need work hard

目录
java中得多态
/        

java中得多态

一、对多态的理解

多态是指一个对象具有多种形态的现象。这里从代码的角度来看:

class A {

}

class B extends A {

}

A a = new B();

此时,a具有俩种形态:AB。那在执行代码的时候,究竟是执行A,还是执行B里面的代码呢?这种不确定性,称为多态。

这里B泛指A的所有子类

从上面可以看出,**多态本身是由于继承机制而导致的现象。**那么子类继承父类的实例属性、静态属性、实例方法、静态方法,有哪些会发生多态现象呢?

多态仅仅发生在实例方法的选择上。至于实例属性、静态属性、静态方法都是不具有多态的。

  • 仅仅选择引用类型中代码称为不具有多态性;
  • 会选择真实对象类型中的代码称为具有多态性;

也就是说,在代码运行时:

  • 实例方法会根据真实对象this来决定执行什么方法
  • 静态方法、实例属性、静态属性是根据引用对象是什么就用什么

注意:方法参数也不具有多态性,它是静态绑定的。但方法重载与多态一关联起来,就会稍微复杂一点了。

二、验证:实例属性、静态属性、静态方法不具有多态性

由于方法参数是不具有多态性的,这里不考虑方法参数有继承的情况。

1、实例属性、静态属性不具有多态性

public class Main {
    static class A{
        public String filed = "A";
        public static String staticField = "StaticA";
    }

    static class B extends A{
        public String filed;
        public static String staticField = "StaticB";
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.filed);// A
        System.out.println(a.staticField);// StaticA
    }
}

2、静态方法不具有多态性

public class Main {
    static class A{
        public static void method(){
            System.out.println("A执行了");
        }
    }

    static class B extends A{
        public static void method(){
            System.out.println("B执行了");
        }
    }

    public static void main(String[] args) {
         A a = new B();
         a.method();// A执行了
    }
}

三、验证实例方法具有多态性

首先,实例方法是具有多态性的,验证方法也很简单。

public class Main {
    static class A{
        public void method(){
            System.out.println("A执行了");
        }
    }

    static class B extends A{
        public void method(){
            System.out.println("B执行了");
        }
    }
    
    public static void main(String[] args) {
         A a = new B();
         a.method();// B执行了
    }
}

但如果考虑方法参数存在继承关系、方法重载的话。问题就会稍微复杂一点。

1、方法参数不具有多态性

public class Main {

    static class A{
        public void method(SuperParam superParam){
            System.out.println("A执行了父类参数");
        }

        public void method(SubParam subParam){
            System.out.println("A执行了子类参数");
        }
    }

    static class B extends A{
        public void method(SuperParam superParam){
            System.out.println("B执行了父类参数");
        }

        public void method(SubParam subParam){
            System.out.println("B执行了子类参数");
        }
    }

    public static void main(String[] args) {
        A a = new B();
        SuperParam superParam = new SuperParam();
        SuperParam subParam = new SubParam();

        a.method(superParam);//B执行了父类参数
        a.method(subParam);//B执行了父类参数
    }
}

可以看到方法参数是静态绑定的,它不会去看自己真实类型是什么的。

上面仅仅是考虑实例方法的多态问题,还有加上方法重载,如果再加上方法重载会发生什么呢?

3、实例方法 + 方法的重载

public class Main {

    static class A{
        public void method(SuperParam superParam){
            System.out.println("A执行了父类参数");
        }
    }

    static class B extends A{
        public void method(SubParam subParam){
            System.out.println("B执行了子类参数");
        }
    }

    public static void main(String[] args) {
        A a = new B();
        SuperParam superParam = new SuperParam();
        SubParam subParam = new SubParam();

        a.method(superParam);//A执行了父类参数
        a.method(subParam);//A执行了父类参数
    }
}

这结果是不是有点意思?它执行了父类的方法???不不不,它其实执行的是B的方法,即BA继承过来的方法。

即使这样是不是很难理解a.method(subParam);为什么也会输出A执行了父类参数。这里其实就是多态 + 方法的重载而导致的。

这个要从俩个时间点来理解:代码未编译前、代码运行时。

(1)代码未编译前

我们知道代码未编译前,是根据其引用类型来判断这个代码合不合理。也就是说a.method(superParam);在代码未编译前没有报错,是因为在A中找到public void method(SuperParam superParam)方法。

但是a.method(superParam);的参数不是说不具有多态性吗,那它岂不是要在A中找public void method(SubParam subParam)方法吗?怎么在这就可以呢?这是因为方法的重载呀!!!在没有直接能匹配类型参数的方法话,它就会去找其父类参数类型。

也就是说在代码未编译前,编辑器认为这种写法没有问题。

(2)代码运行时

代码运行的时候,由于这是个实例方法,所以肯定会去B中找方法来执行的。但巧了,B自己实现了正好能符合参数类型的方法。此时B中方法有俩个:

  • public void method(SuperParam superParam):从父类继承的

  • public void method(SubParam subParam):自己实现的

而执行的代码又恰好符合自己实现的那个方法,代码却执行了从父类继承的方法。这也是多态奇妙的地方,它一定是根据父类方法来决定执行子类的某个方法的。

四、一个面试题

上面说了那么多,其实下面一个面试题就解决了。看懂了下面试题,也就理解上面说的多态问题。

public class MultiTest {

    static class A {
        public String show(D obj) {
            return ("A and D");
        }

        public String show(A obj) {
            return ("A and A");
        }
    }

    static class B extends A {
        public String show(A obj) {
            return ("B and A");
        }

        public String show(B obj) {
            return ("B and B");
        }
    }

    static class C extends B {
    }

    class D extends B {
    }
    public static void main(String[] args) {
        A ab = new B();
        B b = new B();
        C c = new C();
        System.out.println(ab.show(b));
        System.out.println(ab.show(c));
    }
}

分析一下:ab.show(b)

代码在未编译之前,是根据其引用类型来判断这个方法存不存在。那么A存在这个方法重载版本,由于参数能够向上,即String show(B obj)符合条件,所以在编译前是没有疑问得。

代码在运行之后,由于这个是实例方法,它会根据真实类型去找。但是要注意得是,编译前是根据String show(B obj)这个方法来的,所以JVM会去B中找这个方法。其实,B中有俩个版本这个方法,一是从A继承过来得方法,二是B自己重写这个得方法。自然重写方法优先级高,所以它就直接选择B得版本。

所以:方法得执行结果时:B and A

再分析一下:ab.show(c),其实道理跟上面原理一致

首先代码未编译之前,根据其引用类型和方法重载得存在,确定了String show(A obj)

代码在运行之后,跟上面原因一致,所以执行结果是:B and A

最后,小总结一下。

多态是由于继承中实例方法而出现的现象。而方法又有重载的特性,这俩个加到一起就会变得稍微复杂了一点。在这里,我们其实只要掌握:

  • 哪些东西是不具有多态性质得;
  • 只有实例方法才可能发生多态;
  • 实例方法发生多态得时候,一定要根据父类方法,来确定其执行子类得哪些方法;

五、相关问题

1、子类是否可以继承父类的私有变量?

首先:子类可以继承其父级的所有公共成员和受保护成员,无论该子类位于哪个程序包中。如果该子类与其父级位于同一程序包中,则它还将继承父级的程序包级私有成员。

按理说子类是不可以继承父类得私有变量得。但是也有另一种说法:因为可以通过反射得技术获取到父类得私有变量,所以子类可以继承父类得私有变量。

评论