JAVA面向对象(下)(二、多态,抽象类,关键字)

一、多态(难点、重点)

1.0 需求

需求:

  • 声明一个Animal(动物)类,

    • 包含方法 public void eat()方法,输出:吃东西。

  • 声明一个子类Dog(狗)类,

    • 重写 public void eat()方法,输出:啃骨头。

    • 并且增加一个新方法:public void watchHouse(),输出:看家。

  • 声明一个子类Cat(猫)类,

    • 重写 public void eat()方法,输出:吃鱼。

    • 并且增加一个新方法:public void catchMouse(),输出:抓老鼠。

  • 声明一个子类Pig(猪)类

    • 重写 public void eat()方法,输出:啥都吃。

    • 并且增加一个新方法:public void sleep(),输出:睡觉。

Animal父类

​
package com.atguigu.duotai;

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

Dog子类

package com.atguigu.duotai;

public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("啃骨头");
    }
    public void watchHouse(){
        System.out.println("看家");
    }
}

Cat子类

package com.atguigu.duotai;

public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
    public void catchMouse(){
        System.out.println("抓老鼠");
    }
}

Pig子类

package com.atguigu.duotai;

public class Pig extends Animal{
    @Override
    public void eat() {
        System.out.println("啥都吃");
    }

    public void sleep(){
        System.out.println("睡觉");
    }
}

1.1 问题1

  • 声明一个Home类,

    • 含一个方法 public static void look(形参):看宠物吃东西,为了能够接受各种宠物对象,形参的类型设计为Animal

  • 声明一个测试类TestHome

    • 在main方法里调用Home类的look方法,传入不同宠物对象

Home类

public class Home {
/*    public static void look(Dog p){
        p.eat();
    }
    public static void look(Cat p){
        p.eat();
    }
    public static void look(Pig p){
        p.eat();
    }*/
    //重载可以实现,但是麻烦,需要为每一个子类都单独设计一个方法
    
    //下面这个写法,可以用1个形参类型,接收各种实参类型的对象,当然要求这些类型都是它的子类
    //这里Animal的变量p就具有多种形态,可以是Dog对象,可以是Cat对象,可以是Pig对象
    public static void look(Animal p){
        p.eat();
        /*
        根据实参对象的不同,或者根据p对象的实际的形态,来调用各自重写的eat方法。
        **/
    }
}

测试类

package com.atguigu.duotai;

public class TestHome {
    public static void main(String[] args) {
        Dog d = new Dog();
        Cat c = new Cat();
        Pig p = new Pig();

        Home.look(d);
        Home.look(c);
        Home.look(p);
    }
}
/*
啃骨头
吃鱼
啥都吃
*/

1.2 问题2

需求:

  • 声明一个测试类TestAnimalArray,创建一个数组,可以装所有宠物对象

TestAnimalArray类

package com.atguigu.duotai;

public class TestAnimalArray {
    public static void main(String[] args) {
        //声明一个数组,可以装各种宠物的对象
/*        Dog[] dogs = new Dog[3];
        dogs[0] = new Dog();
        dogs[1] = new Cat();//错误
        dogs[2] = new Pig();//错误*/

        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Pig();
        
        //animals[下标]有多种形态,可能是Dog对象,可能是Cat对象,可能是Pig对象
        for (int i = 0; i < animals.length; i++) {
            animals[i].eat();
        }
    }
}

1.3 多态的好处

  • 让程序员编写代码更灵活了。

1.4 什么是多态

多态是指一个变量多种形态。

Animal a = new Dog(); //新名词:多态引用,是指一个父类类型的变量,指向子类的对象。

变量a的编译时类型是Animal类型,a的运行时类型是Dog类型,它是两种形态。

public static void main(String[] args) {
        //声明一个数组,可以装各种宠物的对象
        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Pig();
        
        //animals[下标]有多种形态,可能是Dog对象,可能是Cat对象,可能是Pig对象
        for (int i = 0; i < animals.length; i++) {
            animals[i].eat();
        }
    }

元素变量animals[下标]的编译时类型是Animal类型,animals[下标]的运行时类型,可能是Dog对象,可能是Cat对象,可能是Pig对象。它是多种形态。

public static void look(Animal p){
    p.eat();
}

