【JAVA入门】Day10 - 多态
面向对象的三大特征就是:封装、继承和多态。
封装,是对象代表什么,就得封装对应的数据,并提供数据对应的行为。
为了解决封装代码冗余的问题,继承诞生了,解决了 Javabean 当中代码重复的问题。
多态是指对象的“多种形态”。如下面的代码所示,我们创建了(new)一个 Student 对象,然后直接把它赋值给了其父类 Person 的一个对象 p,这种形式是合理的,它就是多态。
//子类 学生形态 对象 s
Student s = new Student();
//父类 人的形态 对象 p
Person p = new Student();
多态的应用场景也有很多,不同用户身份登录就是其中一种应用。
public void register(? ? ?) {
//方法里面就是注册的代码逻辑
}
我们可以根据传递对象的不同,调用不同对象中的同名方法。基于上面那个多态的特性,我们就可以把子类对象作为参数赋值给父类对象。
public void register(Person p) {
p.show();
}
根据传递对象的不同,就会调用不同的 show 方法。假如 Person 有子类 Administrator 和 NormalUser,那么将 Administrator 的对象作为参数传递给这里,这里的 p.show() 就会调用 Administrator 的;将 NormalUser 的对象作为参数传递给这里,这里的 p.show() 就会调用 NormalUser 的。
Administrator a = new Administrator();
register(a); //把 a 赋值给 p,此时的 p.show() 相当于 a.show()
一、什么是多态
同类型的对象,表现出不同的形态,这就是多态。
其表现形式为:
父类类型 对象名称 = 子类对象;
多态是有前提的:
- 有继承 / 实现关系。
- 有父类引用指向子类对象。 Fu f = new Zi();
- 有方法重写。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show() {
System.out.println(name + ", " + age);
}
}
public class Student extends Person {
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}
public class Teacher extends Person {
@Override
public void show() {
System.out.println("教师的信息为:" + getName() + ", " + getAge());
}
}
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}
public class Test {
public static void main(String[] args) {
//创建三个对象,调用 register 方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王坚果");
t.setAge(30);
Administrator a = new Administrator();
a.setName("管理员");
a.setAge(28);
}
//这个方法要既能接受老师,又能接受学生,还能接受管理员
//只能把参数写成这三个类型的父类
public static void register(Person p) {
p.show();
}
}
二、多态调用成员的特点
- 变量调用:编译看左边,运行也看左边。
- 方法调用:编译看左边,运行看右边。
public class Test {
public static void main(String[] args) {
//创建对象(多态方式)
//Fu f = new Zi();
Animal a = new Dog();
//调用成员变量:编译看左边,运行也看左边
//编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功;如果没有,编译失败。
//运行也看左边:java运行代码的时候,实际获取的也是左边父类中的成员变量的值。
System.out.println(a.name); //动物
//调用成员方法:编译看左边,运行看右边
//编译看左边:javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功;如果没有,编译失败。
//运行看右边:java运行代码的时候,实际上运行的是子类中的方法。
a.show(); // Dog --- show方法
}
}
class Animal {
String name = "动物";
public void show() {
System.out.println("Animal --- show方法");
}
}
class Dog extends Animal {
String name = "狗";
@Override
public void show() {
System.out.println("Dog --- show方法");
}
}
三、多态的优势
- 在多态形势下,右边对象可以实现解耦合,便于扩展和维护。
Person p = new Student();
p.work(); // 业务逻辑发生改变时,后续代码无需修改
如上代码所示,p.work();无需做任何改动,只需要修改第一行等号右边的 new Student(); 就可以实现业务逻辑的改变。
- 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利性。
四、多态的弊端
- 不能调用子类特有的功能(方法)。(编译看左边,如果子类特有这个方法,而父类没有,编译压根不会通过)
因此,针对这个弊端,我们要进行一些改进。解决方案一般是:把调用者变回子类类型就可以了。
Animal a = new Dog();
a.eat();
a.lookHome(); //报错,父类中没有lookHome方法
Animal a = new Dog();
a.eat();
Dog d = (Dog) a; //强制类型转换
d.lookHome();
但是一定要注意:强制转换的父类对象应本来就是用相应的子类对象赋值创建的,转换类型与真实对象类型不一致会报错。比如下面的例子:
Animal a = new Dog(); //用Dog子类创建了一个父类对象
Cat c = (Cat) a; //报错,因为a根本不是Cat创建的
为了防止强转报错,我们可以提前根据该父类对象的创建类型进行 if else 语句判断。此时就要用到新的关键字——instanceof。
- instanceof 关键字:对象变量 instanceof 类名
用于确定某个对象是否属于这个类,如果是,返回 true,如果不是,返回 false。
if(a instanceof Dog) {
Dog d = (Dog) a;
d.lookHome();
}else if(a instanceof Cat) {
Cat c = (Cat) a;
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
根据不同的类型进行强转,然后调用相应类型的特有的方法。
到了 JDK14 ,Java 新添了一种写法,可以直接把判断和强转写在一行:
//如果“是”,强转成 Dog 类型,转换后变量命名为 d;如果“不是”,则不强转。
if(a instanceof Dog d) {
d.lookHome();
//如果“是”,强转成 Cat 类型,转换后变量命名为 c;如果“不是”,则不强转。
}else if(a instanceof Cat c) {
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
强制类型转换可以将多态父类转换成真正的子类类型,从而调用子类独有的功能。