● 多【多种】态【状态】
方法或对象具有多种形态。 是建立在封装和继承之上的面向对象的第三大特征。
方法的多态
重写和重载体现多态
对象的多态(核心)
(1)一个对象的编译类型和运行类型可以不一致。
(2)编译类型在定义对象时,就确定了,不能改变。
(3)运行类型是可以变化的。
(4)编译类型看定义时 “=” 号 的左边,运行看 “=” 号的右边
例如:
Animal animal = new Dog();
【这里animal编译类型是Animal,运行类型是Dog】
animal = new Cat();
【animal的运行类型变成了Cat,编译类型仍然是Animal】
○ 多态的前提是:两个对象(类)存在继承关系。
○ 多态的向上转型
本质:父类的引用指向了子类的对象
语法:父类类型 引用名 = new 子类类型();
特点:
○ 向上转型调用方法的规则
(1)可以调用父类中的所有成员(需遵守访问权限)。
(2)但是不能调用子类的特有成员。
(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的,最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与方法调用规则一致。
以下面例子为例说明向上转型的规则
Animal类
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("表演");
}
}
Cat类,继承Animal类
class Cat extends Animal {
public void eat(){ //重写父类的eat
System.out.println("猫吃鱼");
}
public void catchMouse(){ //cat的特有方法
System.out.println("猫抓老鼠");
}
}
Test类
public class Test{
public static void mian(String[] args){
//向上转型:父类的引用指向子类的对象
//语法:父类类型引用名 = new 子类类型();
Aniaml animal = new Cat();
// Object obj = new Cat(); 也是可以的,因为Object也是Cat的父类
//调用父类中的所有成员(需遵守访问权限)
//不能调用子类的特有成员
//animal.catchMouse(); 是错误的,因为catchMouse是Cat中的特有方法
animal.eat();
animal.run();
animal.show();
animal.sleep();
System.out.println("out~");
}
}
多态的向下转型
向下转型的使用
以Aniaml类与cat类为例,新增一个dog类
Dog类
public class Dog extends Animal{
}
在Test类中测试
调用Cat的catchMouse方法
//使用多态的向下转型
//(1)语法 : 子类类型 引用名 = (子类类型) 父类引用;
Cat cat = (Cat) animal; //cat的编译类型是Cat,运行类型是Cat
cat.catchMouse();
//(2)父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog)animal; //这句会报错,提示类异常
属性没有重写之说,属性的值看编译类型。
public class Test{
public static void main(String[] args){
//属性没有重写之说,属性的值看编译类型
Base base = new Sub(); //向上转型
System.out.println(base.count); //编译类型为base,则输出10
Sub sub = new Sub();
System.out.println(base.count); //编译类型为sub,则输出20
}
}
class Base { //父类
int count = 10; //属性
}
class Sub extends Base { //子类
int count = 20; //属性
}
nstanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型。
public class Test{
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 = new BB();
System.out.println(aa instanceof AA); //true
System.out.println(aa instanceof BB); //true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
System.out.println(AA instanceof obj);//true
String str = "yes";
System.out.println(str instanceof Object);//true
}
}
Java重要特征:动态绑定机制
现在有两个类,A类与B类,其中B类是A类的子类,在main方法中创建对象: A a = new B();调用a.sum()与a.sum1()看看分别得到什么结果?
A类
class A{ //父类
public int i =10;
public int sum(){
return getI() + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
B类
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;
}
}
在main方法中 A a = new B(); //向上转型 System.out.println(a.sum()); //输出40
System.out.println(a.sum1()); //输出30
现在注释掉B类中的sum方法,那输出的结果又该如何?
B类
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;
}
}
在main中调用
System.out.println(a.sum());
因为B类中的sum被注释了,所以会自动向上找sum方法,而父类A中恰好有sum方法,所以调用了A类的sum方法,而A类的sun方法里返回的是getI()+10;而A与B类中都有getI方法,这里就涉及到了java的动态访问机制。
现在回过头去看刚才的问题,刚才调用的getI()方法,是属于对象的方法,所以该方法会和该对象的内存地址/运行类型绑定,我们创建的时候使用的运行类型是B类型,所以getI应该调用B类的getI方法:return
i;所以最后a.sum()输出的结果为:20+10 = 30。
那如果再把B类中的sum1()方法再注删掉呢?a.sum1()的结果是什么?
A类
class A{ //父类
public int i =10;
public int sum(){
return getI() + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
B类
class B extends A{ //子类
public int i = 20;
public int getI(){
return i;
}
}
由于B中并没有sum1方法,所以依然调用父类A的sum1方法,而sum1()中的 i + 10;i为属性,前面介绍过,java动态绑定机制是:当调用对象的属性时,,没有绑定动态机制,就是哪里声明,哪里使用,所以直接返回B类中的i,结果为:10 + 10 = 20
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
应用实例:
现有一个继承结构如下:要求创建1个Person对象、2个Student对象和2个Teacher对象,统一放在数组中,并调用say方法。
代码实现
Person类
public class Person { //父类
private String name;
private int age;
public String say(){ //返回名字和年龄
return name + "\t" + 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;
}
}
Student类
public class Student extends Person{
private double score;
//重写父类say
public String say(){
return "学生" + super.say() + "score =" + 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;
}
}
Teacher类
public class Teacher extends Person{
private double salary;
//重写父类的say方法
public String say(){
return "老师 :" + super.say()+"salary = " + salary;
}
public Teacher(String name, int age, double salary) {
super(name,age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
Test类(主方法类)
创建多态数组并遍历调用say方法
public class test {
// 在main中,分别创建Person和Student对象,调用say方法输出自我介绍
public static void main(String[] args) {
//创建对象数组
Person[] persons = new Person[5];
persons[0] = new Person("jack",20);
persons[1] = new Student("mary",18,100);
persons[2] = new Student("smith",19,30.1);
persons[3] = new Teacher("scott",30,20000);
persons[4] = new Teacher("queen",29,25000);
//循环遍历多态数组,调用say
for(int i = 0; i < persons.length; i++){
//这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断
System.out.println(persons[i].say());//动态绑定机制
}
}
}
运行效果
现在升级需求:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study怎么调用?
含特有方法的Teacher
public class Teacher extends Person{
private double salary;
//重写父类的say方法
public String say(){
return "老师 :" + super.say()+"salary = " + salary;
}
//teacher特有方法
public void teach(){
System.out.println("老师:" +getName() + "正在授课");
}
public Teacher(String name, int age, double salary) {
super(name,age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
含特有方法的Student
public class Student extends Person{
private double score;
//重写父类say
public String say(){
return "学生" + super.say() + "score =" + score;
}
public void study(){
System.out.println("学生:" + getName() + "正在上课");
}
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;
}
}
Test类
通过向下转型与类型判断完成子类特有方法的调用。
public class test {
// 在main中,分别创建Person和Student对象,调用say方法输出自我介绍
public static void main(String[] args) {
//创建对象数组
Person[] persons = new Person[5];
persons[0] = new Person("jack",20);
persons[1] = new Student("mary",18,100);
persons[2] = new Student("smith",19,30.1);
persons[3] = new Teacher("scott",30,20000);
persons[4] = new Teacher("queen",29,25000);
//循环遍历多态数组,调用say
for(int i = 0; i < persons.length; i++){
//这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断
System.out.println(persons[i].say());//动态绑定机制
//使用 类型判断 + 向下转型
if(persons[i] instanceof Student){ //判断person[i] 的运行类型是不是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("类型有误,请检查清楚!");
}
}
}
}
运行效果
多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。
应用案例
定义员工类Employee,包含姓名和月工资【private】,以及计算年工资getAnnual的方法。普通员工和经理继承了员工。
经理类多了奖金bonus属性和管理manage方法
普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。
测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法【e.getAnnual()】
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法。
代码实现
员工类Employee
public class Employee {
private String name;
private double salary;
public double getAnnual(){
return 12 * salary;
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
manage类
public class manger extends Employee{
private double bonus;
//获取年薪
public double getAnnual(){
return super.getAnnual() + bonus;
}
public void manage(){
System.out.println("经理: "+ getName()+"is working");
}
public manger(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;
}
}
普通员工类(Ordinary_employees)
public class Ordinary_employees extends Employee{
public void work(){
System.out.println("普通员工 " +getName() +"is working");
}
public double getAnnual(){ //因为普通员工没有其他收入,则直接调用父类方法就可以
return super.getAnnual();
}
public Ordinary_employees(String name,double salary){
super(name,salary);
}
}
test类
创建普通员工与经理的对象,添加两个新方法并输出需求。
public class test {
// 在main中,分别创建Person和Student对象,调用say方法输出自我介绍
public static void main(String[] args) {
Ordinary_employees tom = new Ordinary_employees("tom",2500);
manger milan = new manger("milan",5000,250000);
test t = new test();
t.showEmpAnnual(tom);
t.showEmpAnnual(milan);
t.testWork(tom);
t.testWork(milan);
}
//添加一个方法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 Ordinary_employees){
((Ordinary_employees)e).work();//向下转型
}else if(e instanceof manger){
((manger)e).manage(); //向下转型
} else {
}
}
效果实现