形参变量p的编译时类型是Animal类型。p的运行时类型可能是Dog类型,可能是Cat类型,可能是Pig类型。它是多种形态。

package com.atguigu.duotai;

public class TestAnimal {
    public static void main(String[] args) {
        Animal a = new Dog(); //多态引用
        //a变量有两种形态
        //a变量的编译时类型是Animal
        //a变量的运行时类型是Dog

        a.eat();
//        a.watchHouse();//编译报错。编译时a看左边,只能看到声明a的类型是Animal。看不到右边new的类型
        //上面这么写代码,就会失去调用 Dog子类扩展的方法的能力
        //上面的语句,执行的结果是 啃骨头,即执行的是Dog子类重写的eat方法,运行时看右边,看子类的代码。
    }
}

1.5 多态引用的相关问题

父类类型  变量名 = 子类对象; //变量名,也是对象名

1、多态引用后,对象名.方法执行的问题

原则:编译时看左边(父类类型),运行时看右边(子类类型)

  • 只能调用父类声明的方法,编译才能通过。

  • 运行时执行的是子类“重写”的方法体。如果子类没有重写,那么仍然执行父类中找到的方法。

package com.atguigu.duotai;

public class TestAnimal {
    public static void main(String[] args) {
        Animal a = new Dog(); //多态引用
        //a变量的编译时类型是Animal
        //a变量的运行时类型是Dog

        a.eat();
//        a.watchHouse();//编译报错。编译时a看左边,只能看到声明a的类型是Animal。看不到右边new的类型
        //上面的语句,执行的结果是 啃骨头,即执行的是Dog子类重写的eat方法,运行时看右边,看子类的代码。
    }
}

2、多态引用的动态绑定机制

多态引用的动态绑定机制是针对==方法调用==来说的。

3、多态引用后,对象名.成员变量的引用

原则:只看编译时类型。

package com.atguigu.duotai;

public class Father {
    int a = 1;
}
package com.atguigu.duotai;

public class Son extends Father{
    int a = 2;
}

 

package com.atguigu.duotai;

public class TestSon {
    public static void main(String[] args) {
        Father f = new Son();//多态引用
        //f.a这个引用方式,没有编译时看左边,运行时看右边的 原则。只有一个原则,看编译时类型。
        //f的编译时类型Father,只看Father类中a
        System.out.println("f.a = " + f.a);//f.a = 1

        System.out.println("===================");
        Son s = new Son();//不是多态引用。如果非要给这种形式取个名字,你可以叫它本态引用。
        //s的编译时类型Son,s.a就看Son类
        System.out.println("s.a = " + s.a);//s.a = 2
    }
}

4、多态引用带来的问题

多态引用后,这个变量只能调用父类声明的成员,不能再调用子类“扩展”的成员。

多态引用,只能操作所有子类的“共同”特征,即父类中声明的成员。

1.6 练习题1

(1)父类Graphic图形

  • public double area()方法:返回0.0

  • public double perimeter()方法:返回0.0

  • 重写toString()方法,返回图形面积和图形周长

(2)子类Circle圆继承Graphic图形

  • 包含属性:radius,属性私有化

  • 包含get/set方法

  • 重写area()求面积方法

  • 重写perimeter()求周长方法

  • 重写toString()方法,返回圆的半径,面积和周长

(3)子类矩形Rectangle继承Graphic图形

  • 包含属性:length、width,属性私有化

  • 包含get/set方法

  • 重写area()求面积方法

  • 重写perimeter()求周长方法

  • 重写toString()方法,返回长和宽,面积、周长信息

(4)在测试类的main方法中创建多个圆和矩形对象放到Graphic[]类型的数组中,并按照面积从小到大排序输出。

Graphic父类

package com.atguigu.exer1;

/*
(1)父类Graphic图形
- public double area()方法:返回0.0
- public double perimeter()方法:返回0.0
- 重写toString()方法,返回图形面积和图形周长

问题:
    为什么Graphic类中要提供这两个方法,它们似乎看起来一点用没有?
    因为看起来返回0.0没有意义。

    答案:
    (1)如果这里不写area() 和  perimeter(),那么这个类的toString()就无法调用 area() 和  perimeter(),
    (2)父类应该代表所有子类“共同”的特征,所有图形子类都有求面积,求周长的功能,那么父类就应该有这个方法,
    才能体现图形这个事物的共同特征。
 */
