本文已参加「新人创作礼」活动,一同敞开创作之路。

1. 回顾承继

那么在上一篇博客Java承继中,咱们大致剖析了承继的概念如何用父类和子类之间的联系来完结,以及各种应用场景,咱们也另外剖析了super,protected以及final在其中所起到的作用,而且介绍了组合的概念

一篇带你搞定Javaの多态

那么接着上一篇的承继,咱们在这一篇博客中介绍多态。

2. 多态

2.1 多态的概念

多态的概念:通俗来说,便是多种形态,那么在Java中,便是去完结某个行为,当不同的目标去完结时会产生不同的状态和表现
举两个简略的比如

一篇带你搞定Javaの多态

一篇带你搞定Javaの多态

总的来说:同一件事,发生在不同目标身上,就会产生不同的成果

2.2 多态完结条件

在Java中要完结多态,那么有必要要满足以下几个条件,缺一不可:

  1. 有必要在承继体系下
  2. 子类有必要要对父类中的办法进行重写
  3. 通过父类的引证调用重写的办法

多态表现:在代码运行时,当传递不同类目标时,会调用对应类中的办法。

public class Animal {
    String name;
    int age;
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name+"吃饭");
    }
}
public class Cat extends Animal{
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(name+"吃鱼");
    }
}
public class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(name+"吃骨头");
    }
}
////////////////分割线/////////////////////
public class TestAnimal {
    //编译器在编译代码的时分,并不知道要调用Dog仍是Cat中eat的办法
    //等程序运行起来之后,形参a引证的详细目标确认后,才知道调用哪个办法
    //此刻要注意:此处的形参类型有必要是父类类型才可以,也便是向上转型
    public static void eat(Animal animal){
        animal.eat();
    }
    public static void main(String[] args){
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七",1);
        eat(cat);
        eat(dog);
    }
}

运行成果

一篇带你搞定Javaの多态

在上述代码中,分割线上方的代码是类的完结者 编写的,分割线下方的代码是类的调用者编写的
当类的调用者在编写eat();这个办法的时分,参数类型为Animal(父类),此刻在该办法内部并不知道,也并不关注当时的animal引证指向的是哪个类型(哪个子类)的实例。此刻animal这个引证调用eat办法或许会又多种不同的表现(和animal引证的实例目标相关),这种行为就叫做多态
一篇带你搞定Javaの多态

2.3 重写

重写(override):也称为掩盖。重写是子类对父类非静态、非private润饰,非final润饰,非结构办法等进行重新编写的一个进程,回来值和形参都不能改动即外壳不改动,中心重写
重写的长处在于子类可以依据需要,界说特定的属于子类自己的行为。
也便是说子类可以依据需要来完结父类的办法,又和父类的办法不完全一样,完结自己的特色

2.3.1 [办法重写的规则]

  • 子类在重写父类的办法时,一般有必要与父类办法原型一致:润饰符 回来值类型 办法名(参数列表)要完全一致
  • JDK7以后,被重写的办法回来值类型可以不同,可是有必要是具有父子联系
  • 拜访权限不能比父类中被重写的办法的拜访权限更低。例如:假如父类办法被public润饰,则子类中重写该办法就不能声明为protected
  • 父类被static、private润饰的办法都不能被重写
  • 子类和父类在同一个包中,那么子类可以重写父类中的一切办法,除了声明为private和final的办法
  • 子类和父类不在同一个包中,那么子类只可以重写父类的 声明为public 和protected的非final办法
  • 重写的办法,可以运用 @Override 注解来显式指定。有了这个注解可以帮咱们查看这个办法有没有被正确重写。例如不小心讲办法名拼写错了,此刻编译器就会发现父类中并没有这个办法,就会编译报错,构不成重写。

一篇带你搞定Javaの多态

2.3.2 [generate小技巧]

咱们右击,点击generate,然后发现这个选项

一篇带你搞定Javaの多态

一篇带你搞定Javaの多态

