一、多态(难点、重点)
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;
}
}