Java继承和多态(2)

本篇文章将对多态的相关知识进行讲解

Java继承和多态(2)_第1张图片


一、向上转型

向上转型是实现多态的条件之一;向上转型是让子类对象转换为父类对象或者是让父类的引用指向子类对象,直观的表现形式就是将子类的对象赋值给父类对象的引用;下面讲解向上转型的三种形式

1.1 直接赋值

class Animal { }
class Dog extends Animal { }

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog(); //向上转型
    }
}

1.2 传参

class Animal {}
class Dog extends Animal {}

public class Test {
    public static void func(Animal animal) { //发生向上转型
        animal.eat();
    }

    public static void main(String[] args) {
        Dog dog = new Dog();
        func(dog); //将Dog对象传过去,在func方法中用父类对象接收
    }
}

1.3 返回值

class Animal {}
class Dog extends Animal {}

public class Test {
    public static Animal func() { 
        return new DOg(); //发生向上转型
    }
    public static void main(String[] args) {
        Dog dog = new Dog();
        func(); 
    }
}

注意:向上转型后父类对象并不能访问子类中特有的成员方法

1.4 向下转型

直接看代码:

class Animal {
    public void eat() {
        System.out.println("吃");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("狗在吃");
    }
    public void barks() {
        System.out.println("狗在叫");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.eat();
        //animal.barks();//向上转型后,animal也不能访问子类独有的成员方法,但是可以进行向下转型
        
        Dog dog = (Dog) animal; //将父类对象强转为子类对象
        dog.barks(); //此时就可以访问子类独有的成员方法
    }
}

向下转型也存在一些危险,在上述代码的基础上再增加一个鸟类:

class Animal {
    public void eat() {
        System.out.println("吃");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("狗在吃");
    }
    public void barks() {
        System.out.println("狗在叫");
    }
}

class Bird extends Animal {
    public void fly() {
        System.out.println("鸟在飞");
    }
    public void eat() {
        System.out.println("鸟在吃");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();
        
        Bird bird = (Bird) animal;
        bird.fly(); //编译不会报错,但在运行时会报错,
                    //因为animal实际引用的是一个狗的对象,只能强转为一个狗类
       

        //为了避免这种情况的发生,引入instanceof关键字
        if (animal instanceof Bird) { //这条语句的意思就是animal所指的对象是否为鸟类
            Bird bird1 = (Bird) animal;
            bird1.flu //运行时不会报错
        }
    }
}

二、方法的重写

方法的重写也是也是实现多态的条件之一,重写也叫覆盖,方法的重写发生在继承关系下,一般称作子类对父类的方法进行重写,即在子类中再写一个方法,这个方法必须和父类那个方法的方法名、参数列表、返回值类型相同;这里可以和方法的重载比较理解

重载:方法名相同,参数列表不同,与返回值类型无关

重写:方法名相同,参数列表相同,返回值类型相同

class Animal {
    public void eat() {
        System.out.println("吃");
    }
}

class Dog extends Animal {

    //重写的方法可以用@Override来注释,这样在编译时可以多一层校验
    //如果加上这条注释后,eat方法没有进行重写,那么在编译时就会报错
     @Override
    public void eat() { //方法名都为eat,返回值类型都为void,参数列表相同
        System.out.println("狗在吃");
    }
}

2.1 重写的注意事项

1. 被final修饰的方法叫做密封方法,此方法不能被重写

class Animal {
    public final void eat() {
        System.out.println("吃");
    }
}

class Dog extends Animal {
    
    @Override
    public void eat() {
        System.out.println("狗在吃");
    }
}

//编译会报错,因为父类eat方法被final修饰,eat方法不能被重写

2. 被static修饰的方法不能被重写

3. 子类重写父类方法时,子类方法的访问限定修饰符必须大于等于父类方法,但如果父类方法被private修饰,那么该方法就不能被重写

4. 重写的方法的返回值类型也可以不同,但必须构成父子类关系

class Animal {
    public Animal eat() {
        System.out.println("吃");
        return null; //这里是为了不报错才这么写的
    }
}

class Dog extends Animal {
    @Override 
    public Dog eat() {
        System.out.println("狗在吃");
        return null;
    }
}

2.2 动态绑定

当一个子类对象被转换为了父类对象(向上转型),如果该对象调用一个在子类中被重写的父类方法,Java会根据子类的实际类型来调用子类对象实际所属类的方法,这个过程叫做动态绑定,动态绑定并不是在编译时就知道调用哪一个方法,而是在运行时才知道真正要调用哪一个方法

2.3 构造方法中避免调用重写方法

在构造方法中如果调用重写方法也会发生动态绑定

class B {
    public B() {
        func(); //这里会调用子类的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); //这里打印的num为0
    }
}

public class Test2 {
    public static void main(String[] args) {
        D d = new D();
    }
}

为什么num打印结果为0?先来回顾以下的执行顺序

父类静态--->子类静态--->父类实例--->父类构造--->子类实例--->子类构造

在上述代码中完成父类构造后,直接调用func方法,没有执行子类实例,也就是此时num并没有被赋值,是默认值0

三、多态

多态就是一个父类引用在调用重写的方法时,根据引用所指的对象不同,而调用的不同的方法,以下面的代码为例:

class Animal {
    public void eat() {
        System.out.println("吃");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("狗在吃");
    }
}

class Bird extends Animal {
    public void eat() {
        System.out.println("鸟在吃");
    }
}

public class Test {
    
    public static void func(Animal animal) {
        animal.eat(); //我们称在这里发生了多态
    }
    public static void main(String[] args) {
        Dog dog = new Dog();
        Bird bird = new Bird();
        
        func(dog);
        func(bird);
    }
}

总结:必须向上转型后才能实现动态绑定,通过动态绑定调用子类重写方法,而多态完成了很多次的动态绑定

3.1 多态的好处

1. 可以避免代码中的圈复杂度(即代码中条件语句和循环语句出现的次数)

2. 可扩展性强

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

class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("圆形");
    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("三角形");
    }
}
public class Test2 {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Triangle triangle = new Triangle();

        Shape[] shapes = new Shape[]{cycle, rect, cycle,rect,triangle}; //发生向上转型
        for(Shape shape: shapes) {
            shape.draw(); 发生多态,如果不使用多态,就要用到大量if-else,使代码的圈复杂度提高
        }
        //结果为:圆形 矩形 圆形 矩形 三角形
    }
}

你可能感兴趣的:(Java语言知识点,java,开发语言)