什么是多态?

多态

向上转型

向上转型:子类对象被赋值给父类引用。
在这里插入图片描述

向上转型的三个时机:

  • 直接赋值的方式
  • 以传参的方式
  • 已返回值的方式

以下通过代码来展示:

class Animal {
    protected String name;
    public Animal(String name){
        this.name = name;
        System.out.println("Animal(String)");
    }
    public void eat(){
        System.out.println(this.name+" Animal::eat()");
    }
}
class Cat extends Animal{
    public int count = 99;
    public Cat(String name){
        super(name);
        System.out.println("Cat(String)");
    }
}
class Bird extends Animal{
    public Bird(String name){
        super(name);
    }
    public void fly() {
        System.out.println(this.name + "Bird::fly()");
    }
}
public class TestMain {
    public static Animal func(){
        Cat cat = new Cat("咪咪");
        return cat;
    }

    public static void main(String[] args) {
        Animal animal = func();//这是发生向上转型时机当中的第三种:以返回值的形式
        animal.eat();
    }
    public static void func(Animal animal){
        animal.eat();
    }
    public static void main3(String[] args) {
        Cat cat = new Cat("咪咪");
        func(cat);//这是发生向上转型时机当中的第二种:以传参的方式
    }
    public static void main2(String[] args) {
       Animal animal =  new Cat("咪咪");//这就是向上转型,一般称为子类对象被赋值给父类引用,这种向上转型的时机就是直接赋值的方式
        animal.eat();
        //animal.count;error 向上转型之后,通过父类的引用只能访问父类自己的方法或者属性。

    }
    public static void main1(String[] args) {//这种引用是很正常的
        Animal animal = new Animal("豆豆");
        Cat cat = new Cat("咪咪");
        cat.eat();
    }

重载:overload

重载指的是方法名相同,参数列表不同(参数列表指的是参数的个数和类型),返回值不作要求,并且在同一个类当中,满足这些条件就构成重载。

重写:override

重写指的是方法名相同,返回值相同,参数列表相同,并且在满足继承关系的不同的类中,满足这些条件就构成重写。

关于重写要注意的事项:

  1. 需要重写的方法不能是被final修饰的,被final修饰之后,它是密封方法,不可以修改。
  2. 被重写的方法,访问修饰限定符一定不能是私有的。
  3. 被重写的方法,子类当中的访问修饰限定符要大于等于父类的访问修饰限定符。(private<default<protected<public )
  4. 被static 修饰的方法是不可以被重写的。

运行时绑定

运行时绑定:父类引用 引用子类对象,同时通过父类引用调用同名的覆盖(重写)方法,此时就会发生运行时绑定,这个过程是程序运行时决定的(而不是编译期),因此称为动态绑定,也称为运行时绑定。

class Animal {
    protected String name;
    public Animal(String name){
        this.name = name;
        System.out.println("Animal(String)");
    }
    public void eat(){
        System.out.println(this.name+" Animal::eat()");
    }
}
class Cat extends Animal{
    public int count = 99;
    public Cat(String name){
        super(name);
        System.out.println("Cat(String)");
    }
    public void eat(){
        System.out.println(this.name+"的示范法Cat::eat()");
    }
}
class Bird extends Animal{
    public Bird(String name){
        super(name);
    }
    public void fly() {
        System.out.println(this.name + "Bird::fly()");
    }
}
public class TestMain {
    public static void main(String[] args) {
      Animal animal =  new Cat("咪咪");
      animal.eat();//这里发生了运行时绑定,也叫动态绑定
    }
}

向下转型

代码演示:

class Animal {
    protected String name;
    public Animal(String name){
        this.name = name;
        System.out.println("Animal(String)");
    }
    public void eat(){
        System.out.println(this.name+" Animal::eat()");
    }
}
class Cat extends Animal{
    public int count = 99;
    public Cat(String name){
        super(name);
        System.out.println("Cat(String)");
    }
    public void eat(){
        System.out.println(this.name+"的示范法Cat::eat()");
    }
}
class Bird extends Animal{
    public Bird(String name){
        super(name);
    }
    public void fly() {
        System.out.println(this.name + "Bird::fly()");
    }
}
public class TestMain {
    //演示向下转型,向下转型非常不安全,现在很少会使用向下转型
    public static void main(String[] args) {
        Animal animal = new Bird("八哥");
        Bird bird = (Bird)animal;//这就是向下转型
        bird.fly();
    }
}

向下转型非常不安全,现在很少会使用向下转型,演示不安全的向下转型:

class Animal {
    protected String name;
    public Animal(String name){
        this.name = name;
        System.out.println("Animal(String)");
    }
    public void eat(){
        System.out.println(this.name+" Animal::eat()");
    }
}
class Cat extends Animal{
    public int count = 99;
    public Cat(String name){
        super(name);
        System.out.println("Cat(String)");
    }
    public void eat(){
        System.out.println(this.name+"的示范法Cat::eat()");
    }
}
class Bird extends Animal{
    public Bird(String name){
        super(name);
    }
    public void fly() {
        System.out.println(this.name + "Bird::fly()");
    }
}
public class TestMain {
    public static void main(String[] args) {//演示不安全的向下转型,这种是编译的时候不报错,运行的时候报错
        Animal animal = new Cat("八个");
        Bird bird = (Bird)animal;
        bird.fly();
    }
}

这种是编译的时候不报错,运行的时候报错,可以这样解决:

class Animal {
    protected String name;
    public Animal(String name){
        this.name = name;
        System.out.println("Animal(String)");
    }
    public void eat(){
        System.out.println(this.name+" Animal::eat()");
    }
}
class Cat extends Animal{
    public int count = 99;
    public Cat(String name){
        super(name);
        System.out.println("Cat(String)");
    }
    public void eat(){
        System.out.println(this.name+"的示范法Cat::eat()");
    }
}
class Bird extends Animal{
    public Bird(String name){
        super(name);
    }
    public void fly() {
        System.out.println(this.name + "Bird::fly()");
    }
}
public class TestMain {
    public static void main(String[] args) {//演示不安全的向下转型,这种是编译的时候不报错,运行的时候报错
        Animal animal = new Cat("八个");
        if (animal instanceof Bird){//A instanceof B 判断A是不是B的一个实例
            Bird bird = (Bird)animal;
            bird.fly();
        } else {
            System.out.println("haha");
        }
    }
}

在构造方法中调用重写的方法(一个坑)

简单说就是这种情况也会发生运行时绑定,在构造方法当中是可以发生动态绑定的

class Animal {
    protected String name;
    public Animal(String name){
        this.name = name;
        eat();//它执行的eat方法不是animal的,而是子类的eat方法
    }
    public void eat(){
        System.out.println(this.name+" Animal::eat()");
    }
}
class Cat extends Animal{
    public int count = 99;
    public Cat(String name){
        super(name);
        System.out.println("Cat(String)");
    }
    public void eat(){
        System.out.println(this.name+"的示范法Cat::eat()");
    }
}
public class TestMain {
    public static void main(String[] args) {
        Cat cat = new Cat("八个");
    }
}

输出:
在这里插入图片描述

理解多态

有了向上转型,运行时绑定,方法重写之后,我们就可以使用多态的形式来设计程序了。

代码示例:打印多种形状

class Shape{
    public void draw(){

    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个○");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个♦");
    }
}
class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个△");
    }
}
/**************我是分割线***************/
public class TestDemo {
    public static void drawMap(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {
        Shape shape1 = new Cycle();
        Shape shape2 = new Rect();//构成重写,向上转型发生了运行时绑定。
        Shape shape3 = new Triangle();
       drawMap(shape1);
       drawMap(shape2);
       drawMap(shape3);
    }
}

代码输出为:
在这里插入图片描述
在这个代码中,分割线上方的代码是类的实现者编写的,分割线下方的代码是类的调用者编写的。
当类的调用者在编写drawMap这个方法的时候,参数类型为Shape(父类),此时在该方法内部并不知道,也不关注当前的shape引用指向的是那个类型(哪个子类)的实例。此时shape这个引用调用draw方法可能会有多种不同的表现(和shape对应的实例相关),这种行为就称为多态。

什么是多态?

可以这么回答:

