【Java复健指南07】OOP中级02-重写与多态思想

前情提要:https://www.cnblogs.com/DAYceng/category/2227185.html

重写

注意事项和使用细节

方法重写也叫方法覆法,需要满足下面的条件
1.子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。

2.子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类,比如父类返回类型是Object ,子类方法返回类型是String

public Object getInfo(){}
...
public String getInfo(){}

3.子类方法不能缩小父类方法的访问权限

重载overload与重写override的区别

image-20221020124049624

练习

1.编写一个Person类,包括属性/private (name.age),构造器、方法say(返回自我介绍的字符串)。
2.编写一个Student类,继承Person类,增加id、score属性/private,以及构造器,定义say方法(返回自我介绍的信息)。
3.在main中,分别创建Person和Student对象,调用say方法输出自我介绍。

public class OverrideExercise {
    public static void main(String[] args) {
        Person person = new Person("jk", 16);
        System.out.println(person.say());
        Student student = new Student(1,88.6);
        System.out.println(student.say());
    }
}

class Person{
    private String name;
    private int age;

    public Person(){
    }

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

    public String say(){
        return "名字: " + name + " 年龄: " + age;
    }
}

class Student extends Person{
    private int id;
    private double score;

    public Student() {
    }

    public Student(int id, double score) {
        this.id = id;
        this.score = score;
    }

    public String say(){
        return "id: " + id + " 成绩: " + score;
    }
}

多态

多态其实是一种思想,它可以有多种体现方式

多态的具体体现

1.方法的多态

方法的重写和重载就体现多态思想。

2.对象的多态(核心)

(1)一个对象的编译类型和运行类型可以不一致

(2)编译类型在定义对象时,就确定了,不能改变

(3)运行类型是可以变化的.
(4)编译类型看定义时=号的左边,运行类型看=号的右边

例如:

Animal animal = new Dog();

【animal编译类型是Animal,运行类型Dog】
animal = new Cat();

【animal的运行类型变成了Cat,编译类型仍然是Animal】

动物类

public class Animal {
    public void cry(){
        System.out.println("Animal cry()动物在叫");
    }
}

狗类

public class Dog extends Animal{
//    @Override
    public void cry() {
        System.out.println("Dog cry()狗叫");
    }
}

猫类

public class Cat extends Animal{
    //@Override语法校验,写不写都行
    public void cry() {
        System.out.println("Cat cry()猫叫");
    }
}

演示

public class PolyObject {
    public static void main(String[] args) {
        //    animal编译类型是Animal,运行类型Dog
        Animal animal =  new Dog();
        //因为执行到该行是,animal的运行类型是Dog
        //所以cry是Dog的cry
        animal.cry();//Dog cry()狗叫

        //    animal编译类型是Animal,运行类型Cat
        animal = new Cat();
        animal.cry();//Cat cry()猫叫
    }
}

多态的细节

多态的前提:两个对象(类)存在继承关系

多态的向上转型

​ 1)本质:父类的引用指向了子类的对象
​ 2)语法:父类类型引用名=new子类类型();

​ 3)特点:

​ 编译类型看左边,运行类型看右边;
​ 可以调用父类中的所有成员(需遵守访问权 限),不能调用子类中特有成员;
​ 最终运行效果看子类的具体实现;

动物类

public class Animal {
    String name ="动物";int age = 10;
    public void sleep(){
        System.out.println("睡");
    }
    public void run(){
        System.out.println("跑");
    }
    public void eat(){
        System.out.println("吃");
    }
    public void show(){
        System.out.println("hello,你好");
    }

}

猫类

public class Cat extends Animal{
    public void eat(){//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }
}

演示

public class PolyDetail {
    public static void main(String[] args) {
        //向上转型:父类的引用指向了子类的对象
        // 语法:父类类型引用名= new子类类型();
        Animal animal = new Cat();//向上转型
        Object obj = new Cat();//可以吗?可以 Object也是 Cat的父类
        //可以调用父类中的所有成员(需遵守访问权限)
        //但是不能调用子类的特有的成员
        //因为在编译阶段,能调用哪些成员,是由编译类型来决定的
        // animal.catchMouse();错误,因为该方法没有在父类中存在,是Cat的特有方法
        //最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法
        // ,然后调用,规则我前面我们讲的方法调用规则一致。
        animal.eat();//从猫类找,猫吃鱼
        animal.run();//跑
        animal.show();//hello,你好
        animal.sleep();//睡
    }
}
多态的向下转型

目的是调用子类特有的方法

1)语法:子类类型引用名=(子类类型)父类引用;

2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须指向的是当前目标类型的对象;

4)可以调用子类类型中所有的成员;

接着上面的演示例子

我们可以通过强转来实现对猫类特定方法的访问

