0
点赞
收藏
分享

微信扫一扫

【前端面试3+1】13 JS特性、JS是单线程还是多线程、JS中的一部和同步、【合并两个有序数组】

上善若水的道 04-14 17:30 阅读 1

在这里插入图片描述

文章目录

1. 多态

面向对象三大特性:封装、继承、多态
今天我们一起来看看多态这一特性~~~

1.1 多态是什么

在生活中,如果遇到特别伤心的事情或者心情很沮丧,可能就会哭泣,但在不同人身上,哭泣的形式不一样,比如"臣妾做不到",小女孩哇哇大哭,还有大耳朵图图抱着妈妈的腿哭泣等等。总之,同一件事情,发生在不同对象上,表现的形式是不一样的~
在这里插入图片描述多态】通俗来说,多种形态,即去完成某个行为,当不同的对象去完成时会产生出不同的状态

1.2 多态的意义

多态的意义
在于提高代码的复用性扩展性,同时实现接口统一,并十分灵活,可以根据不同的输入参数或条件,调用不同的子类方法实现不同的功能,易于维护和修改(本期内容结尾将重点提到)

1.3 多态的实现条件

在Java中实现多态的条件如下(缺一不可):
(1) 必须在继承
(2) 子类必须要对父类中方法进行重写
(3) 通过父类的引用调用重写的方法(向上转型)
多态体现的方面:当传递不同类对象时,会调用对应类中的方法
代码

public class Animal {
    String name;
    String gender;
    int age;

    public Animal(String name,String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

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

    @Override
    public void eat() {
        System.out.println(name+"吃🐟");
    }
}
public class Dog extends Animal{
    public Dog(String name,String gender,int age) {
        super(name, gender, age);
    }

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

    @Override
    public void eat() {
        System.out.println(name+"啄米");
    }
}
public class Test {
    public static void eat(Animal a) {
        a.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("柚子","雌",2);
        Dog dog = new Dog("球球","雄",1);
        Bird bird = new Bird("泡芙","雌",2);
      	eat(cat);
        eat(dog);
        eat(bird);
    }
}

运行结果
在这里插入图片描述
解释说明
在上述代码中,Animal、Cat、Dog和Bird类是类的实现者写的,Test类是类的调用者写的
当类的调用者在编写 eat 这个方法的时,传进来的参数类型为 Animal父类,在该方法内部并不知道当前的a 引用指向的是哪个类型,哪个子类的实例,即a这个引用调用 eat方法可能会有多种不同的表现(a引用的实例相关,比如猫类实例调用、狗类实例调用等,eat表现的形式不同),这种行为称为多态
图示
在这里插入图片描述
@Override
@Override是jdk的注解,用于指示一个方法是重写了父类中的方法
这个注解只在编译时有效,不会保留在生成的字节码文件中
使用 @Override 注解的优点:
1)提高代码的可读性安全性
2)在编译时检查可能的错误
但它不是必需要写的,只要正确地重写了父类中的方法,不使用 @Override 注解,代码仍能够正常运行
(即使用或不使用取决于编码风格和习惯)

2. 重写

2.1 重写的概念

重写】也称为覆盖,重写是子类对父类实现过程进行重新编写, 返回值形参都不能改变,即外壳不变,核心重写,重写的好处是子类可以根据需要,定义特定于自己的行为,即子类能够根据需要实现父类的方法

2.2 重写的规则

重写规则
1)子类在重写父类的方法时,一般必须与父类的返回值类型方法名 (参数列表) 完全一致
2)被重写的方法返回值类型可以不同,但必须具有父子类关系
3)访问权限不能比父类中被重写的方法的访问权限更低(如父类方法被public修饰,则子类中重写该方法不能声明为 private)
4)父类被staticprivatefinal修饰的方法、构造方法都不能被重写
5)重写的方法可使用 @Override 注解来显式指定,能进行一些合法性校验,检查可能会出现的错误(如不小心将方法名字写错, 此时编译器就会发现父类中没有该方法,则编译报错,提示无法构成重写)

2.3 重写与重载的区别

在这里插入图片描述
在这里插入图片描述

2.4 重写的设计

