今天学习的内容是多态
多态可以提高程序的可扩展性和可维护性,是一种非常重要的特性。知识点:
- 多态的形式:父类引用指向子类对象(向上转型),并可以通过父类引用调用子类对象的内容(注意接口和实现类也存在多态,为了叙述方便下面省略)
- 方法的返回值类型和方法的参数列表也存在多态,目的就是提高程序的可扩展性
- 父类引用不能调用子类特有的内容(这是因为编译器注重的是引用的类型!!如果这个引用的类型并没有该变量或该方法,编译器是不会让你通过编译的!!)
- 如果子类中有与父类同名的变量,子类变量不会覆盖父类变量,而是共存于子类对象中。之前说过子类引用默认访问同名的子类变量,而使用向上转型的话,父类引用默认访问同名的父类变量。多态失效(但是实际开发中子父类不会出现同名变量,容易混淆)
- 如果子类中有与父类同名的静态方法,父类引用调用的是父类中的静态方法。多态失效(实际上静态方法与静态变量是通过类调用的,压根不具有多态性!!)
- 如果已经使用了向上转型,而又想访问子类特有的内容,就必须进行向下转型(因为编译器只认引用类型)。并且为了确保父类引用指向的确实是子类对象,最好使用instanceof关键字判断。
- 如果强行要将父类对象转换为子类对象,会发生ClassCaseException
- 向下转型的格式:if(父类引用instanceof 子类){子类 引用=(子类)父类引用}
- 重点:实际上编译器注重的是引用类型,它根本无法得知父类引用到底指向哪个对象,实现多态的关键是动态绑定:除了static方法和final方法(private方法属于final方法),其他所有方法都是在运行时根据对象的类型进行绑定。当父类引用调用方法时,由于动态绑定的存在,JVM才可以根据对象的类型知道到底该调用谁的方法。实际上,多态只是在调用子类覆盖后的非静态方法时才会生效!!!!
- 补充1:数组的类型检查是在运行时进行的,所以数组的多态可以通过编译器的检查,但是数组的多态是有风险的,如果将不同子类的对象放进数组,会出现ArrayStoreException
- 补充2:容器类的类型检查是在编译期进行的,所以容器类没有多态
示例程序:
public class DuoTaiTest {
public static void main(String[] args){
//向上转型
Animal animal=new Dog();
//向上转型
Pet pet=new Dog();
//!animal.gurad(); 父类引用不能调用子类特有方法
//!System.out.println(animal.age); 父类引用不能调用子类特有变量
//!pet.guard(); 接口引用不能调用实现类特有方法
//!System.out.println(pet.age); 接口引用不能调用实现类特有变量
System.out.println(animal.size);//父类的0 如果子类和父类有同名变量时,多态失效
System.out.println(pet.PRICE);//接口的100 如果实现类和接口有同名变量,多态失效
animal.aa();//父类的静态方法 如果子类和父类有同名静态方法,多态失效
//数组的类型检查是在运行时进行的,所以数组的多态可以通过编译器的检查
Animal[] arr_dog=new Dog[]{new Dog(),new Dog()};
Animal[] arr_cat=new Cat[]{new Cat(),new Cat()};
//但是数组的多态是有风险的,如果将不同子类的对象放进数组,会出现ArrayStoreException
//arr_dog[0]=new Cat();
//arr_cat[0]=new Dog();
//容器类的类型检查是在编译期进行的,所以容器类没有多态
//!ArrayList<Animal> list=new ArrayList<Dog>();
//多态的好处:提高程序可扩展性
DuoTaiTest dt=new DuoTaiTest();
dt.doEat(animal);//编译器调用的是Animal引用作为形参的版本
dt.doPlay(pet);//编译器调用的是Pet引用作为形参的版本
dt.doEat(new Dog());//编译器调用的是Dog引用作为形参的版本
dt.doEat(new Cat());//编译器调用的是Cat引用作为形参的版本
dt.doPlay(new Dog());//编译器调用的是Dog引用作为形参的版本
dt.doPlay(new Cat());//编译器调用的是Cat引用作为形参的版本
//向下转型(强制类型转换)
//如果强行把父类对象转成子类对象,就会发生ClassCastException
//所以最好先使用instanceof关键字判断父类引用指向的是不是子类对象
Animal a=dt.fanHuiAnimal();
if(a instanceof Dog){
Dog dog=(Dog)a;
dog.guard();
}
Pet p=dt.fanHuiPet();
if(p instanceof Dog){
Dog dog=(Dog)p;
dog.guard();
}
}
//如果这样定义方法,不利于程序的可扩展性
public void doEat(Dog dog){
dog.eat();
}
public void doEat(Cat cat){
cat.eat();
}
public void doPlay(Dog dog){
dog.play();
}
public void doPlay(Cat cat){
cat.play();
}
public Dog fanHuiDog(){
return new Dog();
}
public Cat fanHuiCat(){
return new Cat();
}
//... (如果添加了新的动物,这里又要新添很多方法,不利于系统维护)
//利用多态,可以提高程序的可扩展性
//将方法的参数列表设置为父类或接口的引用
public void doEat(Animal animal){//Animal animal=new Dog()/new Cat()...
animal.eat();
System.out.println("利用多态");
}
public void doPlay(Pet pet){//Pet pet=new Dog()/new Cat()...
pet.play();
System.out.println("利用多态");
}
//将返回值类型设置为父类或接口
public Animal fanHuiAnimal(){//Animal animal=new Dog()/new Cat()...
return new Dog();
//return new Cat();
//...
}
public Pet fanHuiPet(){//Pet pet=new Dog()/new Cat()...
return new Dog();
//return new Cat();
//...
}
}
//父类
abstract class Animal{
int size=0;
public void eat(){
System.out.println("吃东西");
}
public static void aa(){
System.out.println("父类的静态方法");
}
}
//接口
interface Pet{
int PRICE=100;
void play();
}
//既是子类也是实现类
class Dog extends Animal implements Pet{
//与父类和接口同名变量
private int size=1;
private static int PRICE=200;
//特有变量
private int age;
public void eat(){
System.out.println("啃骨头");
}
public void play(){
System.out.println("和主人玩");
}
//特有方法
public void guard(){
System.out.println("保护主人");
}
//覆盖父类的静态方法(实际上这里并没有被覆盖)
public static void aa(){
System.out.println("子类的静态方法");
}
}
//既是子类也是实现类
class Cat extends Animal implements Pet{
public void eat(){
System.out.println("吃鱼");
}
public void play(){
System.out.println("抓老鼠玩");
}
}