java继承与多态——多态

1. 多态

1.1 多态的概念

多态就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。

例如不同的动物吃的食物不一样。



不同的交通工具速度不一样

1.2 多态的实现条件

要实现多态,必须要满足如下几个条件:

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写
  • 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

class Animals {
    public String name;
    public int age;
    public Animals(String name, int age ) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(name + "吃饭");
    }
}

class Cat extends Animals{
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(name + "吃饭");
    }
}

class Dog extends Animals{
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        //与父类的eat方法构成重写
        System.out.println(name + "吃饭");
    }
}

public class Test {
    public static void eat(Animals animals) {
        animals.eat();
    }
    
    public static void main(String[] args) {
        Dog dog = new Dog("大黄", 6);
        Cat cat = new Cat("小黑", 6);
        eat(dog);
        eat(cat);
    }
}




在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 animals 引用指向的是哪个类型(哪个子类)的实例。此时 animals 这个引用调用 eat方法可能会有多种不同的表现(和 animals 引用的实例相关), 这种行为就称为多态。

1.3 重写

重写(override):也称为覆盖。重写是子类对父类非静态非private修饰非final修饰非构造方法等的实现过程进行重新编写, 返回值形参都不能改变。即外壳不变,核心重写


【方法重写的规则】

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型方法名 (参数列表) 要完全一致。
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected。
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定.,有了这个注解能帮我们进行一些合法性校验.。例如不小心将方法名字拼写错了 (比如写成 aet),那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。


【重写与重载的区别】


重写方法名和参数都一样。

class Animals {
    public String name;
    public int age;

    public void eat() {
        System.out.println(name + "吃饭");
    }
    public void sleep() {
        System.out.println(name + "睡觉");
    }
    public void play() {
        System.out.println(name + "做游戏");
    }
}

class Cat extends Animals{
    public Boolean LoveClean;//爱干净

    public void catchMouse() {
        System.out.println(name + "抓老鼠");
    }
    public void eat() {
        System.out.println(name + "吃饭");//与父类的eat方法构成重写
    }
}


重载方法名相同,参数不同。

class Cat{
    public String name = "小黑";
    
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
    public void catchMouse(String name) {//构成重载
        
    }
}


静态绑定: 也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

动态绑定: 也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

1.4 向上转型和向下转型

1.4.1 向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

Aniamls animals = new Dog("大黄", 7);

向上转型的使用场景:

  • 直接赋值
  • 方法传参
  • 方法返回
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 + "吃食物");
    }
}

class Cat extends Animals {
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(name + "吃食物");
    }
}

class Dog extends Animals {
   
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        //与父类的eat方法构成重写
        System.out.println(name + "吃食物");
    }
}

public class TestAniamls {
    // 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eat(Animals animals) {
        animals.eat();
    }
    //作返回值:返回任意子类对象
    public static Animals byAnimals(String var) {
        if ("小狗狗".equals(var)) {
            return new Dog("小狗狗", 1);
        } else if ("小猫猫".equals(var)) {
            return new Cat("小猫猫", 1);
        }else {
            return null;
        }
    }
    public static void main(String[] args) {
        //直接赋值:子类对象赋值给父类对象
        Animals cat = new Cat("小黑", 7);
        Dog dog = new Dog("大黄", 6);
        eat(cat);
        eat(dog);

    }
}

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

1.4.2 向下转型

将父类引用再还原为子类对象,即向下转型。

向下转型不安全。


因为猫和狗是动物,向上转型安全;但是动物不一定是猫或者狗,所以不安全。

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 + "吃食物");
    }
}

class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    public void catchMouse() {
        System.out.println(name + "抓老鼠");
    }
    @Override
    public void eat() {
        System.out.println(name + "吃食物");
    }
}

class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }
    public void brak() {
        System.out.println(name + "汪汪叫");
    }
    @Override
    public void eat() {
        //与父类的eat方法构成重写
        System.out.println(name + "吃食物");
    }
}

public class TestAnimals {
    public static void main(String[] args) {
        Animal animal = new Cat("小黑", 7);
        Cat cat = (Cat)animal;//向下转型
        cat.catchMouse();
    }
}


Dog类当中没有抓老鼠这一方法,引用的话会报错。

//部分代码
public class TestAnimals {
    public static void main(String[] args) {
        Animal animal = new Dog("大黄", 7);
        Cat cat = (Cat)animal;//向下转型
        cat.catchMouse();
    }
}


如果想要避免运行后报错,可以使用 instanceof关键字。

public class TestAnimals {
    public static void main(String[] args) {
        Animal animal = new Dog("大黄", 7);
        if (animal instanceof Cat) {
            Cat cat = (Cat)animal;//向下转型
            cat.catchMouse();
        }
    }
}


如果反生错误的话会返回一个 flase。

1.5 多态的优缺点


先来看一段代码:

一个用来画三种图形的代码

package demo1;

class Graph {
    public void draw() {
        System.out.println("画图形");
    }
}

class Rectangle extends Graph{
    @Override
    public void draw() {
        System.out.println("画矩形!");
    }
}

class Cycle extends Graph{
    @Override
    public void draw() {
        System.out.println("画圆!");
    }
}

class Triangle extends Graph{
    @Override
    public void draw() {
        System.out.println("画三角形!");
    }
}

public class Test {
    public static void main(String[] args) {
        Graph graph1 = new  Rectangle();
        graph1.draw();
        Graph graph2 = new Cycle();
        graph2.draw();
        Graph graph3 = new Triangle();
        graph3.draw();
    }
}


利用多态来进行优化:

package demo1;


class Graph {
    public void draw() {
        System.out.println("画图形");
    }
}

class Rectangle extends Graph{
    @Override
    public void draw() {
        System.out.println("画矩形!");
    }
}

class Cycle extends Graph{
    @Override
    public void draw() {
        System.out.println("画圆!");
    }
}

class Triangle extends Graph{
    @Override
    public void draw() {
        System.out.println("画三角形!");
    }
}

public class Test {
    public static void drawMap(Graph graph) {
        graph.draw();
    }

    public static void main(String[] args) {
        drawMap(new Rectangle());
        drawMap(new Cycle());
        drawMap(new Triangle());
    }
}

同样的drawMap方法,在做着不同的事情,说明这是一个多态。


多态使代码变得简洁了。

【使用多态的好处】

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else。
  2. 如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。

在上面的代码中如果先要实现新的类直接添加就可以了。

//部分代码
class Apple extends Graph{
    public void draw() {
        System.out.println("画苹果!");
    }
}

public class Test {
    public static void drawMap(Graph graph) {
        graph.draw();
    }

    public static void main(String[] args) {
        drawMap(new Rectangle());
        drawMap(new Cycle());
        drawMap(new Triangle());
        drawMap(new Apple());
    }
}



这里可以说明使用多态后,改动也比较方便。

1.6 避免在构造方法中调用重写的方法


先来看一段代码

package demo1;

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

这段代码也该是会输出 D.func() 1。


但是结果却是:



说明一开始num就没有被初始化为1。

  • 构造 D 对象的同时, 会调用 B 的构造方法。
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的func。
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1。
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

结论:

“用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与大师约会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值