重写的设计原则】对于已经投入使用的类,尽量不要进行修改,最好是重新定义一个新的类,重复利用其中共性的内容,并添加或者改动新的内容
举一个栗子吧~
例如之前的电视机,只能看正在播放的频道,而当今时代,科技越来越发达,技术在进步,现在的电视不仅可以看正在播放的频道,还可以回看,直播等等,而我们不应该直接在原来的类修改,因为原来的类可能仍有用户正在使用,而正确的解决方式为,新建一个新型电视机的类,对可以看的内容这个方法进行重写,增添更多的功能,即达到需求
在这里插入图片描述
静态绑定】即在编译时,根据用户所传递实参类型就确定具体调用哪个方法,代表:函数重载
动态绑定】即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用哪个类的方法(发生多态的基础)

3. 向上转型和向下转型

3.1 向上转型

向上转型】实际上是创建一个子类对象,当父类对象使用
语法格式】父类类型 对象名 = new 子类类型();
语法举例

animal是父类Animal类型,即小的范围变成大的范围(是可以的),比如猫、狗和鸟等都是动物,将子类对象转化为父类引用是合理、安全的,大的范围包含小的范围

使用场景
1)直接赋值 2)方法传参 3)方法返回 【具体使用代码举例如下】:

public class Test1 {
    public static void main(String[] args) {
        Cat cat = new Cat("柚子","雌",2);
        Dog dog = new Dog("球球","雄",1);
        //1.直接复制 子类对象赋值给父类对象
        Animal bird = new Bird("泡芙","雌",2);
        eat(cat);
        eat(dog);
        eat(bird);
    }
    //2.方法传参 形参的类型为Animal父类,可接受任意子类对象,即animal1的类型可以是猫、狗、鸟等子类
    public static void eat(Animal animal1) {
        animal1.eat();
    }
    //3.方法返回 返回任意子类对象
    public static Animal guessAnimal(String name) {
        if("球球".equals(name)) {
            return new Dog("球球","雄",1);
        }
        return null;
    }
}

优点
让代码实现更简单灵活
缺点
不能调用到子类特有的方法只能调用父类的方法

3.2 向下转型

向下转型】子类对象向上转型后当父类对象方法使用,再无法调用子类的方法,如需调用子类特有的方法,将父类引用再还原为子类对象即可,即向下转型,在Java中,向下转型是从一个更通用的类型向一个更具体的类型转换的过程
在这里插入图片描述
语法格式】子类对象名 = (子类类型) 对象名
语法举例

使用场景
将它们转回具体的类型以利用它们的具体实现的情况

public class Animal {
    String name;
    String gender;
    int age;

    public Animal(String name,String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

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

    @Override
    public void eat() {
        System.out.println(name+"吃🐟");
    }

    public void meow() {
        System.out.println("猫在叫~");
    }
}
public class Dog extends Animal{
    public Dog(String name,String gender,int age) {
        super(name, gender, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"吃骨头");
    }
    public void bark() {
        System.out.println("狗在叫~");
    }
}
public class Bird extends Animal{
    public Bird(String name,String gender,int age) {
        super(name, gender, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"啄米");
    }

    public void chirp() {
    System.out.println("小鸟叫~");
    }

}
public class Test2 {
    public static void main(String[] args) {
        Cat cat = new Cat("柚子","雌",2);
        Dog dog = new Dog("球球","雄",1);
        Bird bird = new Bird("泡芙","雌",2);

        //向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        animal = bird;
        animal.eat();
   
        //向下转型
        //程序可以通过编程但是会抛出异常,animal实际指向的是鸟,但现在强制还原为猫,无法还原
        //cat = (Cat)animal;
        //cat.meow();

        //animal指向的是鸟,将animal还原为鸟是安全的
        bird = (Bird)animal;
        bird.chirp();

        //instanceof 判断它左边的对象是否是它右边的类的实例,返回boolean的数据类型,是返回true否则返回false;
        if(animal instanceof Cat) {
            cat = (Cat)animal;
            cat.meow();
        }

        if(animal instanceof Dog) {
            dog = (Dog)animal;
            dog.bark();
        }
        //animal指向的是鸟,左边的对象animal是右边鸟类的实例,为true,执行if内容语句
        if(animal instanceof Bird) {
            bird = (Bird) animal;
            bird.chirp();
        }
    }
}

运行结果
在这里插入图片描述
解释说明
在这里插入图片描述

优点
可访问更具体类型的特定方法和属性,明确正在处理的具体类型
缺点
不安全,万一转换失败,运行时会抛异常,Java中为了提高向下转型的安全性,引入instanceof关键字 ,如该表达式为true,则可以安全转换,较为麻烦

3.3 instanceof关键字

概念】instanceof是Java中的保留关键字
作用】测试它的左边对象是否是它的右边类的实例,返回类型:boolean类型
用法】res = 对象名 instanceof 类名;
举例】以上述代码举例
在这里插入图片描述

4. 多态的优缺点

4.1 多态的优点

1)能够避免使用大量的 if - else
如果需要打印多个形状,不基于多态写,则会有好几个if语句