public class Graphic {
    public double area(){
        return 0.0;
    }
    public double perimeter(){
        return 0.0;
    }

    @Override
    public String toString() {
        return "面积:" + area() +",周长:" + perimeter();
        //return "面积:" + this.area() +",周长:" + this.perimeter();


    }
    /*
    思考题2:
        这里area()方法和 perimeter()执行的一定是上面的 return 0.0;吗?
        答案:不一定。
        要看 this 当前对象是谁,当前对象是Graphic类new的对象,那么执行的是上面的return 0.0;
        要看 this 当前对象是谁,当前对象是Rectangle类new的对象,那么执行的是Rectangle类重写的area()
        要看 this 当前对象是谁,当前对象是Circle类new的对象,那么执行的是Circle类重写的area()


        这句代码中有动态绑定?
          这里隐含了一个变量,就是this变量。
          this变量的编译时类型此时是 Graphic,
          this变量的运行时类型可能是Circle,可能是Rectangle
     */
}

Circle子类

package com.atguigu.exer1;

public class Circle extends Graphic{
    private double radius;//半径

    public Circle() {
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return Math.PI * radius * 2;
    }

    @Override
    public String toString() {
        return "半径:"+ radius + "," + super.toString();
        /*
        super.toString()代表的是Graphic类中的toString方法的代码, "面积:" + area() +",周长:" + perimeter();
         */
    }
}

Rectangle子类

package com.atguigu.exer1;

public class Rectangle extends Graphic{
    private double length;//长
    private double width;//宽

    public Rectangle() {
    }

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }

    @Override
    public double perimeter() {
        return 2 * (length  + width);
    }

    @Override
    public String toString() {
        return "长:" + length +",宽:" + width + "," +   super.toString();
        /*
        super.toString()代表的是Graphic类中的toString方法的代码, "面积:" + area() +",周长:" + perimeter();
         */
    }
}

测试类1TestGraphic

package com.atguigu.exer1;

public class TestGraphic {
    public static void main(String[] args) {
        Rectangle r = new Rectangle(6,2);
        r.setLength(9);
        System.out.println(r);
        //当我们打印对象时,会自动调用r.toString()

        Circle c = new Circle(2.5);
        System.out.println(c);

        Graphic g = new Rectangle(7,3);
//        g.setLength(9);//错误,因为setLength方法是子类Rectangle定义,父类没有
        System.out.println(g);
        //编译时看左边,看Graphic
        //运行时看右边,看Rectangle
        /*
        总结:如果调用大家都有的方法(父类,子类都有的方法)
        Rectangle r = new Rectangle(6,2);
        Graphic g = new Rectangle(7,3);
        没有区别。
        如果想要调用父类无,子类有的方法,那么只能这么写。
        Rectangle r = new Rectangle(6,2);
         */
    }
}

测试类2TestGraphicArray

package com.atguigu.exer1;

public class TestGraphicArray {
    public static void main(String[] args) {
        Graphic[] graphics = new Graphic[4];//这一行看没有多态引用
        graphics[0] = new Circle(2.5);//这一行有多态引用
        //左边 graphics[0]的类型是 Graphic类型
        //右边 new 的是Circle类型
        //父类类型的元素变量 指向了 子类对象
        graphics[1] = new Rectangle(5,2);
        graphics[2] = new Rectangle(3,2);
        graphics[3] = new Circle(3.0);

        //遍历数组
        for (int i = 0; i < graphics.length; i++) {
            System.out.println(graphics[i]);//自动调用toString
            //方法的调用有没有动态绑定?
            /*
            有: graphics[i]编译时是Graphic类型,
                graphics[i]运行时可能是Circle,可能是Rectangle,会根据new的类型动态的去找它们重写的toString方法。
             */
        }

        //排序
        System.out.println("按照面积从小到大排序");
        /*
        无论是冒泡还是选择排序,n个元素,都是需要n-1轮。
        这里是最基础的选择排序。
         */
        for(int i=0; i<graphics.length-1; i++){
            for(int j=i+1; j<graphics.length; j++){
                if(graphics[i].area() > graphics[j].area()){
                    Graphic temp = graphics[i];
                    graphics[i] = graphics[j];
                    graphics[j] = temp;
                }
                //方法的调用有没有动态绑定?
            /*
            有: graphics[i]编译时是Graphic类型,
                graphics[i]运行时可能是Circle,可能是Rectangle,会根据new的类型动态的去找它们重写的area方法。
             */
            }
        }

        //输出排序结果
        for (int i = 0; i < graphics.length; i++) {
            System.out.println(graphics[i]);//自动调用toString
        }
    }
}

