JavaSE--方法覆盖和多态

发布时间 2023-08-09 10:25:36作者: 洛小依ovo

一、方法覆盖Override

1、什么时候使用Override

  例如:以下代码:鸟儿在执行move方法时,最好输出“鸟儿在飞翔”,但是当前程序在执行move方法时,输出“动物在移动”,显然Bird子类继承来的方法无法满足需求

  子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权利进行重新编写方法

public class OverrideTest{
    public static void main(String[] args){
        Bird b = new Bird();
        b.move();
        Cat c = new Cat();
        c.move();
    }
}
// 父类
class Animal{
    public void move(){
        System.out.println("动物在移动");
    }
    // protected表示受保护的,没有public开放
    protected void eat(){
        System.out.println("动物在吃");
    }
}
// 子类
class Bird extends Animal{
    // 子类继承父类之后,有一些行为可能需要进行改进
    // move重写
    public void move(){
        System.out.println("鸟儿在飞翔");
    }
    /*
        错误:正在城市分配更低的访问权限,以前为public
        protected void move(){
            System.out.println("鸟儿在飞翔");
        }
    */
    // 可以更高
    public void eat(){
        System.out.println("鸟儿在抓虫子吃");
    }
    public void sing(){
        System.out.println("鸟儿在唱歌");
    }
}
class Cat extends Animal{
    public void move(){
        System.out.println("猫猫在走路");
    }
    public void catchMouse(){
        System.out.println("猫猫抓老鼠");
    }
}

2、结论

  当子类堆父类继承过来的方法进行“方法覆盖”之后,子类对象调用该方法的时候,一定执行覆盖之后的方法

3、构成方法覆盖的条件

  条件1:两个类必须有继承关系

  条件2:重写之后的方法和之前的方法具有:相同的方法名、相同的形参列表、相同的返回值类型

      注意:相同的返回值类型:对于返回值类型是基本数据类型,必须一致

                对于返回值类型是引用数据类型,可以变小,但意义不大,不会这样写

  条件3:访问权限不能更低,可以更高

  条件4:重写之后的方法不能比之前的方法抛出更多的异常,可以更少

  注意事项:注意1:方法覆盖只是针对于方法,和属性无关

       注意2:私有方法无法覆盖

       注意3:构造方法不能被继承,所以构造方法也不能被覆盖

       注意4:方法覆盖只是针对于实例方法,静态方法覆盖没有意义

4、解释:方法覆盖只是针对于实例方法,静态方法覆盖没有意义

  方法覆盖只针对实例方法,静态方法覆盖没有意义

/*
1、
    没有多态机制,方法覆盖可有可无
    没有多态机制,方法覆盖也可以没有,如果无法满足子类业务需求时,子类完全可以重新定义一个新方法
2、静态方法存在方法覆盖么?
    多态自然和对象有关系
    但是静态一般和对象没有关系
    所以一般情况下,我们会说“静态方法”没有方法覆盖
*/

public class OverrideTest05{
    public static void main(String[] args){
        Animal a = new Cat();
        a.doSome();// 输出:Animal的doSome方法
        Animal.doSome();
        Cat.doSome();
    }
}
class Animal{
    public static void doSome(){
        System.out.println("Animal的doSome方法");
    }
}
class Cat extends Animal{
    // 尝试覆盖
    public static void doSome(){
        System.out.println("Cat的doSome方法");
    }
}

5、解释:私有方法无法覆盖

  在外部类无法访问私有的

二、多态

1、向上转型与向下转型

  1)向上转型

    子----->父(可以这样叫自动类型转换)

  2)向下转型

    父----->子(可以这样叫强制类型转换,需要加强制类型转换符)

    因为自动类型转换和强制类型转换是基本数据类型之间,向上转型和向上转型是在引用类型之间

注意:java中允许向上转型也允许向下转型,但是无论哪种,两种类型之间必须有继承关系,没有继承关系编译器会报错

2、什么时候使用向下转型

  不要随便使用强制类型转换

  当你需要访问的是子类对象中“特有”的方法,此时需要向下转型

