JavaSE-多态

多态的概念

在完成某个行为时,不同的对象在完成时会呈现出不同的状态。

比如:动物都会吃饭,而猫和狗都是动物,猫在完成吃饭行为时吃猫粮,狗在完成吃饭行为时吃狗粮,猫和狗都会叫,狗在完成这个行为时会狗叫,而哈基米会哈气。

在编程中,多态是指利用子类继承父类的特性在使用对象时达到简化代码的目的。

多态的实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下

  2. 子类必须要对父类中方法进行重写

  3. 通过父类的引用调用重写的方法

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

下面是简单的示例:

java 复制代码
public class Animal {

    public String name;
    public int age;

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

    public void cry(){
        System.out.println(name+"正在叫---");
    }

}

public class Dog extends Animal{
    public Dog(String name,int age){
        super(name,age);
    }

    public void cry(){
        System.out.println(name+"正在狗叫---");
    }
}

ublic class Cat extends Animal{
    public Cat(String name,int age){
        super(name,age);
    }

    public void cry(){
        System.out.println(name+"正在哈气---");
    }
}

public class test {
    public static void main(String[] args) {
        Animal dog = new Dog("富贵",3);

        Animal cat = new Cat("哈吉米",4);

        dog.cry();
        cat.cry();
    }
}

运行结果:

在父类Animal和子类Dog与Cat中都有名为cry的方法,我们可以发现在主方法实例化子类后调用的cry均为子类本身的方法,这种现象就称之为多态。

重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

【方法重写的规则】

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

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

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

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

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

方法的重写与重载的区别:

在重写中,两个方法的参数列表必须相同,返回类型必须相同或者构成父子关系,访问权限限定符必须相同或者更宽松,而在重载中,参数列表必须不同才能构成重载,返回类型可以随意修改,访问权限限定符可以随意修改。

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

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

两种转型

向上转型

概念;创建一个子类对象,将其作为父类对象来使用。

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

由于父类包含子类,父类是大范围,子类是小范围,所以这种转型就是一种父类对子类的引用。

向上转型的使用场景:

1.直接赋值

java 复制代码
Animal dog = new Dog("富贵",3);

将子类直接赋值给父类。

2.方法传参

java 复制代码
public void AnimalCry(Animal a){
        a.cry();
    }

在方法的参数列表中使用父类Animal声明,在传参时就可以传入子类Cat和Dog。

3.方法返回值

java 复制代码
public Animal buyAnimal(int a){
        if(a==1){
            return new Cat("猫猫",1);
        }else if(a==2){
            return new Dog("狗狗",1);
        }else{
            return null;
        }
    }

示例:

java 复制代码
public class test {
    public static void animalCry(Animal a){
        a.cry();
    }

    public static Animal buyAnimal(int a){
        if(a==1){
            return new Cat("猫猫",1);
        }else if(a==2){
            return new Dog("狗狗",1);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Animal dog = new Dog("富贵",3);
        Animal cat = new Cat("哈吉米",4);

        animalCry(dog);
        animalCry(cat);

        Animal sweet = buyAnimal(1);
        sweet.cry();

        sweet = buyAnimal(2);
        sweet.cry();
    }
}

在主方法中,我们首先实例化Dog和Cat,然后将两个子类对象传参给animalCry,然后用sweet接受buyAnimal返回的子类对象。这里就使用到了三种向上转型的场景。

向上转型的优点:使代码更加方便简洁。

向上转型的缺点:不能使用子类特有的方法。

向下转型

将一个子类对象向上转型为父类对象后,无法在使用子类特有的方法,此时需要用到向下转型。

java 复制代码
public class Animal {

    public String name;
    public int age;

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

    public void cry(){
        System.out.println(name+"正在叫---");
    }

}

public class Dog extends Animal{
    public Dog(String name,int age){
        super(name,age);
    }

    public void cry(){
        System.out.println(name+"正在狗叫---");
    }

    public void eatBone(){
        System.out.println(name+"正在啃骨头---");
    }
}

public class Cat extends Animal{
    public Cat(String name,int age){
        super(name,age);
    }

    public void cry(){
        System.out.println(name+"正在哈气---");
    }

    public void eatFish(){
        System.out.println(name+"正在吃鱼---");
    }
}

public class test {
    public static void main(String[] args) {
        Dog dog = new Dog("富贵",3);
        Cat cat = new Cat("哈吉米",4);

        //向上转型
        Animal animal =cat;
        animal.cry();
        animal=dog;
        animal.cry();

        animal.eatBone();
        //这里编译器会将animal作为Animal对象处理
        //而Animal类中没有eatBone方法
        //所以会编译报错

        cat = (Cat)animal;
        cat.eatFish();
        //这里对animal进行强制类型转换为cat
        //但是animal在向上转换之前指向的实际是dog
        //所以这里在运行时会出现异常

        dog=(Dog)animal;
        dog.eatBone();
    }
}

删除错误代码后的运行结果:

为了确保向下转型时的安全性,Java引入了instanceof关键字

当(a instanceof b )为真时,代表a为b类实例化的对象,以此判断a的类型。

多态的优缺点

优点

假设有如下父类和子类:

java 复制代码
class Shape {

    public void draw() {
        System.out.println("画图形!");}
    }
class Rect extends Shape{

    public void draw() {
        System.out.println("♦");
    }
}
class Cycle extends Shape{

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

    public void draw() {
        System.out.println("❀");
    }
}

如果我们不使用多态思想,那么需要这样打印这些图形:

java 复制代码
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();
            }
        }
    }

使用多态思想:

java 复制代码
public static void drawShapes() {
    // 我们创建了一个 Shape 对象的数组.
        Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }

两种方法输出相同,代码量却相去甚远:

我们可以看出多态思想为我们省去了许多if-else分支,降低了代码的圈复杂度 ,这就是多态的第一个优点。

多态的第二个优点时可扩展性强,如果新增一种形状,使用多态的方法改动也更加简洁:

java 复制代码
class Triangle extends Shape {
    public void draw() {
        System.out.println("△");
    }
}

缺点

  1. 属性没有多态性

当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性

  1. 构造方法没有多态性