面向对象的第三大特性——多态 (多态的概念,向上转型和向下转型,重写,重写和重载的区别等等干货)

多态的概念

多态概念:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。
简单点来说就是:当完成某个行为时,不同的对象去完成时会产生不同的效果!
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)

多态的实现条件

1、继承:在多态中必须存在继承关系的子类和父类

2、重写:子类对父类中的某些方法进行重新定义,在调用这些方法时,就会调用子类的重写方法

3、向上转型:在多态中,需要用父类引用指向子类对象,只有这样才能够具备通过父类调用子类中重写父类的方法

以上就是多态的实现条件,现在看不懂没关系,下面将会针对这些条件一一讲解

向上转型和向下转型

1、向上转型

向上转型其实就是把数据类型小的引用转换成数据类型大的引用,进行数据类型之间的一个转换,从而能够访问到大的数据类型中的成员方法,但是不能访问到子类特有的方法。下面用代码解释:

//父类Animal
public class Animal {
    public String name;
    public int age;
    public void eat() {
        System.out.println(name+"吃饭");
    }
}
//子类Cat
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    //定义一个cat独有的新方法miMi
    public void miMi() {
        System.out.println("喵喵");
    }
}
//TestAnimal类
public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("小黑", 12);
       Animal animal = new Animal("animal",12);
       cat.miMi();//cat可以调用属于自己的miMi方法
       cat.eat();//cat能调用eat方法,虽然子类中没有,但是它继承了父类,所以调用的是父类中的eat方法
       animal.eat();//animal可以调用属于自己的eat方法
       animal.miMi();//但animal不能调用cat引用的对象里的miMi方法,因为animal引用的是父类对象,父类对象里没有miMi这个方法
    }

}

以上是没有经过向上转型时的代码 ,下面用代码演示三种向上转型的的三种方式;

1、直接赋值

2、方法传参

3、通过返回类型

1、直接赋值
将子类的对象直接赋值给父类的引用

public class TestAnimal {
    public static void main(String[] args) {
   //父类引用 - 引用了子类对象
        Animal animal2 = new Cat("小猫",11);                 
        animal2.eat();
        animal2.miMi();//错误,没办法引用      
        //经过向上转型后,变成了父类引用 引用了子类对象,但是animal2还不能访问Cat类中的miMi()方法,那向上转型有什么用呢,不要着急,等一下讲过重写就知道了就知道了
	}
}

2、方法传参

public class TestAnimal {
    //形参用Animal类型接受
    public static void eat(Animal animal) {
        animal.eat();
    }
    public static void main(String[] args) {
        //父类引用 - 引用了子类对象
        Cat cat = new Cat("小猫",12);
        //实参是Cat类型
        eat(cat);
    }
}

在main方法中,实例化了三个对象,然后调用了TestAnimal中的eat()方法,而传参时,传入的实参是对象的引用,而实参的类型是子类,形参用的是父类来接受的,这里就相当与进行了类型转化,因为,类也是一种引用数据类型嘛!所以,在这种场景下,发生了向上转型!

当然这里会出现一个疑问,不是参数类型不同不能进行传参么,对的,类型不同不能进行传参,但是,如果是父子类关系的话,就可以进行传参!

3、通过返回类型

public class TestAnimal {
    //返回类型设置成父类Animal
    public static Animal animalMethod() {
        return new Cat("小猫",11);
    }
    public static void main(String[] args) {
        //返回的类型是Animal,所以用Animal来接受
        Animal animal = animalMethod();
        animal.eat();
    }
}

向上转型的优点:让代码的实现更加灵活;

向上转型的缺点:不能调用到子类特有的方法,只能调用到发生重写的方法;

以上就是向上转型的三种方式,请读者朋友慢慢品会。

2、向下转型

将类型大的向类型小的进行转换(不安全)

