1. 多态性的定义
相比于封装和继承,Java多态是三大特性中比较难的一个,封装和继承最后归结于多态, 多态指的是类和类的关系,两个类由继承关系,存在有方法的重写,故而可以在调用时有父类引用指向子类对象。可以理解为一个事物的多种形态。指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式
2. 多态性的好处
- 可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
- 可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
- 接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
- 灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
- 简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
2.1 实现多态的技术
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
2.2 多态的作用
多态的作用:消除类型之间的耦合关系。
3. 多态性使用:虚拟方法调用
- 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
- 总结:编译,看左边;运行,看右边。
- 对象的多态:在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
4. 多态性使用前提
- 类的继承关系
- 方法的重写
5. 多态性应用举例
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal){
animal.eat();
animal.shout();
}
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪! 汪! 汪!");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵! 喵! 喵!");
}
}
6. 多态性使用注意点总结
- 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
- 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
多态使用总结:
- 多态的作用:提高了代码的通用性,常称作接口重用
- 使用前提:①需要存在继承或者实现关系 ②有方法的重写
- 成员方法:
编译时:要查看引用变量所声明的类中是否有所调用的方法
运行时:调用实际new的对象所属的类中的重写方法
- 成员变量:不具备多态性,只看引用变量所声明的类
7. 向上转型与向下转型
7.1 向上转型
向上转型:多态
7.2 向下转型
7.2.1 为什么使用向下转型
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特的属性和方法?使用向下转型。
7.2.2 如何实现向下转型
使用强制类型转换符:()
Person p = new Man();
Man m1=(Man)p2;//向下转型
7.2.3 使用时注意点
- 使用强转时,可能出现ClassCastException的异常。
- 为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
- 只有对象A是B的子类实例化对象或者在下层的子类,才能向下转型
7.2.4 instanceof使用
- a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
- 如果 a instanceof A返回true,则 a instanceof B也返回true.其中,类B是类A的父类。
- 要求a所属的类与类A必须是子类和父类的关系,否则编译错误。
应用举例:
public class InstanceTest {
public static void main(String[] args) {
InstanceTest test1 = new InstanceTest();
test1.method(new Graduate());
}
public void method(Person e){
//方式一
if (e instanceof Graduate){
System.out.println("a graduated student");
System.out.println("a student");
System.out.println("a person");
}else if (e instanceof Student){
System.out.println("a student");
System.out.println("a person");
}else {
System.out.println("a person");
}
//方式二
if (e instanceof Graduate){
System.out.println("a graduated student");
}
if (e instanceof Student){
System.out.println("a student");
}
if (e instanceof Person){
System.out.println("a person");
}
//虚拟方法调用
String info = e.getInfo();
System.out.println(info);
}
}
class Person{
protected String name = "person";
protected int age = 50;
public String getInfo(){
return "Name:"+name+"\n"+"age:"+age;
}
}
class Student extends Person{
protected String school = "pku";
public String getInfo(){
return "Name:"+name+"\n"+"age:"+age+"\nschool:"+school;
}
}
class Graduate extends Student{
public String major = "IT";
public String getInfo(){
return "Name:"+name+"\n"+"age:"+age+"\nschool:"+school+"\nmajor:"+major;
}
}
8. 面试题
8.1 谈谈你对多态性的理解
- 实现代码的通用性。
- 举例:
- Object类中定义的public boolean equals(Object obj){ }
- JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
- 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
8.2 多态是编译时行为还是运行时行为
运行时行为
代码演示:
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);//20
s.display();//20
Base b = s;//多态性
//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
System.out.println(b == s);//true
System.out.println(b.count);//10
b.display();//20
}
}
- 若子类重写了父类方法,就意味着子类里面定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边