1.多态的概念
表示一个对象具有多重特征,可以在特定的情况下表现出不同的状态。
我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。这时候使用多态。
2.多态的形式和体现
1、多态引用
Java规定父类类型的变量可以接收子类类型的对象。
父类类型 变量名 = 子类对象;
父类类型:指子类继承的父类类型。
所以说继承是多态的前提
2、多态引用的表现
表现:编译时类型与运行时类型不一致,编译时看“父类”,运行时看“子类”。
3、多态引用的好处和弊端
弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;
好处:运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
3. 应用多态解决问题
1、声明变量是父类类型,变量赋值子类对象
-
方法的形参是父类类型,调用方法的实参是子类对象
-
实例变量声明父类类型,实际存储的是子类对象
package com.haogu.polymorphism.grammar; public class OnePersonOnePet { private Pet pet; public void adopt(Pet pet) {//形参是父类类型,实参是子类对象 this.pet = pet; } public void feed(){ pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同 } }
package com.haogu.polymorphism.grammar; public class TestOnePersonOnePet { public static void main(String[] args) { OnePersonOnePet person = new OnePersonOnePet(); Dog dog = new Dog(); dog.setNickname("小白"); person.adopt(dog);//实参是dog子类对象,形参是父类Pet类型 person.feed(); Cat cat = new Cat(); cat.setNickname("雪球"); person.adopt(cat);//实参是cat子类对象,形参是父类Pet类型 person.feed(); } }
2、数组元素是父类类型,元素对象是子类对象
package com.haogu.polymorphism.grammar; public class OnePersonManyPets { private Pet[] pets;//数组元素类型是父类类型,元素存储的是子类对象 public void adopt(Pet[] pets) { this.pets = pets; } public void feed() { for (int i = 0; i < pets.length; i++) { pets[i].eat();//pets[i]实际引用的对象类型不同,执行的eat方法也不同 } } }
package com.haogu.polymorphism.grammar; public class TestPets { public static void main(String[] args) { Pet[] pets = new Pet[2]; pets[0] = new Dog();//多态引用 pets[0].setNickname("小白"); pets[1] = new Cat();//多态引用 pets[1].setNickname("雪球"); OnePersonManyPets person = new OnePersonManyPets(); person.adopt(pets); person.feed(); } }
3、方法返回值类型声明为父类类型,实际返回的是子类对象
package com.haogu.polymorphism.grammar; public class PetShop { //返回值类型是父类类型,实际返回的是子类对象 public Pet sale(String type){ switch (type){ case "Dog": return new Dog(); case "Cat": return new Cat(); } return null; } }
package com.haogu.polymorphism.grammar; public class TestPetShop { public static void main(String[] args) { PetShop shop = new PetShop(); Pet dog = shop.sale("Dog"); dog.setNickname("小白"); dog.eat(); Pet cat = shop.sale("Cat"); cat.setNickname("雪球"); cat.eat(); } }
4、向上转型与向下转型
首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
这个和基本数据类型的转换是不同的。基本数据类型是把数据值copy了一份,相当于有两种数据类型的值。而对象的赋值不会产生两个对象。
1、为什么要类型转换呢?
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过。
-
向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
-
此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
-
但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
-
此时,一定是安全的,而且也是自动完成的
-
-
向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
-
此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
-
但是,运行时,仍然是对象本身的类型
-
不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
-
2、如何向上转型与向下转型
向上转型:自动完成
向下转型:(子类类型)父类变量
3、instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
变量/匿名对象 instanceof 数据类型
那么,哪些instanceof判断会返回true呢?
-
变量/匿名对象的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
-
变量/匿名对象的运行时类型<= instanceof后面数据类型,才为true
5、 虚方法
在Java中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
当我们通过“对象xx.方法”的形式调用一个虚方法时,要如何确定它具体执行哪个方法呢?
(1)静态分派:先看这个对象xx的编译时类型,在这个对象的编译时类型中找到能匹配的方法
(2)动态绑定:再看这个对象xx的运行时类型,如果这个对象xx的运行时类重写了刚刚找到的那个匹配的方法,那么执行重写的,否则仍然执行刚才编译时类型中的那个匹配的方法