public class PolyDetail {
    public static void main(String[] args) {
        //向上转型:父类的引用指向了子类的对象
        // 语法:父类类型引用名= new子类类型();
        Animal animal = new Cat();
        
        animal.eat();//从猫类找,猫吃鱼
        
        //希望调用Cat的 catchMouse方法
        // 多态的向下转型
        //语法:子类类型引用名=(子类类型)父类引用;
        //编译类型Cat,运行类型Cat
        //相当于生成了一个新的引用cat也指向cat对象
        //简单理解就是"披着Cat皮的Animal"
        Cat cat = (Cat) animal;//猫抓老鼠
        //(2)要求父类的引用必须指向的是当前目标类型的对象
        //可以吗?错误
        //因为父类Animal的引用名animal指向的是子类类型Cat
        //Dog也是子类类型,两者间不能转换(因为不存在关系,是平级的)
        Dog dog = (Dog) animal;

        System.out.println("ok~~");   
    }
}
属性没有重写这种说法

属性的值直接看编译类型

public class PolyDetail02 {
    public static void main(String[] args) {
        //属性没有重写之说!属性的值看编译类型
        Base base = new Sub();//向上转型
        //看编译类型,Base,所以结果是10
        System.out.println(base.count);
    }
}
class Base{
    int count = 10;
}

class Sub extends Base{
    int count = 20;
}
instanceof

instanceof用于判断对象运行类型是否为XX类型或其子类

public class PolyDetail03 {
    public static void main(String[] args) {
        BB bb = new BB();
        System.out.println(bb instanceof BB);// true
        System.out.println(bb instanceof AA);// true
        //aa编译类型AA,运行类型是BB
        AA aa = new BB();
        System.out.println(aa instanceof AA);
        System.out.println(aa instanceof BB);
    }
}
class AA{}//父类
class BB extends AA{}//子类

练习

1.
public class PolyExercise01{
    public static void main(Stringlargs){
        double d = 13.4;//ok
        long l = (long)d; //ok
        System.out.println(l): //13
        int in = 5; //ok
        boolean b =(boolean)in;//不对,boolean -> int
        Object obj="Hello";//可以,向上转型
        String objStr = (String)obj;//可以向下转型
        System.out.println(objStr); // hello

        Object objPri = new Integer(5);//可以,向上转型
        String str = (String)objPri;//错误ClassCastExcetpion,指向Integer的父类引用,转成String
        Integer str1 = (Integer)objPri;//可以,向下转型
     }
}
2.
public class PolyExercise02 {
    public static void main(String[] args) {
        SubEx s = new SubEx();
        System.out.println(s.count);//20
        s.display();//20
        BaseEx b = s;//相当于BaseEx b = new SubEx();
        System.out.println(b == s);//T
        System.out.println(b.count);//因为属性只看编译类型,故这里为10
        b.display();//方法调用,先看运行类型,是SubEx,所以输出20
    }

}

class BaseEx {//父类
    int count = 10;

    public void display() {
        System.out.println(this.count);
    }
}

class SubEx extends BaseEx{//子类
    int count = 20;
    public void display(){
        System.out.println(this.count);
    }
}

总结

1、多态是是由java的独特设计所产生的一种精简代码的思想【因为java需要将编译与运行过程分开】

2、向上转型指父类引用子类对象【 Animal animal = new Cat(),Cat需要先继承Animal】
具体使用场景:

​ 当我们想使用父类的方法,并且希望这些方法如果有被子类重写,应该获取子类重写的值的情况

例如:父类是Animal,子类是Cat,Cat重写了父类的eat()方法,我想在使用父类方法eat()时,返回子Cat重写的eat()所获取到的值,这时需要使用向上转型

3、向下转型指父类在向上转型时,若想使用子类的特定方法,需要再建立一个新的子类引用并强制转换到父类的引用名上

Animal animal = new Cat();
Cat cat = (Cat) animal;//"披着Cat皮的Animal"

4、属性调用只看编译类型,方法调用先看运行类型

动态绑定机制

java的动态绑定机制

1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明,那里使用【按作用域走】

举例说明

public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型A,运行类型B
        A a = new B();//向上转型
        /*
         * 由于a绑定的运行类型是B,将B中sum()方法注释后,由于继承机制的影响,子类B会去找父类A的sum()方法
         * 而父类A的sum()方法又调了getI(),此时触发动态绑定机制,java去判断a绑定的的运行类型
         * 查到是B,于是去调B内的getI(),返回B中的属性i,即20
         * 给到A的sum()方法,故20+10 = 30
         */
        System.out.println(a.sum());//?40->30
        /*
        * 同理,在注释B中sum1()方法后,子类B会去找父类A的sum1()方法
        * 其返回i + 10,i没有动态绑定机制,在A中声明,就在A中使用,故返回10+10=20
        * */
        System.out.println(a.sum1());//?30->20

    }
}

class A {//父类
    public int i = 10;
    public int sum() {
        return getI() +10;
    }
    public int sum1() {
        return i + 10;
    }
    public int getI() {
        return i;
    }
}

class B extends A {//子类
    public int i = 20;
//    public int sum() {
//        return i + 20;
//    }
    public int getI(){
        return i;
    }
//    public int sum1(){
//        return i + 10;
//    }
}

多态数组

定义

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

演示