必备技能

  • Debug分析代码走到哪里了

  • 会分析某个变量的编译时类型是什么,运行时类型是什么

1.7 向上转型与向下转型

1.7.1 为什么要转换

多态引用让某个变量==编译时==看左边,按父类类型处理,操作的是所有子类“共同的”特征,即编译时不能操作子类“特有的、扩展的”成员。

现在我们想要在“编译时”,能够操作子类“特有的、扩展的”成员,怎么办?

解决办法:向下转型,即从父到子的转换过程。

那么与向下转型对应的操作,就是向上转型,即从子到父的过程。

注意:无论是向上转型,还是向下转型都是针对“编译时类型”来说,对象的运行时类型,即实际new的类型从头到尾没有发生过改变。

package com.atguigu.cast;

import com.atguigu.duotai.Animal;
import com.atguigu.duotai.Dog;

public interface TestClassCast {
    public static void main(String[] args) {
        Animal a = new Dog();//向上转型,从子到父
        a.eat();//只能调用Animal中声明的,大家共有的方法

        Dog d = (Dog) a;//向下转型,从父到子
        d.eat();//调用共同的方法
        d.watchHouse();//调用子类特有的方法
    }
}

1.7.2 向下转型有没有风险?

当对象的运行时类型,不符合强制向下转型后的类型,就会发生ClassCastException类型转换异常。

想要向下转型运行成功,有一个要求:对象的运行时类型 必须 < 或 = 向下转型后的类型。

package com.atguigu.cast;

import com.atguigu.duotai.Animal;
import com.atguigu.duotai.Dog;

public class TestClassCast2 {
    public static void main(String[] args) {
        Animal a = new Dog();//向上转型,从子到父

        Dog d = (Dog) a;//(1)
//        Cat c = (Cat) a;//(2)
//        Pig p = (Pig) a;//(3)
        //上面的代码,编译都通过了。
        //因为编译器认为 Animal是Dog,Cat,Pig的父类, 从父到子的向下转型都是OK的。
        //但是上面的3句代码,运行时(2)(3)都会报错
        //因为a变量的运行时类型是 Dog,那么Dog类型的对象是不能赋值给Cat或Pig类型的变量,运行时类型校验就会报错
        //所以会发生运行时异常ClassCastException类型转换异常
    }
}

 

package com.atguigu.cast;

import com.atguigu.duotai.Dog;

public class Husky extends Dog {//哈士奇,它是Dog类型的一个分支

    @Override
    public void eat() {
        System.out.println("吃狗粮");
    }

    public void faDai(){
        System.out.println("发呆");
    }
}
package com.atguigu.cast;

import com.atguigu.duotai.Animal;
import com.atguigu.duotai.Dog;

public class TestClassCast2 {
    public static void main(String[] args) {
        Animal a = new Dog();//向上转型,从子到父

        Dog d = (Dog) a;//(1)
        //a的运行时类型是Dog,a向下转型后的类型Dog,Dog = Dog类型

//        Cat c = (Cat) a;//(2)
        //a的运行时类型是Dog,a向下转型后的类型Cat,  Dog与Cat之间没有大小关系,因为它们之间没有父子类关系
//        Pig p = (Pig) a;//(3)
        //上面的代码,编译都通过了。
        //因为编译器认为 Animal是Dog,Cat,Pig的父类, 从父到子的向下转型都是OK的。
        //但是上面的3句代码,运行时(2)(3)都会报错
        //因为a变量的运行时类型是 Dog,那么Dog类型的对象是不能赋值给Cat或Pig类型的变量,运行时类型校验就会报错
        //所以会发生运行时异常ClassCastException类型转换异常

        System.out.println("==================");
        Animal a2 = new Husky();//多态引用,也是向上转型,从子类Husky类型到Animal类
        Dog d2 = (Dog) a2;//向下转型,从a2原来的Animal类型到现在的Dog类型,从父到子
        //a2的运行时类型是Husky,a2向下转型后的类型Dog,Husky < Dog类型
    }
}

 

