J a v a 多 态 \color{black}{\huge{Java多态}} Java多态
多态
1. 什么是多态?
多态:同种类型的对象,执行同一个任务,会表现出不同的行为特征,这就是多态(这种解释顶级抽象)
再细致一点说多态就是为不同的数据类型提供了同一个接口。(这里这个不同数据类型一般就是指的父类与子类不同、接口类与实现类不同)
在举例子之前先说一下多态的格式。
2. 多态的格式
1. 父类类型 对象名称 = new 子类构造器
2. 接口 对象名称 = new 实现类构造器
实例:
//创建一个抽象动物类,其中的run()方法由子类进行重写
public abstract class Animal {
public String name = "父类对象";
public abstract void run();
}
//狗类继承动物类,并且重写run()方法
public class Dog extends Animal {
@Override
public void run() {
System.out.println("狗跑的非常快~~");
}
}
//乌龟类继承动物类,并且重写run()方法
public class Tortoise extends Animal{
@Override
public void run() {
System.out.println("乌龟根本跑不了!!");
}
}
//测试类:用于多态的测试
public class Test {
public static void main(String[] args) {
//多态的形式: 父类类型 对象名称 = new 子类构造器;
Animal a = new Dog();
a.run();
Animal a2 = new Tortoise();
a2.run();
}
}
观察测试类中的代码Animal a = new Dog(),Animal a2 = new Tortoise()
,Animal
类本应该调用Animal
类的对象,但是在这里调用的是两个子类的构造方法,这就是多态的体现。
3. 多态中的成员访问特点
J a v a Java Java语言中的多态设计是希望倾向于行为的多态,就像定义那样:同种类型的对象,会展现出不同的行为。所以实现多态的对象访问成员的时候特征如下:
方法调用 : 编译看左边,运行看右边
变量调用 : 编译看左边,运行也看左边(多态侧重的是行为的多态)
方法调用(多态的侧重点)
观察下面代码
Animal a = new Dog();
a.run();
Animal a2 = new Tortoise();
a2.run();
这两段代码都是Animal
类创建了两个对象,但是调用构造器的时候是调用了不同子类的构造器,所以当调用run()
方法的时候也是分别调用自己类的方法来运行。
运行结果
这就是行为的多态,所谓的看左边就是创建的时候编译器是按照Animal
这个类进行创建对象的。当运行方法的时候看右边就是指又要按照右边的具体子类去执行对应的方法。
变量调用
在狗类中增加以下代码:
public String name = "狗类动物";
在乌龟类中增加以下代码:
public String name = "乌龟类动物";
两个类中的name
都设定为public
便于外面直接访问。
动物中原有的代码:
public String name = "父类对象";
在测试类中运行如下代码:
System.out.println(a.name);
System.out.println(a2.name);
测试结果
结果发现访问这两个对象的name
变量,显示的结果都是打印出父类对象。这就是编译看左,运行也看左。编译的时候,左边的类就是Animal
类,根本无调用的谁的构造器,自然访问name
变量的时候也就只能是访问的Animal
类中的name
。
4. 多态的优势(为什么使用多态?)
在多态形式下,右边对象可以实现解耦合,便于扩展和维护(太抽象啦这些名词)
简单来讲就是如果定义一个方法比方说go()
方法,它的面向的对象就是动物对象,可以这么创建吗?
public static void go(Dog d){
...
}
当然不可以!都已经说过了,这个go()
方法是面向所有的动物创建的,这样写就是只让Dog
类产生的对象使用的,肯定是不行的。
如果这样写呢?
public static void go(Animal a){
...
}
这样写的话如果结合刚才的多态,就可以实现Dog
和Tortoise
都能使用这个方法了。这就增加了方法的通用性。并且有利于维护。
原理:定义方法的时候,使用父类型作为参数,利用多态,那么该方法就可以接受父类继承产生的一切子类对象,体现出多态的扩展性和便利性。
❗❗但是直接使用多态会产生一个问题:多态下不能够使用子类独有的功能,因为编译器默认的是Animal
类产生的对象,所以无法调用子类特有的功能,除了子类重写父类的方法。
5. 类型转换
其实一上来就直接那样写多态,多少让人有点懵,为什么可以那么写呢?
J
a
v
a
Java
Java的类从父类开始到一层一层的子类,类的概括范围是越来越小的(从父类的大概到子类越来越来分的精细化)。所以父类的覆盖范围总是比子类要大的。再回过头看这个代码:
Animal t = new Tortoise()
Tortoise
这个类作为子类范围比Animal
类要小,把一个小范围的对象,赋值给一个大范围的,这一定是可以的。或者说Tortoise
构造器产生的对象一定属于Tortoise
类型,但是Tortoise
类型又是Animal
的子类,所以上述代码是可行的。
以上就是对象自动类型转换。
有自动类型转换就必然有强制类型转换。
强制类型转换
将父类对象强制改变为子类类型对象,也就是将大范围强制改变为小范围(非常危险的操作属于是)。
//使用乌龟类的构造器生成动物类的对象
Animal a2 = new Tortoise();
a2.run();
//将这个动物类父类强制转换为乌龟类,然后调用乌龟类的layEgg方法
Tortoise t = (Tortoise)a2;
t.layEgg();
//乌龟类
public class Tortoise extends Animal{
@Override
public void run(){
System.out.println("乌龟跑的非常的慢~~~");
}
/**
* 独有功能
*/
public void layEgg(){
System.out.println("乌龟在下蛋~~");
}
}
❗❗强制类型转换可以有效解决多态的时候无法使用子类独有的方法这个问题,但是这个操作非常危险,上面的代码也是知道了这个Animal
就是使用乌龟类的构造器创建的,所以才敢强转,其他的时候必须进行判断才可以强转
**注:**如果发生了转换之后的类型和对象的真实类型不是同一种类型,编译的时候不会报错,因为编译器允许有继承或者实现关系在编译阶段发生强转,但是在转换的时候就会发生ClassCastException
错误
Animal t = new Tortoise();
Dog d = (Dog)t //出现异常 ClassCastException
Instanceof
instanceof
这个关键字就是解决这个问题的,使用方法:
变量名字 instanceof 真实类型
//判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是是其子类型,是则返回true,反之
示例
if(a2 instanceof Tortoise){
Tortoise t1 = (Tortoise) a2;
t1.layEgg();
}else if(a2 instanceof Dog){
Dog d = new Dog();
d.lookDoor();
}
这样就不会出现强转范围的问题了。
❗这里的真实类型指的是,右边调用构造器对应的类型
强转的作用
-
1. 可以转换成真正的子类,从而调用子类型的独有的功能
-
2. 有继承关系/实现的2个类型就可以进行强制类型转换,编译没有问题
-
3. 运行的时候,如果发现强制转换后的类型不是对象真实的类型就会报错 类型转换异常:ClassCastException