需要依次定义Person(父类)、Student和Teacher(子类)三个类

public class Person {//父类
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String say() {//返回名字和年龄
        return name + "\t" +age;
    }

}
//==================================================
public class Student extends Person{
    private double score;

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    //重写父类say()
    @Override
    public String say() {
        return super.say() + " score = " + score;
    }

    //特有的方法
    public void study() {
        System.out.println("学生" +getName() + " 正在学java.. . ");
    }

}
//==================================================
public class Teacher extends Person{
    private double sal;

    public Teacher(String name, int age, double sal) {
        super(name, age);
        this.sal = sal;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    //重写父类say()
    @Override
    public String say() {
        return super.say() + " salary = " + sal;
    }

    //特有方法
    public void teach(){
        System.out.println("老师" +getName() +"正在讲java课程...");
    }

}

测试类PloyArray

public class PloyArray {
    public static void main(String[] args) {
        //应用实例:现有一个继承结构如下:要求创建1个Person对象、
        //2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象say方法
        
        //数组的定义中有:数组中的元素可以是任何数据类型,包括基本类型和引用类型
        //这里其实是用引用类型的数据(即对象persons)组成了数组,因此多态数组也是一个数组
        Person[] persons = new Person[5];
        persons[0] = new Person("jack",20);
        persons[1] = new Student("jk",18, 100);
        persons[2] = new Student( "smith",19, 30.1);
        persons[3] = new Teacher( "scott",30,20000);
        persons[4] = new Teacher( "king" ,50,25000);
        

        //循环遍历多态数组,调用say
        for (int i = 0; i < persons.length; i++) {
            //persons[i]的编译类型是Person
            //但是运行类型根据所存放的对象动态变化,由JVM去判断
            System.out.println(persons[i].say()); //动态绑定
            //错误,因为这些方法是写在子类里面的,需要进行向下转型才能使用
            //首先先需要判断persons[i]的运行类型(方法看运行类型)
//            persons[i].teach();
//            persons[i].study();
            if(persons[i] instanceof Student){
                Student student = (Student)persons[i];//向下转型
                student.study();
                //或((Student)persons[i]).study();
            } else if (persons[i] instanceof Teacher) {
                Teacher teacher = (Teacher)persons[i];
                teacher.teach();
                //或((Teacher)persons[i]).teach();
            }else if(persons[i] instanceof Person) {

            }else {
                System.out.println("类型有误,请检查");
            }
        }
    }
}
/*
jack	20
jk	18 score = 100.0
学生jk 正在学java.. . 
smith	19 score = 30.1
学生smith 正在学java.. . 
scott	30 salary = 20000.0
老师scott正在讲java课程...
king	50 salary = 25000.0
老师king正在讲java课程...
*/

总结

数组中的元素可以由任何数据类型构成,包括基本类型引用类型。【ps:数组本身也是引用类型】

而所谓的"多态数组"就是由对象构成的一种特殊数组

其中的对象可以遵循多态的思想来使用,使得这个特殊的数组本身也具有多态的性质

多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型

应用实例

定义员工类Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。

普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法
测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual0]
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法

先分别定义Employee(父类)、Worker和Manager(子类)

public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    //得到年工资的方法
    public double getAnnual() {
        return 12 * salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
//===========================================
public class Worker extends Employee{
    public Worker(String name, double salary) {
        super(name, salary);
    }

    public void work(){
        System.out.println("普通员工" +getName() + " is working");
    }

    @Override
    public double getAnnual() {//没有额外收入直接调用就行
        return super.getAnnual();
    }
}
//===========================================
public class Manager extends Employee{
    private double bonus;

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public void manage() {
        System.out.println("经理" +getName() +" is managing");
    }
    //重写获取年薪方法
    @Override
    public double getAnnual(){
        return super.getAnnual() + bonus;
    }
}

测试类PolyParameter

public class PolyParameter {
    public static void main(String[] args) {
        Worker tom = new Worker( "tom",2500);
        Manager milan = new Manager("milan",5000,200000);
        PolyParameter polyParameter = new PolyParameter();
        // 传进去的tom与Worker绑定,因此调的是Worker中重写的getAnnual()
        polyParameter.showEmpAnnual(tom);
        //传进去的milan与Manager绑定,因此调的是Manager中重写的getAnnual()
        polyParameter.showEmpAnnual(milan);

        polyParameter.testWork(tom);
    }

    // showEmpAnnual(Employee e)
    //实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]
     public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());//动态绑定机制
     }

    //添加一个方法,testWork ,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
     public void testWork(Employee e){
         if(e instanceof Worker){
            ((Worker) e).work();//向下转型操作
         } else if (e instanceof Manager) {
             ((Manager) e).manage();
         }else {
             System.out.println("nothing");
         }
     }
}

总结

和多态数组的概念类似,所谓的"多态参数"也就是将对象作为输入形参时的特殊情况

参数中的对象可以遵循多态的思想来使用

posted @ 2022-10-23 19:58  dayceng  阅读(42)  评论(0编辑  收藏  举报