七分钟“手撕”三大特性<多态>

发布于:2024-04-24 ⋅ 阅读:(25) ⋅ 点赞:(0)

目录

一、学习多态之前需要的知识储备

二、重写

1.什么是重写

2.重写可以干嘛

3.怎么书写重写

4.重载与重写的区别

三、向上转型

1.什么是向上转型?

2.向上转型的语法

3.向上转型的使用场景 

四、多态是什么

六、多态实现

七、多态的好处

八、多态的缺点 

九、向下转型


一、学习多态之前需要的知识储备

学习多态之前,我们得知道多态需要的知识储备,有了这个基础,我们才能更好的学习多态。

多态的发生需要三个条件(三个“要”):

(一)要继承

(二)要重写

(三)要向上转型

在之前的博客谈及过继承,可以翻阅我之前的博客,这里就不赘述了。因此,我们先需要学习重写与向上转型。请看:

二、重写

1.什么是重写

重写是指:书写方法时返回值和形参都不能改变。即外壳不变,核心重写!简单来讲:增加多点业务。

例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改(因为可能还有老用户在使用)正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。

2.重写可以干嘛

重写就是为了服务多态,为多态做继承可以认为是不同人做不同事,多写出一个一模一样的方法,打个不恰当的比方就是看人下菜

3.怎么书写重写

public class Animal {
    String name;
    int age;
//构造方法
    public Animal(String name,int age){
        this.name=name;
        this.age=age;
    }
    //这个eat方法与下面的eat方法一模一样(除了里面的内容),形成重写
    public void eat(){
        System.out.println(this.name+"吃饭");
    }
}

class Dog extends Animal{
    public Dog(String name,int age){
        super(name, age);
    }
    //eat方法重写,对于不同的对象(狗狗)用不同的方法
    public void eat(){
        System.out.println(this.name+"吃骨头");
    }
}

class Cat extends Animal{
    public Cat(String name,int age){
        super(name, age);
    }
    //eat方法重写,对于不同的对象(猫猫)用不同的方法
    public void eat(){
        System.out.println(this.name+"吃鱼");
    }
}

【方法重写的规则】

1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致。

2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的。

3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected。

4.父类被static、private修饰的方法、构造方法都不能被重写。

5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。

4.重载与重写的区别

三、向上转型

1.什么是向上转型?

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

2.向上转型的语法

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

Animal animal = new Dog("puppy",2);

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。 

3.向上转型的使用场景 

 【使用场景】

1. 直接赋值

2. 方法传参

3. 方法返回

public class Test {
    //方法传参的方法
    public static void eat(Animal animal){
        animal.eat();
    }
    //方法返回的方法
    public static Animal buyAnimal(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) {
        //直接赋值
        Animal animal=new Dog("puppy",2);
        //方法传参
        eat(new Dog("puppy",2));
        //方法返回
        Animal animal2 = buyAnimal("狗");
    }
}

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

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

四、多态是什么

既然我们了解了重写以及向上转型,加上之前博客提到的继承,我们接下来来学习一下多态。

首先,多态是什么呢?

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

六、多态实现

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(this.name+"吃饭");
    }
}

class Dog extends Animal{
    public Dog(String name,int age){
        super(name, age);
    }
    public void eat(){
        System.out.println(this.name+"吃骨头");
    }
}

class Cat extends Animal{
    public Cat(String name,int age){
        super(name, age);
    }
    public void eat(){
        System.out.println(this.name+"吃鱼");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal1=new Cat("花花",2);
        animal1.eat();
        Animal animal2=new Dog("puppy",2);
        animal2.eat();
        Animal animal3=new Animal("caddy",3);
        animal3.eat();
        
    }
}
//执行结果:
花花吃鱼
puppy吃骨头
caddy吃饭

对于不同的对象调用,产生的结果不一样。 

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

七、多态的好处

假设我们有以下代码:

class Shape {
//属性....
public void draw() {
    System.out.println("画图形!");
}
}
class Rect extends Shape{
@Override
public void draw() {
    System.out.println("♦");
}
}
class Cycle extends Shape{
@Override
public void draw() {
    System.out.println("●");
}
}

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

1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else 

比如,假设我们不基于多态,我们会写出下列代码:

public static void drawShapes() {
    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();
}
}
}

 如果我们基于多态,会写出以下代码:

public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), 
new Rect(), new Cycle(),
new Rect(), new Flower()
};//我们通过把子类的对象放进父类的数组里面,也算向上转型
for (Shape shape : shapes) {
    shape.draw();
}
}

2. 可扩展能力更强 

如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。

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

八、多态的缺点 

我们需要避免在构造方法中调用重写的方法:

一段有坑的代码.。我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法。并且在 B 的构造方法中调用 func。

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 Test {
public static void main(String[] args) {
    D d = new D();
}
}
// 执行结果
D.func() 0

构造 D 对象的同时, 会调用 B 的构造方法。

B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func。

此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0。

所以在构造函数内,尽量避免使用实例方法,除了final和private方法。 

九、向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。 

 

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();
// 编译失败,编译时编译器将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();
}
}
}