这样就可以自动生成重写的办法了
一篇带你搞定Javaの多态

2.3.3 [重写和重载的差异]

差异点 重载(overloading) 重写(override)
参数列表 有必要修正 必定不能修正
回来类型 可以修正 必定不能修正
拜访限定符 可以修正 不能做出更严格的约束(子类权限大于父类)

即:办法重载式一个类的多态性的表现,而办法重写式子类与父类的一种多态性表现

一篇带你搞定Javaの多态

一篇带你搞定Javaの多态

2.3.4 [重写的规划原则]

关于现已投入运用的类,咱们要做到尽量不去进行修正。最好的办法是:重新界说一个新的类,来重复利用其中共性的内容,而且增加或许改动新的内容。
例如:若干年前的手机,只能打电话,发短信,来电显现只能显现号码,而今日的手机在来电显现的时分,不仅仅可以显现号码,还可以显现头像,地区等。在这个进程中,咱们不应该在本来的老的类上进行修正,因为本来的类或许还有用户在运用,直接修正会影响到这些用户的运用作用,正确做法应该是新建一个手机的类,对来电显现这个办法重写就好了,这样就达到了咱们当今的需求

一篇带你搞定Javaの多态

静态绑定

也称为前期绑定(早绑定),即在编译时,依据用户所传递实参类型就确认了详细调用哪个办法。
典型代表函数重载

动态绑定

也称为后期绑定(晚绑定),即在编译时,不能确认办法的行为,需要等到程序运行时,才可以确认详细调用哪个类的办法

2.4 向上转型和向下转型

2.4.1 向上转型

一篇带你搞定Javaの多态

向上转型:实际上便是创立一个子类目标,将其当成父类目标来舒勇
语法格式: 父类类型 目标名 = new 子类类型();

Animal animal = new Cat("元宝"2);

animal是父类类型,但可以引证一个子类目标,因为是从小范围向大范围的转换

一篇带你搞定Javaの多态

一篇带你搞定Javaの多态

猫和狗都是动物,因而将子类目标转化为父类引证时合理的,大范围可以包括小范围,是安全
【运用场景】

  1. 直接赋值
  2. 办法传参
  3. 办法回来
public class TestAnimal {
    //2.办法传参:形参为父类型引证,可以承受恣意子类的
    public static void eatFood(Animal a){
    a.eat();
    }
    //3.作为回来值:回来恣意子类目标
    public static Animal buyAnimal(String var){
        if("狗"==var){
            return new Dog("狗狗"1);
        }else if("猫"==var){
            return new Cat("猫猫",1);
        }else{
            return null;
        }
    }
    public static void main(String[] args){
        //1.直接赋值:子类目标赋值给父类目标
        Animal cat = new Cat("元宝"2);
        Dog dog = new Dog("小七",1);
        eatFood(cat);
        eatFood(dog);
        Animal animal = buyAnimal("狗");
        animal.eat();
        animal = buyAnimal("猫");
        animal.eat();
    }
}

向上转型的长处:让代码完结愈加简略灵活
向上转型的缺陷:不能调用到子类特有的办法

2.4.2 向下转型

将一个子类目标经过向上转型之后当成父类目标运用,再无法调用子类的办法,但有时分或许需要调用子类特有的办法,此刻:将父类引证再还原为子类目标即可,即向下转换。

一篇带你搞定Javaの多态

public class TestAnimal {
    Cat cat = new Cat("元宝",2);
    Dog dog = new Dog("小七",1);
    //向上转型
    Animal animal = cat;
    animal.eat();
    animal = dog;
    animal.eat();
    //下面这种状况会编译失利
    //编译时编译器将animal当作Animal的目标处理
    //而Animal类中并没有bark办法,因而编译就会失利
    //animal.bark();
    //向上转型
    //程序可以通过编译,可是运行的时分仍是会抛出反常
    //因为:animal实际上指向的是狗的目标
    //现在要强制还原为猫则无法正常还原,运行时抛出:ClassCastException
    cat = (Cat)animal;
    cat.mew();
    //animal本来指向的便是狗,因而将animal还原为狗也是安全的
    dog = (Dog)animal;
    dog.bark();
}