//父类
public class Animal {
   public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println("吃饭");
    }
}
//子类
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    public void miMi() {
        System.out.println("喵喵");
    }
}
//子类
public class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }
}
//测试
public class TestAnimal {
    public static void main(String[] args) {
        //发生向上转型
        Animal animal = new Cat("小猫",11);;
        animal.miMi();//无法进行调用

        //向下转型
        Cat cat = (Cat)animal;
        cat.miMi();
        
         //对狗类进行向上转型
        Animal animal1 = new Dog("小狗",12);
        //对animal向下转型
        Cat cat1 = (Cat)animal1;//运行时会抛出错误
        cat1.miMi();
    }
}

在这里插入图片描述

第一种情况:

当进行Cat进行向上转型后,通过父类引用仍然是没办法访问miMi方法,因为在Animal类中根本就没有miMi方法,所以,可以将animal进行向下转型,变成Cat类型的,因为Cat类里面有miMi方法,所以可以进行调用;

第二种情况:

当对狗类进行向上转型后,有对animal进行了向下转型,转化成了Cat类,并没有报错,但是运行时抛出了异常,因为animal本来指向的是狗类,但是非要强转成猫类,这样驴头不对马嘴,当然是不行的,所以就会报出类转换异常ClassCastException;而第一种情况是,因为animal本来指向的就是猫类,向下转型转换成猫类也是没问题的;所以向下转型是不安全的。
在这里插入图片描述

如果要进行向下转型,需要利用instanceof 作出检查,如果表达式为真,则可以安全转化;下面代码演示:

public class TestAnimal {
    public static void main(String[] args) {
        //发生向上转型
        Animal animal = new Cat("小猫",11);;
        //向下转型
        Cat cat = (Cat)animal;
        cat.miMi();
        //对狗类进行向上转型
        Animal animal1 = new Dog("小狗",12);
        //假如if语句进行检查
        if(animal1 instanceof Cat) {
            Cat cat1 = (Cat)animal1;
            cat1.miMi();
        }else {
            System.out.println("转换异常");
        }
    }
}

在这里插入图片描述

重写

概念:重写也称为覆盖,在子类中,对父类中的方法进行重新编写,等于把父类中方法复制黏贴到子类里面。

现在注意力回到刚才向上转型那里,刚刚经过向上转型后,父类引用指向了子类对象,虽然能够调用eat方法,但是调用的仍然是父类中的eat方法,那么现在,在子类中也定义一个和父类一模一样的eat方法,经过向上转型之后会发生什么变化呢?

//父类Animal
public class Animal {
    public String name;
    public int age;
    public void eat() {
        System.out.println(name+"吃饭");
    }
}
//子类Cat
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    //定义一个cat独有的新方法miMi
    public void miMi() {
        System.out.println("喵喵");
    }
    //定义一个和父类中一模一样的eat方法
        @Override
    public void eat() {
        System.out.println(name+"吃猫粮");
    }
}
//TestAnimal类
public class TestAnimal {
    //进行向上转型
    public static void method(Animal animal) {
        animal.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("小猫",11);
        method(cat);
    }

}

在这里插入图片描述

在这里插入图片描述

运行结果显示,虽然是父类引用,引用了子类对象,但是在调用eat方法时,并没有调用父类中的eat方法,而是通过父类引用去调用了子类中的重写了父类的eat方法,所以得出结论,因为在子类中,对父类的eat方法进行了重写,所以在调用eat方法是,会调用子类的eat方法。在下面多态的实现中还会结合例子为大家讲解;但是,在这里,还要再引出两个新的名词动态绑定和静态绑定,刚刚在通过父类引用去调用子类中重写了父类的eat方法的这个过程就是动态绑定。

动态绑定(运行时绑定)概念:即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用了哪个类中的方法,方法的重写就是动态绑定;

下面通过汇编代码讲解为什么称为运行时绑定

在这里插入图片描述

