多态

如果子类重写了父类的方法,则调用的子类的方法;如果子类没有重写父类的方法,则调用的是父类的方法。这就是多态。

简单的讲,当调用子父类同名参数的方法时,实际执行的是子类重写的方法,这也被称为虚拟方法调用

直接看例子:

class Person {
    String name;
    int age;

    public void eat() {
        System.out.println("吃东西");
    }

    public void say() {
        System.out.println("说话");
    }
}

class Man extends Person {

    Boolean isHandsome;

    @Override
    public void eat() {
        System.out.println("男人吃的多");
    }

    public void earnMoney() {
        System.out.println("男人要赚钱");
    }
}

class Woman extends Person {

    Boolean isBeauty;

    @Override
    public void eat() {
        System.out.println("女人吃的少");
    }

    public void goShopping() {
        System.out.println("女人喜欢购物");
    }
}


public class Test {
    public static void main(String[] args) {
        Person p1 = new Person();

        Man man = new Man();
        man.eat();       // 男人吃的多
        man.age = 20;
        man.earnMoney();  //男人要赚钱

        //对象的多态性,父类的引用指向子类的对象
        Person p2 = new Man();
        Person p3 = new Woman();
        
        /*****多态*****/
        p2.eat();  // 男人吃的多
        p2.say();  // 说话
    }
}

注意这里的57、58行,可以明显的看到,p2的类型是父类Person,而赋值内容却是Man子类,而且运行eat()方法时运行的是子类的Man的eat()方法,并不是父类Person的eat()方法。

触发多态的条件:

  • 发生类的继承
  • 方法的重写
  • 父类的引用指向子类的对象(向上转型)

Person p2 = new Man(); 在这个语句中,父类的引用指向的是子类的对象,因此这里发生了对象的多态。

而且可以看到p2.eat(); 执行的是子类的重写的方法。

多态中当调用子父类同名参数的方法时,实际执行的是子类重写的方法,那么父类能不能调用子类特有的方法呢?

答案是不能的,当父类执行 p2.earnMoney(); 是会报错的。

当发生多态时:编译看左边,运行看右边

编译看左边:这个的意思是在Person p2 = new Man();等号左边中,p2对象所能调用的方法只能是p2的类型Person中的方法,而不能调用它引用的Man对象的方法。这就是p2.earnMoney();为啥会报错的原因。

运行看右边:这个的意思是p2调用方法时,是调用Person p2 = new Man();等号右边Man对象中重写的方法,如果Man没有重写Person中的方法,那么p2调用的是Person自身的方法。 因此p2.eat();运行的是子类Man的eat()方法,因为在子类中重写了父类的eat()方法;而p2.say(); 调用的是父类中的方法,因为子类没有重写,子类中也没有这方法,所以p2调用不到子类的,所以会调用自己的。

总之,类型是在编译阶段确定的,而方法的调用是在运行时确定的

运行看右边只适用于有方法的重写,而不会触及变量

class Person {
    String name = "Person";
    void say() {
        System.out.println(this.name);
    }
}

class Man extends Person {
    String name = "Man";
    void say() {
        System.out.println(this.name);
    }
}


public class Test {
    public static void main(String[] args) {
        Person p = new Man();
        System.out.println(p.name); // Person
        p.say();                    // Man
    }
}

上面可以看出,p.name调用的是父类的name而不是子类的。 但是在p.say()方法中虽然运行的是运行的是子类的say()方法,但是这个方法里面的this是子类对象,而不是父类对象,所以20行输出的是子类的name。

多态的应用:

class Animal {
    public void eat () {
        System.out.println("动物,进食");
    }
    public void shout() {
        System.out.println("动物,叫");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    @Override
    public void shout() {
        System.out.println("狗,汪汪汪");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃鱼");
    }

    @Override
    public void shout() {
        System.out.println("猫,喵喵喵");
    }
}




public class Test {

    public static void main(String[] args) {
        Test test = new Test();

        // 有多态性
        test.func(new Dog()); // Animal animal = new Dog();
        test.func(new Cat()); // Animal animal = new Cat();

        // 没多态性
        test.funcDog(new Dog());
        test.funcCat(new Cat());

    }

    public void func(Animal animal) {  // 声明的是Animal 而使用的是子类
        // 这里只能调用Animal中的方法,可是运行中是运行的子类重写的方法
        animal.eat();                  
        animal.shout();
    }

    public void funcDog(Dog dog) {
        dog.eat();
        dog.shout();
    }

    public void funcCat(Cat cat) {
        cat.eat();
        cat.shout();
    }
}

在这个案例中,func可以传入Animal所以的子类对象,这里就相当于Animal animal = new 子类(); 也是一个多态的发生。 如果没得多态,那么只能写成funcDogfuncCat方法传入指定的类型,所以如果有一百个类就要写一百个方法,这样就非常鸡肋。

向下转型

类型转换

其实多态的发生就是向上转型的发生,那么向下转型是怎么发生呢?

就上面的例子中,p2只能调用Person中有的方法,而运行的时候是调用的子类重写的方法。 这里就有个问题,就是p2不能调用子类特有的方法,如上面的earnMoney方法,在Person p2 = new Man();向上转型中p2就调用不了earnMoney方法,可是要怎么才能调用呢?

  1. 定义类型肯定是要子类类型
  2. 要进行一个强制类型转化
Man m1 = (Man)p2;
m1.earnMoney();

这时才可以调用子类的earnMoney方法,其实很容易理解,m1的类型就是Man,而且引用的对象也被强制转化为Man,所以能调用earnMoney方法。

但是强制类型转化,就有可能出现转换问题,如:

Woman w1 = (Woman)p2;
w1.goShopping();

这里就会发生类中错误,p2引用的时Man对象,而Man对象不能转化为Woman。

instanceof

为了避免在向下转型之前出现类型异常,一般要进行类型判断

a instanceof A : 判断对象a是否是A的实例。 如果是返回true,不是返回false

所以一旦返回true,就向下转型,否则不进行向下转型

if(p2 instanceof Woman){
    Woman w1 = (Woman)p2;
    w1.goShopping();
    System.out.println("p2 是一个Woman实例");
}

if(p2 instanceof Man){
    Man m2 = (Man)p2;
    m1.earnMoney();
    System.out.println("p2 是一个Man实例");
}

这样就避免了类型错误的出现

补充:

如果A是B的父类
那么 a instanceof B 返回true, 则a instanceof A 也返回true

如:

if (p2 instanceof Person) {
    System.out.println("Person");
}

if (p2 instanceof Object) {
    System.out.println("Object");
}

上面例子中会打印Person,Object

常见的问题

问题一: 编译时通过,运行时报错

举例一:

Person p4 = new Woman();
Man m3 = (Man) p4;

举例二:

Person p4 = new Person();
Man m4 = (Man) p4;

问题一: 编译时通过,运行时也通过

Object obj = new Woman();
Person p = (Person) obj;

问题三: 编译不过

Man m5 = new Woman();  // 不是父子类关系
最后修改:2021 年 08 月 25 日 11 : 55 AM
如果觉得我的文章对你有用,请随意赞赏