1.7.3 instanceof关键字

能不能提前避免向下转型可能发生的异常,不要等到运行时报错才发现问题呢?

解决办法:在向下转型之前,用instanceof判断。

package com.atguigu.cast;

import com.atguigu.duotai.Animal;
import com.atguigu.duotai.Cat;
import com.atguigu.duotai.Dog;
import com.atguigu.duotai.Pig;

public class Home {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Pig pig = new Pig();
        Husky husky = new Husky();

        look(dog);
        System.out.println("=========");
        look(cat);
        System.out.println("=========");
        look(pig);
        System.out.println("=========");
        look(husky);
    }

    public static void look(Animal a){
        a.eat();

        //当a指向的是Dog对象或Dog的子类对象,if(a instanceof Dog)就会成立
        //当a指向的是Cat对象或Cat的子类对象,if(a instanceof Cat)就会成立
        //当a指向的是Pig对象或Pig的子类对象,if(a instanceof Pig)就会成立
        if(a instanceof Husky){
            Husky h = (Husky) a;
            h.faDai();
        }else if(a instanceof Dog) {
            Dog d = (Dog) a;
            d.watchHouse();
        }else if(a instanceof Cat) {
            Cat c = (Cat) a;
            c.catchMouse();
        }else if(a instanceof Pig) {
            Pig p = (Pig) a;
            p.sleep();
        }
    }
}

1.8 练习题2

(1)声明一个父类Employee员工类型,

  • 有姓名属性,私有化,提供get/set方法

  • 提供有参构造public Employee(String name)

  • public double earning():代表实发工资,返回0.0

  • 重写public String toString():显示姓名和实发工资

(2)声明MyDate类型

  • 有int类型的年,月,日属性,私有化,提供get/set方法

  • 提供有参构造public MyDate(int year, int month, int day)

  • 重写public String toString(),返回“xxxx年xx月xx日”

(3)声明一个子类SalaryEmployee正式工,继承父类Employee

  • 增加属性,double类型的薪资,MyDate类型的出生日期,私有化,提供get/set方法

  • 提供有参构造public SalaryEmployee(String name, double salary, MyDate birthday)

  • ==手动==提供有参构造public SalaryEmployee(String name,double salary, int year, int month ,int day)

  • 重写方法,public double earning()返回实发工资, 实发工资 = 薪资

  • 重写public String toString():显示姓名和实发工资、生日

(4)声明一个子类HourEmployee小时工,继承父类Employee

  • 有属性,double类型的工作小时数和每小时多少钱

  • 提供有参构造public HourEmployee(String name, double moneyPerHour)

  • 提供有参构造public HourEmployee(String name, double moneyPerHour, double hour)

  • 重写方法,public double earning()返回实发工资, 实发工资 = 每小时多少钱 * 小时数

  • 重写public String toString():显示姓名和实发工资,时薪,工作小时数

(5)声明一个子类Manager经理,继承SalaryEmployee

  • 增加属性:奖金比例,私有化,提供get/set方法

  • 提供有参构造public Manager(String name, double salary, MyDate birthday, double bonusRate)

  • ==手动==提供有参构造public Manager(String name,double salary, int year, int month ,int day, double bonusRate)

  • 重写方法,public double earning()返回实发工资, 实发工资 = 薪资 *(1+奖金比例)

  • 重写public String toString():显示姓名和实发工资,生日,奖金比例

(6)声明一个员工数组,存储各种员工,你现在是人事,遍历查看每个人的详细信息,并统计实发工资总额,通知财务准备资金。

(7)从键盘输入当期月份值,如果他是正式工(包括SalaryEmployee和Manager),并且是本月生日的,通知领取生日礼物。

父类Employee(员工)

package com.atguigu.exer2;

public class Employee {
    private String name;

    public Employee() {
    }

    public Employee(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public double earning(){
        return 0.0;
    }

    @Override
    public String toString() {
        return "姓名:" + name +",实发工资:" + earning();
    }
}

日期类型MyDate

package com.atguigu.exer2;

public class MyDate {
    private int year;
    private int month;
    private int day;