静态绑定概念:在编译时,根据用户所传递的实参的类型及顺序确定了具体调用哪个方法。函数的重载就是静态绑定;

重写规则:

1、在子类中被重写的方法必须与父类中的方法外壳一模一样,但可以修改方法体中的内容,外壳即修饰符、返回值、方法名、参数列表

2、重写方法的返回值可以不同,但必须是父子类关系

3、注意:父类中,被private、final、修饰的方法不能被重写,静态方法和构造方法不能被重写

4、如果父类方法被public修饰,则在子类中重写该方法时,就不能被剩余的三个修饰符修饰,所以:在重写时,访问权限不能比父类中被重写的方法的访问权限更低。

5、重写方法可以用@Overrride注解显示描述,有了这个注解能进行一些合法的校验,例如把方法名字拼错了,此时编译器就会报错,提示无法构成重写。

6、构造器不能被重写,但可以被重载

面试问题:重写和重载的区别

1、方法的重载和重写都是实现多态的方式,区别在与前者实现的是编译时的多态性,而后者实现的是运行时的多态性

2、重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

3、重写:发生在父子类中,方法名、参数列表必须相同,返回在可以不同,但必须是父子类关系,在子类中,重写方法的访问修饰符要大于等于父类中的访问修饰符,如果父类方法访问修饰符是private,则子类中就不是重写

多态的实现

在代码中,写了一个父类Animal ,和子类Dog、子类Cat、子类Bird;它们分别都继承了父类Animal;所以继承方式是多个类继承同一个类;多说无益,下面将一步一步的讲解->

//父类Animal
public class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //成员方法
    public void eat() {
        System.out.println(name+"吃饭");
    }
    public void eat() {
        System.out.println(name+"吃饭");
    }
}
//子类cat  
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(name+"吃猫粮");
    }
}
//子类Dog
public class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(name+"吃狗粮");
    }
}
//子类Bird
public class Bird extends Animal{
    public Bird(String name, int age) {
        super(name, age);
    }
       @Override
    public void eat() {
        System.out.println(name+"吃鸟粮");
    }
}
//类TestAnimal用于测试
public class TestAnimal {
    public static void eat(Animal animal) {
        animal.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("小猫", 12);
        Dog dog = new Dog("小狗", 13);
        Bird bird = new Bird("小鸟", 14);
        //因为eat()方法是用static修饰的,所以不用通过引用来调用eat()方法
        eat(cat);//传入对象的引用
        eat(dog);
        eat(bird);
    }
}

在这里插入图片描述

当传入的对象的引用不同时,通过animal所调用的eat方法就会发生不一样的效果,从而就验证了:当要完成一个行为时,不同的对象去完成时,产生的效果也不同!

在这里插入图片描述

你品,你细品!!!

再来看这张图,实例化了三个子类对象,分别调用了三次eat方法,将三个不同的引用分别作为实参传给了型参,而形参的类型是父类Animal类型,所以发生了向上转型,所以当animal引用的是Cat对象时,调用的就是子类Cat里面的eat()方法,当animal引用的是Dog对象时,调用的就是Dog里面的eat()方法,当animal引用的是Bird对象时,调用的就是Bird里面的eat()方法;所以这样就完成了通过父类去调用子类中的重写方法;当然,这是其中一种理解方式;还有第二种理解方式:当在main方法中调用eat方法时,传入一个cat引用,因为animal的类型是Animal,所以进行了向上转型,又因为,父类中的eat和子类Cat中的eat方法发生了重写,所以子类Cat中的eat方法覆盖了父类中的eat方法,所以在调用时,调用了被覆盖的父类中的eat方法!!!

所以只要能在继承的条件下发生向上转型,就能发生动态绑定,能发生动态绑定,就可以实现多态!

以上就是关于多态的讲解,多态是一种思想,需要我们慢慢的去品汇,去理解,如果觉得本篇文章不错,还望留下一个小小小的👍哟

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值