目录:
1.认识多态
2.多态中调用成员的特点
3.多态的优势和弊端
1.认识多态:
多态是面向对象的三大特征的最后一个,那么我们先了解什么是多态呢?实际上就是同类型的对象,表现出不同形态。
多态的表现形式:父类类型 对象名称=子类对象。
多态的前提:
1.有继承关系
2.有父类引用指向子类对象 Fu f=new Zi();
3.有方法重写
可以先体会一下多态,当我们在IDEA中创建了四个类,Administrator,Person,Student,Teacher这四个类之后,Person类中有基础的show方法,然后每个类中都重写一个新的show方法,当我们在Test类中进行测试的时候
java
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 admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
/*
运行输出:
学生的信息为:张三, 18
老师的信息为:王建国, 30
管理员的信息为:管理员, 35
*/
2.多态中调用成员的特点
变量调用:编译看左边,运行也看左边
方法调用:编译看左边,运行看右边
java
public class Test {
public static void main(String[] args) {
//创建对象(多态方式)
//Fu f=new Zi();
Animal a = new Dog();
//调用成员变量:编译看左边,运行也看左边
//编译看左边:Javac在编译代码的时候,会先看左边的父类中有没有这个变量,如果有,编译成功,如果没有,则编译失败
//运行也看左边:Java运行代码的时候,实际获取的是左边父类中成员变量的值
System.out.println(a.name);//动物
//调用成员方法:编译看左边,运行看右边
//编译看左边:同上
//运行看右边:运行代码的时候是运行子类中的方法
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方法");
}
}
class Cat extends Animal{
String name = "猫";
//@Override
public void show() {
System.out.println("Cat --- show方法");
}
}
对于这个该怎么理解呢?
Animal a =new Dog();现在是用a去调用变量和方法的,而a是Animal类型的,所以默认都会从Animal这个类中去找。
对于成员变量来说,由于子类和父类是继承关系,在子类的对象中,会把父类的成员变量也继承下的。比如说父:name;子:name的情况下,a.name是调用的父,因为在Animal类中。
对于成员方法来说:如果子类对方法进行了重写,那么在虚方法表中,会把父类的方法进行覆盖,但是实际上还是调用的父方法,所以是编译看右边了。
我们可以再通过内存图来加深一下想法,首先我们是先创建了Animal类,然后又用Dog类继承了Animal。然后我们代码往下走,在方法区写了Test的主函数。
先利用多态,创建a,是Animal类的,然后在方法区加载了Animal和Dog类。注意在Dog类中的show是重写过后的。然后我们会在堆中创建空间,分别存父类和子类的name,因为这个我们是Animal类的a嘛,所以当a.name的时候就会去打印出来"动物"。如果我们是之前的方法,Dog d=new Dog()这种方法,那我们就是直接访问的子类的name,如果子类中没有,才会去看父类中的name。
当利用show的时候,根据调用方法的特点,也是直接看重写的show。
3.多态的优势和弊端:
优势:
1.在多态形势下,右边对象可以实现解耦合,便于扩展和维护
java
Person p=new Student();
p.work();//当业务逻辑发生改变时,后续代码无需修改
当我这个不想执行student的work了,我想改成teacher的,就只需要把第一行后边这个new student改成teacher就可以了。
2.定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
可以看在StringBuilder里,sb.append就可以直接添加object类型,方便了使用。
那么多态有什么弊端呢?
多态弊端:父类引用不能直接调用子类独有的方法
java
public class Test {
public static void main(String[] args) {
//创建对象,多态:父类引用指向子类对象
Animal a = new Dog();
//编译看左边,运行看右边,执行Dog重写后的eat()
a.eat();
//多态弊端:父类引用不能直接调用子类独有的方法
//a.lookHome(); //这行会编译报错,Animal类中没有lookHome方法
//如果要调用子类特有方法,需要向下转型
Dog d = (Dog) a;
d.lookHome();
//多态,父类引用指向Cat子类对象
Animal c = new Cat();
c.eat();
//向下转型调用猫特有方法
Cat cat = (Cat) c;
cat.catchMouse();
}
}
class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
//Dog子类特有方法
public void lookHome(){
System.out.println("狗看家");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃小鱼干");
}
//Cat子类特有方法
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
还是刚才的代码,如果我想转换类型,该怎么做?
利用instanceof这个关键字进行判断,在Java17+之后,就可以不用再单独写转换,直接在判断框内写就可以了
java
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.eat();
// ---------------- 旧版本写法 ----------------
/*
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("没有这个类型,无法转换");
}
*/
// ---------------- Java17+ 新模式匹配写法 ----------------
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
// 测试Cat对象
Animal b = new Cat();
b.eat();
if(b instanceof Dog d){
d.lookHome();
}else if(b instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
}
}