    public MyDate() {
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}

子类SalaryEmployee(正式工)

package com.atguigu.exer2;

public class SalaryEmployee extends Employee{//SalaryEmployee正式工
    private double salary;
    private MyDate birthday;

    public SalaryEmployee() {
    }

    //这个构造器,要求在测试类中new好MyDate对象,传过来
    public SalaryEmployee(String name, double salary, MyDate birthday) {
        super(name);
        this.salary = salary;
        this.birthday = birthday;
    }

    //这个构造器,要求在测试类中把year,month,day传过来就可以,在这里面new对象
    public SalaryEmployee(String name, double salary, int year, int month,int day) {
        super(name);
        this.salary = salary;
        this.birthday = new MyDate(year,month,day);
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public double earning() {
        return salary;
    }

    @Override
    public String toString() {
        return super.toString() +",生日:" + birthday;
    }
}

子类HourEmployee(小时工)

package com.atguigu.exer2;

public class HourEmployee extends Employee{//HourEmployee小时工
    private double hours;
    private double moneyPerHour;//每小时多少钱

    public HourEmployee() {
    }

    public HourEmployee(String name, double moneyPerHour) {
        super(name);
        this.moneyPerHour = moneyPerHour;
    }

    public HourEmployee(String name, double hours, double moneyPerHour) {
        super(name);
        this.hours = hours;
        this.moneyPerHour = moneyPerHour;
    }

    public double getHours() {
        return hours;
    }

    public void setHours(double hours) {
        this.hours = hours;
    }

    public double getMoneyPerHour() {
        return moneyPerHour;
    }

    public void setMoneyPerHour(double moneyPerHour) {
        this.moneyPerHour = moneyPerHour;
    }

    @Override
    public double earning() {
        return moneyPerHour  * hours;
    }

    @Override
    public String toString() {
        return super.toString() +",时薪:" + moneyPerHour +",工作的小时数:" + hours;
    }
}

子类Manager(经理)

package com.atguigu.exer2;

public class Manager extends SalaryEmployee{
    private double bonus;

    public Manager() {
    }

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

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

    public double getBonus() {
        return bonus;
    }

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

    @Override
    public double earning() {
        return getSalary() * (1+bonus);
    }

    @Override
    public String toString() {
        return super.toString() +",奖金比例:" + bonus;
    }
}
测试类
package com.atguigu.exer2;

import java.util.Scanner;

public class TestEmployee {
    public static void main(String[] args) {
        //声明一个员工数组,存储各种员工,你现在是人事,遍历查看每个人的详细信息,并统计实发工资总额,通知财务准备资金。
        /*
        为了能把多种员工对象放到同一个数组中,所以这里声明的数组类型只能是父类Employee类型
         */
        Employee[] all = new Employee[4];
        //当我们把每一种员工对象,放到all数组中时,就会发生“向上转型”操作,因为多态引用发生了
        all[0] = new SalaryEmployee("张三",15000, new MyDate(1995,9,6));
        //多态引用, all[0]的编译时类型是Employee,给它赋值的是子类对象,即new的是子类SalaryEmployee对象

        all[1] = new SalaryEmployee("李四",14000,1998,8,6);
        all[2] = new HourEmployee("王五",200, 80);
        all[3] = new Manager("方鹏",18000,1992,8,1,0.03);

        //上面的代码中,为了把不同子类的对象放到一个数组中,所以“不得不”发生向上转型
        double sum = 0;
        for (int i = 0; i < all.length; i++) {
            System.out.println(all[i]);//操作大家共同的方法 toString()

            sum += all[i].earning();//操作大家共同的方法 earning()
        }
        System.out.println("总的薪资:" + sum);

        System.out.println("================================");
        //(7)从键盘输入当期月份值,如果他是正式工(包括SalaryEmployee和Manager),并且是本月生日的,通知领取生日礼物。
        Scanner input = new Scanner(System.in);

        System.out.print("请输入当前月份值:");
        int month = input.nextInt();

        //遍历数组,判断哪些员工是 正式工,只有正式工才有生日属性
        //只要员工的类型是 SalaryEmployee和Manager,就是正式工
        for (int i = 0; i < all.length; i++) {
            //不管all[i]是SalaryEmployee还是Manager, if(all[i] instanceof SalaryEmployee)都会满足
            if(all[i] instanceof SalaryEmployee){
                //all[i].getBirthday();//报错,因为all[i]现在编译时类型是Employee
                //为了调用SalaryEmployee及其子类特有的getBirthday方法,父类Employee没有的方法,
                //就必须向下转型
                SalaryEmployee s = (SalaryEmployee) all[i];
                if(s.getBirthday().getMonth() == month){
                    System.out.println(s.getName()+"今天生日,请领取生日礼物");
                }
            }
        }

        input.close();
    }
}

1.9 虚方法(为了做一些面试题)

虚方法:在Java中凡是可以被子类重写的方法都是虚方法。只要是虚方法,在多态引用时,都使用动态绑定机制。

父类的类型 变量名 = 子类对象;
变量名.虚方法(...);

虚方法调用时,要注意:

(1)编译时:静态委派

此时变量先看左边,即看父类,在父类中寻找可以匹配的方法。