public class Shape {
    public void draw() {
        System.out.println("画图形~");
    }
}
public class Rectangle extends Shape{
    @Override
    public void draw() {
        System.out.println("◇");
    }
}
public class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("○");
    }
}

public class Fish extends Shape{
    @Override
    public void draw() {
        System.out.println("🐟");
    }
}
public class Test3 {
    public static void drawShapes() {
        Rectangle rectangle = new Rectangle();
        Cycle cycle = new Cycle();
        Fish fish = new Fish();
        String[] shapes = {"cycle","rectangle","fish"};

        for(String x : shapes) {
            if(x.equals("cycle")) {
                cycle.draw();
            }else if(x.equals("rectangle")) {
                rectangle.draw();
            }else if(x.equals("fish")) {
                fish.draw();
            }
        }
    }

    public static void main(String[] args) {
        drawShapes();
    }
}

基于多态编写代码,创建一个Shape对象的数组,则简洁明了~

public class Test3 {
    public static void drawShapes1() {
        Shape[] shapes = {new Cycle(),new Rectangle(),new Fish()};
        for(Shape x: shapes) {
            x.draw();
        }
    }

    public static void main(String[] args) {
        drawShapes1();
    }
}

两者打印结果一致,所以可以避免大量的if-else语句
在这里插入图片描述
2)可扩展能力更强
如果要新增加一种新的形状,使用多态方式编写的代码改动成本较低,体现可扩展性强,比如我们要增加三角形这个形状类型

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

解释说明】基于多态代码,对类的使用者来说,drawShapes方法只需创建一个新类的实例,改动成本较低,对于不基于多态的代码, 则需把drawShapes方法中的 if - else 进行修改,改动成本更高

4.2 多态的缺点

代码的运行效率降低
1)属性没有多态性,当父类和子类都有同名属性时,通过父类引用只能引用父类自己的成员属性
2)构造方法没有多态性
A为父类,在A类构造方法中调用func()方法,B为子类,B类中重写func()方法
代码举例

public class A {
    public A() {
        func();
    }
    public void func() {
        System.out.println("A.func()");
    }
}
public class B extends A{
    private String str = "~";

    @Override
    public void func() {
        System.out.println("B.func()"+" "+str);
    }
}
public class Test4 {
    public static void main(String[] args) {
        B b = new B();
    }
}

运行结果
在这里插入图片描述
解释说明
构造B对象的同时,会调用A的构造方法
A的构造方法中调用func()方法,此时会触发动态绑定,即在编译时不能确定方法的行为,等程序运行时,确定是具体调用B类中的 func()方法,此时B对象自身还没有构造, str是未初始化的状态,为null,如果具备多态性,str的值应该是~,即构造方法没有多态性
结论】在构造函数内,尽量避免使用实例方法,除final和private方法
即尽量不要在构造器中调用方法,如果这个方法被子类重写,就会触发动态绑定,但此时子类对象还没构造完成,可能会出现一些隐藏但很难发现的问题,带来不必要的麻烦
💛💛💛本期内容回顾💛💛💛
在这里插入图片描述✨✨✨本期内容到此结束啦~下期再见!

举报

相关推荐

0 条评论