  1. 父类引用 引用子类对象
  2. 父类和子类有同名的覆盖方法
  3. 通过父类引用调用这个重写的方法的时候。多数的话就可以称为多态,单数可以说运行时绑定。

使用多态有什么好处?

  1. 类调用者对类的使用成本进一步降低
    封装是让类的调用者不需要知道类的实现细节,多态能让类的调用者连这个类的类型是什么都不必知道,只需要知道这个对象具有某个方法即可。因此,多态可以理解成是封装的更进一步,让类调用者对类的使用成本进一步降低。
  2. 能够降低代码的“圈复杂度”,避免使用大量的if-else
    例如:如果不使用多态的话,实现代码如下:
class Shape{
    public void draw(){

    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个○");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个♦");
    }
}
class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个△");
    }
}
public class TestDemo {
    public static void main(String[] args) {
            Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Triangle triangle = new Triangle();
        String[] shapes = {"cycle","rect","triangle"};
        for (String shape: shapes) {
            if (shape.equals("cycle")){
                cycle.draw();
            }else if (shape.equals("rect")){
                rect.draw();
            }else if (shape.equals("triangle")){
                triangle.draw();
            }
        }
    }
}

输出为:
在这里插入图片描述
如果使用多态,则不必写这么多的if-else分支语句,代码更简单

class Shape{
    public void draw(){

    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个○");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个♦");
    }
}
class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个△");
    }
}
/**************我是分割线***************/
public class TestDemo{
    public static void main(String[] args) {
       Shape[] shapes = {new Cycle(),new Rect(),new Triangle()};
        for (Shape shape:shapes) {
            shape.draw();
        }
    }
}

输出为:
在这里插入图片描述
什么叫“圈复杂度”?
圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,那么就比较简单容易理解,而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就叫“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构,不同公司对于代码的圈复杂度的规范不一样,一般不会超过10。
3. 可扩展能力更强
如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。

class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个△");
    }
}

对于一个类的调用者来说,只要创建一个新类的实例就可以了,改动成本很低、
而对于不用多态的情况,就要把方法中的if-else进行一定的修改,改动成本更高。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值