  • 先找最匹配的。实参的编译时类型与形参的类型完全一致。

  • 如果没有最匹配的,就找可以兼容的。实参的编译时类型 小于 形参的类型。

(2)运行时:动态绑定机制

此时变量要看右边,即看子类。在子类中寻找重写刚刚匹配的方法。

  • 如果找到了,就执行重写后的方法。

  • 如果没找到,就仍然执行刚刚匹配的方法。

示例代码

Father类、Son类、Daughter类
package com.atguigu.mianshi;

public class Father {
}

package com.atguigu.mianshi;

public class Son extends Father{
}
package com.atguigu.mianshi;

public class Daughter extends Father{
}

 

MyClass类、MySub类
package com.atguigu.mianshi;

class MyClass {
    public void method(Father f) {
        System.out.println("father");
    }

    public void method(Son s) {
        System.out.println("son");
    }
}

 

package com.atguigu.mianshi;

public class MySub extends MyClass{
    //是重写。方法名称相同,形参列表也相同(类型、个数、顺序),和形参名无关
    public void method(Father d) {
        System.out.println("sub--father");
    }
    //是重载。方法名相同,形参列表不同(类型不同)
    public void method(Daughter d) {
        System.out.println("daughter");
    }
}
测试类
package com.atguigu.mianshi;

public class Test {
    public static void main(String[] args) {
        Father f = new Father();
        Son s = new Son();
        Daughter d = new Daughter();

        MyClass my = new MySub();//多态引用
        my.method(f);//sub--father
        /*
        (1)编译是看my变量的左边,my变量的左边是MyClass类型。在MyClass中寻找匹配的方法。
        实参f的编译时类型是Father。
        形参是method(Father f)  和 method(Son s)
        f实参与 method(Father f) 最匹配。
        (2)运行是看my变量的右边,my量的右边是MySub。在MySub类中寻找重写的方法。
        找到了对刚刚匹配的method(Father f)方法的重写
        public void method(Father d) {
            System.out.println("sub--father");
        }
        执行重写的代码
         */

        my.method(s);//son
        /*
        (1)编译是看my变量的左边,my变量的左边是MyClass类型。在MyClass中寻找匹配的方法。
        实参s的编译时类型是Son。
        形参是method(Father f)  和 method(Son s)
        s实参与 method(Son s) 最匹配。
        (2)运行是看my变量的右边,my量的右边是MySub。在MySub类中寻找重写的方法。
        有没有对 method(Son s) 方法的 重写呢?没有
        没有就仍然执行刚刚匹配的方法
         */

        my.method(d);//sub--father
        /*
        (1)编译是看my变量的左边,my变量的左边是MyClass类型。在MyClass中寻找匹配的方法。
        实参d的编译时类型是Daughter。
        形参是method(Father f)  和 method(Son s)
        d与 method(Father f) 兼容,因为 Daughter < Father
        (2)运行是看my变量的右边,my量的右边是MySub。在MySub类中寻找重写的方法。
        有没有对刚刚匹配的method(Father f) 进行重写,有,就执行重写的代码。
         public void method(Father d) {
            System.out.println("sub--father");
        }
         */


        System.out.println("===================");
        Father f2 = new Son();
        my.method(f2);//sub-father
        /*
        (1)编译是看my变量的左边,my变量的左边是MyClass类型。在MyClass中寻找匹配的方法。
        实参f2的编译时类型是Father。
        形参是method(Father f)  和 method(Son s)
        f2与 method(Father f) 匹配
        (2)运行是看my变量的右边,my量的右边是MySub。在MySub类中寻找重写的方法。
        有没有对刚刚匹配的method(Father f) 进行重写,有,就执行重写的代码。
         public void method(Father d) {
            System.out.println("sub--father");
        }
         */
    }
}

二、抽象类

2.1 什么是抽象类

在Java中用abstract修饰的类,就称为抽象类。

【其他修饰符】 abstract class 类名{
    
}

2.2 抽象类有什么特点

1、抽象类不能直接new对象

只能创建它非抽象的子类对象。

package com.atguigu.chouxiang;

public class TestDemo {
    public static void main(String[] args) {
        Demo d = new Demo();//报错
        //抽象类不能直接new对象
    }
}

2、抽象类中可以包含抽象方法

如果类是非抽象类,就不能包含抽象方法。换句话说,包含抽象方法的类,必须是抽象类。

如果子类继承了抽象类,子类本身不是抽象类的话,就必须重写抽象方法。

所谓的抽象方法:

【其他修饰符】 abstract class 类名{
    【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
    //抽象方法没有方法体    
}

3、抽象类也可以没有抽象方法(了解)

此时目的只有1个,希望你创建它子类的对象,该父类类型只是作为多态引用的一种类型而已。

2.3 为什么要用抽象类?

案例:

