多态🚩
多态就是多种状态,非固定状态。多态在继承的基础上进一步增加了代码的复用性。根据形参(父类)的实际接收参数不同(子类对象不同),方法运行时就可以实现针对某个具体对象的功能,但是却不用针对每个对象,都重新写一个方法。
方法的多态:
重载和重写体现了方法的多态
对象的多态
对象多态的前提是:两个对象(类)存在继承关系,父类引用指向子类对象。
-
编译类型看等号的左边,运行类型看等号的右边
-
一个对象的编译类型和运行类型可以不一致,父类的引用可以指向子类的对象
例:Animal animal = new Dog(); animal的编译类型是Animal,运行类型是Dog
-
编译类型在定义对象时,就确定了,不能改变
-
运行类型是可以变化的
例:animal = new Cat(); //animal的运行类型从Dog变成了Cat,编译类型仍然是Animal
多态的向上转型
-
本质:父类的引用指向了子类的对象
-
语法:父类类型 引用名 = new 子类类型();
-
编译类型看左边,运行类型看右边。编译类型在编译时确定,运行类型运行时才确定,而这是两个时刻。
-
可以调用父类中的所有成员(需要遵守访问权限),不能调用子类中的特有成员,最终运行效果看子类重写(若有)的具体实现(若未重写,向父类查找)
-
为什么不能调用子类中的特有成员?运行类型不是子类类型吗?
因为在编译阶段,能使用哪些成员,是由编译类型来决定的,此时运行类型还没发挥作用。
而在具体运行的时候才由运行类型来决定,即先在子类中查找是否有该方法,没有的话向上(父类)查找。
Animal animal = new Dog();//Dog是Animal的子类
例:animal不能调用Dog特有的成员,运行时要先看Dog类中方法的实现
多态的向下转型
-
语法:子类类型 引用名 = (子类类型)父类引用
Cat cat = (Cat) animal;
-
只能强转父类的引用,不能强转父类的对象。父类的引用本身指向的是堆中的子类对象,强转以后,多了一个cat 引用变量 指向子类对象,而且是Cat类型的,不是Animal类型的。
-
而且强转以前就要求父类的引用必须指向的是当前目标类型的对象
Animal animal = new Cat(); //即有这条语句才能用上面的强转语句
-
当向下转型后,可以调用子类类型中的所有成员,因为cat的编译类型此时就是子类类型
-
属性没有重写之说,属性的值直接看编译类型。方法的具体实现就得看运行类型。
//Animal里面有name属性,Cat里面也有name属性 Animal animal = new Cat(); animal.name;//此时为Animal name的值,因为编译类型是Animal。
-
instanceof 比较操作符,用于判断某对象的运行类型是否为某类型或某类型的子类型
Animal animal = new Cat(); System.out.println(animal instance of Cat);//true System.out.println(animal instance of Animal);//true
-
练习
public class PolyExercise01 { public static void main(String[] args) { double d = 13.4: //ok long I = (long)d; //ok System.out.println();//13 int in = 5; //ok boolean b = (boolean)in; //不对,int -> boolean Object obj =“Hello”; //可以,向上转型 String objStr = (String)obj; //可以,向下转型 System.out.println(objStr); // hello Object objPri = new Integer(5);//可以,向上转型 String str =(String)objPri; //错误,指向ClassCastException Integer str1 = (Integer)objPri; //可以,向下转型 } }
java 的动态绑定机制—实现多态的基础
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
总结:方法看运行类型,属性看编译类型。
public class DynamicBinding
{
public static void main(String[] args)
{
A a = new B();
System.out.println(a.sum()); //结果是20 + 10 = 30
System.out.println(a.sum1()); //结果是20 + 10 = 30
}
}
class A
{
public int i = 10;
public int sum()
{
return getI() + 10; //会调用子类的方法getI()
}
public int getI()
{
return i;
}
}
class B extends A
{
public int i = 20;
public int getI()
{
return i;
}
public int sum1()
{
return i + 10;
}
}
多态的应用
-
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
Person[] persons = new Person[5]; persons[0] = new Person("jack",20); persons[1] = new Student("marry",18,100); persons[2] = new student("smith",19,30.1); persons[3] = new Teacher("scott",30,20000) : persons[4] = new Teacher("king",50,25000); for(int i = 0; i < persons.length; i++) { persons[i].say(); //由于动态绑定机制,这里会分别调用不同类的say() if(persons[i] instanceof Student) //判断persons[i]的运行类型是不是Student { //如果不强转,是无法调用子类特有的方法的,比如 person[i].study()编译无法通过哦 ((Student)persons[i]).study(); //向下转型,调用Student类特有的方法 } }
-
多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型,实际运行时进行动态绑定。
class Master { public String name; public void feed(Animal animal, Food food) //这里可以传入Animal的子类和Food的子类 { System.out.println(animal.getName() + " 在吃 " + food.getName()); } } Master master = new master("主人"); Dog dog = new Dog("大黄"); //Dog是Animal的子类 Bone bone = new Bone("大棒骨"); //Bone是Food的子类 master.feed(dog, bone); //feed方法可以传入形参的子类 //结果:大黄 在吃 大棒骨