向下转型用的比较少,而且不安全,假如转换失利,运行时就会抛出反常。Java中为了提高向下转型的安全性,引入了instanceof,假如该表达式为true,则可以安全转换

public class TestAnimal {
    public static void main(String[] args){
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七",1);
        //向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        }
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

2.5 多态的优缺点

运用多态的长处
1.可以降低代码的“圈复杂度”,防止运用大量的if-else

什么叫“圈复杂度”? 圈复杂度是一种描绘一段代码复杂程度的办法。 一段代码假如平铺直叙,那么就比较简略简略了解。 而假如有很多的条件分支或许循环句子,就认为了解起来更复杂 因而咱们可以简略粗暴的核算一段代码中条件句子和循环句子呈现的个数,这个 数就称为“圈复杂度”。假如一个办法的圈复杂度太高,就需要考虑重构 不同公司关于代码的圈复杂度的标准不一样,一般不会超过10

例如咱们现在需要打印的不是一个形状了,而是多个形状,假如不根据多态,完结代码如下

public class DrawShapes {
    Rect rect = new Rect();
    Cycle cycle = new Cycle();
    Flower flower = new Flower();
    String[] shapes = {"cycle","rect","cycle","rect","flowers"};
    public static void main(String[] args) {
        Rect rect = new Rect();
        Cycle cycle = new Cycle();
        Flower flower = new Flower();
        String[] shapes = {"cycle","rect","cycle","rect","flower"};
        for(String shape:shapes){
            if(shape.equals("cycle")){
                cycle.draw();
            }else if(shape.equals("rect")){
                rect.draw();
            }else if(shape.equals("flower")){
                flower.draw();
            }
        }
    }
}

输出成果为

一篇带你搞定Javaの多态

假如运用多态,则不必写出这么多的if-else分支句子,代码将愈加简略

    public static void drawShapes(){
        Shapes[] shapes = {new Cycle(),new Rect(),new Cycle(),
                           new Rect(),new Flower()};
        for(Shapes shape = shapes){
            shape.draw();
        }
    }

2.可扩展才能更强
假如要新增一种新的形状,运用多态的办法代码改动本钱也比较低

class Triangle extends Shape {
    @Override
    public void draw(){
        System.out.println("▲");
    }
}

关于类的调用者来说,(drawShapes办法),只需创立一个新类的实例就可以了,改动本钱很低。
而关于不用多态的状况,就要把drawShapes中的if-else进行必定的修正,改动本钱更高
多态的缺陷:代码运行的效率降低

2.6 防止在结构办法中调用重写的办法

这儿介绍一个埋着坑的代码,咱们创立两个类,B是父类,D是子类,D中重写了func的办法。而且B的结构办法中调用了func

class B{
    public B(){
        func();
    }
    public void func(){
        System.out.println("B.func()");
    }
}
class D extends B{
    private int num = 1;
    public void func(){
        System.out.println("D.func()"+num);
    }
}
public class Test {
    public static void main(String[] args){
        D d = new D();
    }
}

一篇带你搞定Javaの多态

为啥这儿的履行成果是0而不是1捏?

  • 结构D目标的时分,会调用B的结构办法。
  • B的结构办法中调用了func办法,此刻会触发动态绑定,会调用到D中的func
  • 此刻D目标本身还没有结构,此刻num处在未初始化的状态,值为0

结论:“用尽量简略的办法使目标进入可工作状态”,尽量不要在结构器中调用办法(假如这个办法被子类重写,就会触发动态绑定,可是这个时分子类目标还没有结构完结),或许就会呈现一些隐藏的且极难被发现的问题。

多态就介绍到这儿
希望能帮到你
感谢阅览~