问:
什么是多态,形成多态的条件是什么?
引入
多态是继封装、继承之后,面向对象的第三大特性。
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
定义
- 多态: 是指同一行为,对于不同的对象具有多个不同表现形式。
- 程序中多态: 是指同一方法,对于不同的对象具有不同的实现.
- 例如: 吃饭
- 狗: 吃骨头
- 猫:猫吃鱼
前提条件【重点】
- 继承或者实现【二选一】
- 父类引用指向子类对象【格式体现】 Animal anl = new Dog(); A a = new Imp(😉
- 方法的重写【意义体现:不重写,无意义】
实现多态
多态的体现:父类的引用指向它的子类的对象:
父类类型 变量名 = new 子类对象;
变量名.方法名();
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
class Animal{}
class Cat extends Animal{}
class Dog extends Animal{}
clsss Person{}
//测试类:
main(){
Animal a1 = new Cat();
Animal a2 = new Dog();
Animal a3 = new Person();//编译错误,没有继承关系。
}
多态的表现形式:
-
普通父类多态
public class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
} -
抽象父类多态
public abstract class Fu{} public class Zi extends Fu{} public class Demo{ public static void main(String[] args){ Fu f = new Zi();//左边是一个“父类” } }
-
父接口多态
public interface A{}
public class AImp implements A{}
public class Demo{
public static void main(String[] args){
A a = new AImp();
}
}
多态时访问成员的特点—重点
- 多态时成员变量的访问特点
- 编译看左边,运行看左边
- 简而言之:多态的情况下,访问的是父类的成员变量
- 编译看左边,运行看左边
- 多态时成员方法的访问特点
- 非静态方法:编译看左边,运行看右边
- 简而言之:编译的时候去父类中查找方法,运行的时候去子类中查找方法来执行
- 静态方法:编译看左边,运行看左边
- 简而言之:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行
- 非静态方法:编译看左边,运行看右边
- 注意:多态的情况下是无法访问子类独有的方法
- 演示代码:
class Fu{
int a = 10;
public void show1(){
System.out.println("Fu show1...");
}
public static void show2(){
System.out.println("Fu show2...");
}
}
class Zi extends Fu{
int a = 100;
public void show1(){
System.out.println("Zi show1...");
}
public static void show2(){
System.out.println("Zi show2...");
}
}
public class Test1 {
public static void main(String[] args) {
/*
- 多态时成员变量的访问特点: 编译看父类,运行看父类(编译看左边,运行看左边)
- 多态时成员方法的访问特点:
非静态方法: 编译看父类,运行看子类(编译看左边,运行看右边)
静态方法: 编译看父类,运行看父类(编译看左边,运行看左边)
记忆:
除了非静态方法是编译看父类,运行看子类,其余都是看父类
除了非静态方法是编译看左边,运行看右边,其余都是看左边
*/
// 父类的引用指向子类的对象
Fu f = new Zi();
// f访问变量a
System.out.println(f.a);// 10
// f访问show1方法
f.show1();
// f访问show2方法
f.show2();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMCwF1KF-1595463944696)(img\image-20200710121035511.png)]
小结:
多态时成员变量的访问特点: 编译看父类,运行看父类(编译看左边,运行看左边)
多态时成员方法的访问特点:
非静态方法: 编译看父类,运行看子类(编译看左边,运行看右边)
静态方法: 编译看父类,运行看父类(编译看左边,运行看左边)
记忆:
除了非静态方法是编译看父类,运行看子类,其余都是看父类
除了非静态方法是编译看左边,运行看右边,其余都是看左边
注意: 多态的情况下,是无法访问子类独有的成员的(后期引用类型转换能解决这个弊端)
多态的应用场景:
多态的几种应用场景:
-
变量多态 -----> 意义不大
class Fu{ void show(){ System.out.println("Fu show"); } } class Zi extends Fu{ void show(){ System.out.println("Zi show"); } void method(){ System.out.println("Zi method"); } } public class Test1 { public static void main(String[] args) { /* - 变量多态 -----> 意义不大 */ Fu f = new Zi(); f.show(); // f.method();// 编译报错,因为多态成员访问,编译看父类,而父类没有这个方法 Zi zi = new Zi(); zi.show(); zi.method(); } }
-
形参多态----> 常用
abstract class Animal{ public abstract void eat(); } class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨头..."); } } class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼..."); } } public class Test2 { public static void main(String[] args) { /* 形参多态----> 常用 */ Dog dog = new Dog(); invokeEat(dog); System.out.println("-------------------------------------"); Cat cat = new Cat(); invokeEat(cat); // Animal anl = dog; ===> Animal anl = new Dog(); // anl = cat; ===> anl = new Cat(); } // 定义一个方法,可以用来接收Dog和Cat对象,并在该方法中调用他们的eat方法 /*public static void invokeEat(Dog d){ d.eat(); } public static void invokeEat(Cat c){ c.eat(); }*/ // 问题:如果Animal类有100个子类,都需要定义这种方法来调用一下eat方法,那么就需要定义100个重载的方法来实现 // 出现了代码冗余,而且可扩展性不强,后期维护成本很大 // 解决: 形参多态 // 原因: 父类类型的变量作为方法的形参,就可以接受该父类所有的子类对象 public static void invokeEat(Animal anl){ anl.eat(); } }
-
返回值多态—> 常用
class Person{ } class Student extends Person{ } class Teacher extends Person{ } public class Test3 { public static void main(String[] args) { /* 返回值多态---> 常用 */ Person p = getObject(); } // 结论: 返回值类型为父类类型,可以返回该父类的所有子类对象 public static Person getObject(){ if (1==1){ return new Student(); }else { return new Teacher(); } } public static Person getObject(Person p){ // ... return p; } }
小结:
- 变量多态— 不常见 集合阶段: List list = new ArrayList();
- 形参多态— 父类类型作为方法的参数,就可以接收该父类类型的所有子类对象
- 返回值多态—父类类型作为方法的返回值类型,就可以返回该父类类型的所有子类对象
- 问题:
- 如果参数类型为Object类型,那么就可以接收所有类的对象
- 如果返回值类型为Object类型,那么就可以返回所有类的对象
多态的好处和弊端
- 好处
- 提高了代码的扩展性
- 弊端
- 多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。
- 示例代码
多态的好处:===============
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}
public class Test1 {
public static void main(String[] args) {
/*
多态的好处: 提高代码的扩展性,父类类型作为方法的参数,可以接收该父类类型所有子类对象
*/
Dog dog = new Dog();
invokeEat(dog);
System.out.println("========================");
Cat cat = new Cat();
invokeEat(cat);
}
// 父类类型作为方法的参数,可以接收该父类类型所有子类对象
public static void invokeEat(Animal anl){
anl.eat();
}
}
多态的弊端============
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
// 独有功能
public void lookHome(){
System.out.println("狗正在看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
// 独有功能
public void catchMouse(){
System.out.println("猫抓老鼠...");
}
}
public class Test2 {
public static void main(String[] args) {
// 多态的弊端: 无法访问子类独有的功能(成员)
Animal anl1 = new Dog();
anl1.eat();
// anl1.lookHome();// 编译报错,编译看父类(左边)
Animal anl2 = new Cat();
anl2.eat();
// anl2.catchMouse();// 编译报错,编译看父类(左边)
System.out.println("==============================");
invokeEat(anl1);// 传入Dog类对象
invokeEat(anl2);// 传入Cat类对象
}
// 父类类型作为方法的参数,可以接收该父类类型所有子类对象
public static void invokeEat(Animal anl){
anl.eat();
// 访问传入的子类对象的独有功能,就访问不了了
//anl.lookHome();// 编译报错,编译看父类(左边)
//anl.catchMouse();// 编译报错,编译看父类(左边)
}
}
引用类型转换
向上转型
-
子类类型向父类类型向上转换的过程,这个过程是默认的。
Aniaml anl = new Cat();
向下转型
-
父类类型向子类类型向下转换的过程,这个过程是强制的。
Aniaml anl = new Cat(); Cat c = (Cat)anl;//向下转型 c.catchMouse();// 可以访问 子类独有的功能,解决多态的弊端
instanceof关键字
-
向下强转有风险,最好在转换前做一个验证 :
-
格式:
变量名 instanceof 数据类型 如果变量属于该数据类型,返回true。 如果变量不属于该数据类型,返回false。 if( anl instanceof Cat){//判断anl是否能转换为Cat类型,如果可以返回:true,否则返回:false Cat c = (Cat)anl;//安全转换 }
class Fu{
}
class Zi extends Fu{
}
class Z extends Fu{
}
public class Test1 {
public static void main(String[] args) {
/*
引用类型转换: 前提条件--要是父子关系,或者实现关系
向上转型:
概述: 子类类型 向 父类类型转换的一个过程,这个过程是自动的
格式: 父类类型 变量名 = 子类对象;
向下转型:
概述: 父类类型 向 子类类型转换的一个过程,这个过程是强制的
格式: 子类类型 变量名 = (子类类型)父类类型的变量;
注意: 向下转型要求,父类类型的变量指向的对象一定要属于要转换的子类类型
instanceof关键字:
作用: 可以进行类型判断
格式: if(变量 instanceof 数据类型){}
执行:
判断变量指向的对象是否属于instanceof后面指定的数据类型:
如果是属于后面指定的数据类型,那么就返回true
如果不属于后面指定的数据类型,那么就返回false
*/
Zi z = new Zi();// 不转型
// 向上转型
Fu f = new Zi(); // 类似: double numD = 10;
// Test1 t = new Zi();// 编译报错,因为Test1和Zi没有父子类关系
// 向下转型
f = new Z();
if (f instanceof Zi){
Zi zi = (Zi)f;
}
//Zi zi = (Zi)new Fu();// 运行报错,发生类型转换异常
}
}
小结
-
向上转型: 概述: 子类类型 向 父类类型转换的一个过程,这个过程是自动的 格式: 父类类型 变量名 = 子类对象; 向下转型: 概述: 父类类型 向 子类类型转换的一个过程,这个过程是强制的 格式: 子类类型 变量名 = (子类类型)父类类型的变量; 注意: 向下转型要求,父类类型的变量指向的对象一定要属于要转换的子类类型 instanceof关键字: 作用: 可以进行类型判断 格式: if(变量 instanceof 数据类型){} 执行: 判断变量指向的对象是否属于instanceof后面指定的数据类型: 如果是属于后面指定的数据类型,那么就返回true 如果不属于后面指定的数据类型,那么就返回false
扩展多态的弊端解决
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
// 独有功能
public void lookHome(){
System.out.println("狗正在看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
// 独有功能
public void catchMouse(){
System.out.println("猫抓老鼠...");
}
}
public class Test {
public static void main(String[] args) {
// 多态的弊端: 无法访问子类独有的功能(成员)
Animal anl1 = new Dog();
anl1.eat();
Dog d = (Dog)anl1;
d.lookHome();// 编译报错,编译看父类(左边)
System.out.println("=======================================");
Animal anl2 = new Cat();
anl2.eat();
Cat c = (Cat)anl2;
c.catchMouse();// 编译报错,编译看父类(左边)
System.out.println("=======================================");
Dog dog = new Dog();
invoke(dog);
System.out.println("=======================================");
Cat cat = new Cat();
invoke(cat);
}
// 定义一个方法,可以接受Animal类的所有子类对象,在方法中调用共有的eat方法以及各子类独有的方法
public static void invoke(Animal anl){
anl.eat();
// 访问传入的子类对象的独有功能,就访问不了了
if (anl instanceof Dog){
Dog d = (Dog)anl;
d.lookHome();
}
if (anl instanceof Cat){
Cat c = (Cat)anl;
c.catchMouse();
}
}
}