  • 编写父类Graphic图形

    • public double area()方法:返回0.0

    • public double perimeter()方法:返回0.0

发现父类在area()和 perimeter()方法里面return 0.0,一点意义都没有。所以干脆把area()和perimeter()方法声明为抽象方法。

Java要求包含抽象方法的类,必须是抽象类。

package com.atguigu.chouxiang;

public abstract class Graphic {
    public abstract double area();
    public abstract double perimeter();

    @Override
    public String toString() {
        return "面积:" + area() +",周长:" + perimeter();
    }
}

4.4 抽象类有构造器吗?

答案:一定有。只是它的构造器不是用来创建抽象类本身的对象的,而是给子类用的。

package com.atguigu.chouxiang;

public abstract class Father {
    private String name;

    public Father() {
    }

    public Father(String name) {
        this.name = name;
    }
}
package com.atguigu.chouxiang;


public class Son extends Father {
    public Son() {
        super();
    }

    public Son(String name) {
        super(name);
    }
}

 

三、关键字final

final的意思:最终的,不可更改的。

final可以用于修饰类、方法、变量。

3.1 final修饰类

final修饰的类,不能被继承,比喻:太监类。

咱们常见的String,Math,System等都是final类的代表。

打开一个类的源码的快捷键,Ctrl + n

这种类都非常重要,非常基础,不允许程序员随意继承,因为继承意味着方法可能被重写,功能可能被扩展。这些类太重要,它们是整个Java程序的基石,不允许有任何不确定的因素。

3.2 final修饰方法

final修饰的方法:不允许被重写。子类可以继承来使用,但是不能重写。

3.3 final修饰变量

final修饰的变量:它的值不允许被修改,不能被重新赋值。

final可以修饰局部变量,也可以修饰成员变量。final修饰的变量,都必须手动初始化。因为后期它的值不允许修改了,所以要把值确定下来。

对于final属性来说,可以通过有参构造进行初始化。

无论是静态变量,还是实例变量,加了final之后,都没有set方法。

package com.atguigu.keyword;

public class TestVariable {
    public static void main(String[] args) {
         final double pi = 3.14; //final修饰局部变量
       //  pi = 3.15;//不允许重新赋值
    }
}
package com.atguigu.keyword;

public class Account {
    private static final double rate = 0.035;//final修饰静态变量
    private final String  id;//final修饰实例变量,即属性
    private double balance;

    public Account() {
        id = "111111";//除非你这里给id指定一个 明确的默认值,否则无参构造没有意义
    }

    public Account(String id, double balance) {
        this.id = id;
        this.balance = balance;
    }

    public static double getRate(){
        return rate;
    }

    public String getId() {
        return id;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值