3、多态的基础语法

  父类型引用指向子类型对象

  包括编译阶段和运行阶段。编译阶段:绑定父类方法,运行阶段:动态绑定子类方法

public class Test01{
    public static void main(String[] args){
        Animal a1 = new Animal();
        a1.move();
        Cat c = new Cat();
        c.move();
        Bird b = new Bird();
        b.move();
        
        /*
            父类型的引用允许指向子类型的对象
            允许a2这个父类型引用指向子类型的对象
        */
        Animal a2 = new Cat();
        Animal a3 = new Bird();
        /*
            什么是多态
            分析a2.move();
            java程序分为编译阶段和运行阶段
            编译阶段:
                编译器只知道a2的类型是Animal,所以编译器在检查语法的时候会去Animal.class字节码文件中找move方法
                找到之后,绑定上move方法,编译通过,静态绑定成功(编译阶段属于静态绑定)
            运行阶段:
                实际上在堆内存中创建的java对象是Cat对象,所以在move时,真正参与move的对象是Cat,
                所以运行阶段会动态执行Cat对象的move方法,这个过程属于运行阶段绑定(运行阶段绑定属于动态绑定)
            多态表示多种形态:
                编译的时候时一种形态,运行的时候是一种形态
        */
        a2.move();// 猫猫在走路
        a3.move();// 鸟儿在飞翔
        
        Animal a5 = new Cat();
        // 分析程序一定要分析静态绑定和动态绑定
        // 编译不能通过,报错,编译器只知道a5是Animal类型,去Animal.class中找catchMouse没找到
        // a5.catchMouse();
        
        // 如果a5非要使用catchMouse方法,就需要使用向下转型
        // 什么时候使用向下转型:这个行为是子类型对象特有的方法
        Cat x = (Cat)a5;// 强制转换为Cat类型
        x.catchMouse();
    }
}

4、向下转型有风险,怎样避免

/*
    编译不会报错
    但是运行报错
    因为在运行阶段,堆内存中实际存放的Bird对象,但是又强制转成Cat,Bird与Cat没有继承关系
*/
Animal a6 = new Bird();
Cat y = (Cat)a6;
y.catchMouse();

   1)避免向下转型风险

 Java规范中要求:任何时候对类型进行向下转型时,一定要先使用instanceof运算符进行判断

// 怎样避免ClassCastException异常 类转换异常发生
/*
    运算符 instanceof
    第一:instanceof可以在运行阶段动态判断引用指向的对象的类型
    第二:instanceof语法
        (引用 instanceof 类型)
    第三:instanceof运算符的运算结果只能是true、false
    第四:c是一个引用,c变量保存了内存地址指向了堆中的对象
        假设(c instanceof Cat)为true表示:c引用指向的堆内存中的java对象是一个Cat
        假设(c instanceof Cat)为true表示:c引用指向的堆内存中的java对象不是是一个Cat
*/
Animal a6 = new Bird();
if (a6 instanceof Cat){// 如果a6是Cat,在强制类型转换
    Cat y = (Cat)a6;
    y.catchMouse();
}

  2)为什么要用instanceof判断

/*
    明明可以看到new的是谁,为什么还要判断呢
    原因是:有可能以后肉眼看不到
*/
Animal x = new Bird();
Animal y = new Cat();
if(x instanceof Bird){
    Bird b = (Bird)x;
    b.sing();
}else if(x instanceof Cat){
    Cat c = (Cat)x;
    c.catchMouse();
}
if(y instanceof Bird){
    Bird b = (Bird)y;
    b.sing();
}else if(y instanceof Cat){
    Cat c = (Cat)y;
}
class AnimalTest{
    public void test(Animal a){
        // 别人调用时调用test方法时传参数是穿一个Bird还是Cat
        // 别人不知道要传过来一个什么
        // Cat c = (Cat)a;
        // c.catchMouse();
        if(x instanceof Bird){
            Bird b = (Bird)a;
            b.sing();
        }else if(x instanceof Cat){
            Cat c = (Cat)a;
            c.catchMouse();
        }
    }
}