Java-多态(详解)
目录
一、多态的概念
二、多态实现的条件
示例:
分析:
三、关于Java语言中的向上转型和向下转型:
1.向上转型(Upcasting)
(1).示例代码1
(2).示例代码2
2.向下转型(Downcasting)
(1).示例代码1
(2).示例代码2
3.需求:程序运行阶段动态确定对象
4.总结
四、多态在开发中的应用
1.开闭原则(OCP)
(1)、原则本质
2.OCP与多态的示例
(1).问题描述
(2).问题分析:
(3).解决方案:通过多态与OCP重构
步骤1:定义抽象层
步骤2:实现具体宠物类
步骤3:重构Master类
步骤4:测试扩展性
(4).扩展性验证
(5).对比原始代码与重构后代码
(6).总结
五、多态总结
1.多态代码示例
(1). 父类 Animal
(2). 子类 Cat 和 Dog
(3). 测试类 Test
2.多态的特点
3、多态的优点
4、注意事项
5、总结
一、多态的概念
多态是面向对象编程的三大特性之一(封装、继承、多态)。
在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作。
例如:狗和猫都是动物,动物共同的行为都有吃这个动作,而狗可以表现为啃骨头,猫则可以表现为吃老鼠。这就是多态的表现,即同一件事情,发生在不同对象的身上,就会产生不同的结果。
多态是指同一个行为具有多个不同表现形式或形态的能力。
在 Java 中,多态分为两种:
-
编译时多态:方法重载(Overload)。
-
运行时多态:方法重写(Override),通过父类引用指向子类对象实现。
二、多态实现的条件
在Java中,要实现多态性,就必须满足以下条件:
1.继承关系
存在继承关系的类之间才能够使用多态性。多态性通常通过一个父类用变量引用子类对象来实现。
2.方法重写
子类必须重写(Override)父类的方法。通过在子类中重新定义和实现父类的方法,可以根据子类的特点行为改变这个方法的行为,如猫和狗吃东西的独特行为。
3.父类引用指向子类对象
使用父类的引用变量来引用子类对象。这样可以实现对不同类型的对象的统一操作,而具体调用哪个子类的方法会在运行时多态决定
多态的实现条件(简洁版)
继承关系:必须存在父子类关系。
方法重写:子类重写父类的方法。
向上转型:父类引用指向子类对象。
示例:
下面的案例是根据猫和狗叫的动作的不同,而实现的多态:
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗发出汪汪声");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("猫发出喵喵声");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 父类引用指向子类对象
Animal animal2 = new Cat(); // 父类引用指向子类对象
animal1.sound(); // 输出:狗发出汪汪声
animal2.sound(); // 输出:猫发出喵喵声
}
}
运行结果为:
分析:
这段Java代码演示了多态的三个核心要素:继承、方法重写和向上转型。以下是关键点解析:
1.继承关系:
(1).Dog和Cat类继承自Animal基类
(2).子类获得了父类的特性并可以扩展新功能
2.方法重写(Override):
子类通过
@Override
注解重写父类的sound()
方法这是实现多态的必要条件
3.向上转型(Upcasting):
Animal animal1 = new Dog(); // 父类引用指向子类对象
Animal animal2 = new Cat();
●将子类对象赋值给父类类型的引用变量
●这是多态实现的基础
4.动态绑定(Late Binding):
在运行时根据对象实际类型确定调用哪个方法
animal1.sound()实际调用的是Dog类的方法
animal2.sound()实际调用的是Cat类的方法
5.代码扩展优势:
新增动物类型只需继承Animal并实现sound()
调用方代码无需修改即可支持新类型
6.执行过程分析:
编译时检查父类是否存在sound()方法
运行时根据对象实际类型进行方法调用
JVM通过虚方法表实现动态绑定
注意事项:
-
使用多态时无法直接调用子类特有方法
-
需要向下转型时建议使用
instanceof
进行类型检查 -
父类如果不需要实例化应该声明为抽象类
技术原理:
-
动态绑定:Java在运行时通过虚方法表(vtable)查找实际要执行的方法实现。
-
编译检查:编译器验证父类是否存在被调用方法(确保编译通过),运行时决定具体实现。
此设计符合面向对象的开闭原则(对扩展开放,对修改关闭),是Java实现多态的典型范例。
三、关于Java语言中的向上转型和向下转型:
1.关于基本数据类型之间的类型转换:
* 第一种:小容量转换成大容量,叫做自动类型转换。
* int i = 100;
* long x = i;
* 第二种:大容量转换成小容量,不能自动转换,必须添加强制类型转换符才行。叫做强制类型转换。
* int y = (int)x;
*
2. 除了基本数据类型之间的类型转换之外,对于引用数据类型来说,也可以进行类型转换。
* 只不过不叫做自动类型转换和强制类型转换。我们一般称为向上转型和向下转型。
*
* 关于Java语言中的向上转型和向下转型:
* 向上转型(upcasting):子 ---> 父 (可以等同看做自动类型转换。)
* 向下转型(downcasting):父 ---> 子 (可以等同看做强制类型转换。)
*
* 不管是向上还是向下转型,两种类型之间必须要有继承关系,编译器才能编译通过。这是最基本的大前提。
注意:
我这里
向上转型的示例代码1对应向下转型的示例代码1
向上转型的示例代码2对应向下转型的示例代码2
1.向上转型(Upcasting)
向上转型是指将子类对象赋值给父类引用变量,这是一种自动类型转换,在编译和运行时都是安全的。
(1).示例代码1
// 父类
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
// 子类
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗发出汪汪声");
}
public void bark() {
System.out.println("狗在汪汪叫");
}
}
public class Main {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
animal.sound(); // 调用子类重写后的方法
// 以下代码会编译错误,因为父类引用无法调用子类特有的方法
// animal.bark();
}
}
代码解释:
●Animal 是父类,Dog 是子类,Dog 类继承自 Animal 类,并重写了 sound() 方法,同时还有自己特有的 bark() 方法。
●Animal animal = new Dog(); 这行代码进行了向上转型,将 Dog 类的对象赋值给了 Animal 类型的引用变量 animal。
●调用 animal.sound() 时,由于多态的存在,实际调用的是 Dog 类中重写后的 sound() 方法。
●由于 animal 是 Animal 类型的引用,它只能访问 Animal 类中定义的方法,不能直接访问 Dog 类特有的 bark() 方法,若尝试调用会导致编译错误。
错误原因分析:
这个错误通常出现在使用向上转型后,尝试通过父类引用调用子类特有的方法时。向上转型会使父类引用只能访问父类中定义的方法,而不能直接访问子类特有的方法。
解决办法:
① 向下转型
如果你确定 Animal
引用指向的是 Dog
类的对象,可以通过向下转型将其转换为 Dog
类型的引用,然后再调用 bark
方法。为了避免 ClassCastException
异常,建议在转型前使用 instanceof
进行类型检查。(下面讲)
②在父类中定义方法(如果适用)
如果 bark
方法对于所有 Animal
子类都有一定的通用性,可以考虑将 bark
方法定义在 Animal
类中,并在子类中进行重写。不过这种方法可能不太适用于 bark
这种明显是狗特有的行为。
// 父类
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
public void bark() {
System.out.println("默认的叫声");
}
}
// 子类
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗发出汪汪声");
}
@Override
public void bark() {
System.out.println("狗在汪汪叫");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.bark(); // 可以正常调用
}
}
通过上述两种方法,你可以解决 “Cannot resolve method 'bark' in 'Animal'” 错误。在实际开发中,向下转型是更常用的解决方式,尤其是当子类有特有的行为时。
(2).示例代码2
父类Animal:
public class Animal {
public void move(){
System.out.println("动物在移动");
}
public void eat(){
System.out.println("正在吃东西");
}
}
子类Cat:
public class Cat extends Animal{
@Override
public void move() {
System.out.println("猫在走猫步");
}
/**
* 这个方法/行为是子类特有的。父类没有。
*/
public void catchMouse(){
System.out.println("猫在抓老鼠");
}
}
子类Bird:
public class Bird extends Animal{
@Override
public void move() {
System.out.println("鸟儿在飞翔");
}
/**
* 这个方法也是子类特有的。
*/
public void sing(){
System.out.println("鸟儿在歌唱!");
}
}
测试类Test01:
public class Test01 {
public static void main(String[] args) {
Animal a1=new Cat();
a1.move();
}
}
运行结果:
解释:
java程序包括两个重要的阶段:
第一阶段:编译阶段
●在编译的时候,编译器只知道a1的类型是Animal类型。
●因此在编译的时候就会去Animal类中找move()方法。
●找到之后,绑定上去,此时发生静态绑定。
●能够绑定成功,表示编译通过。
第二阶段:运行阶段
●在运行的时候,堆内存中真实的java对象是Cat类型。
●所以move()的行为一定是Cat对象发生的。
●因此运行的时候就会自动调用Cat对象的move()方法。
●这种绑定称为运行期绑定/动态绑定。
因为编译阶段是一种形态,运行的时候是另一种形态。因此得名:多态。
向上转型(upcasting):
1. 子 --> 父
2. 也可以等同看做自动类型转换
3. 前提:两种类型之间要有继承关系
4. 父类型引用指向子类型对象。这个就是多态机制最核心的语法。
编译时看父,但运行时看具体的对象
添加a1.catchMouse()语句;
解释:
编译错误是因为编译器只知道a1是Animal类型,去Animal类中找 catchMouse()方法了,结果没有找到,无法完成静态绑定,编译报错。
大白话:在运行时才能new对象,在编译的时候,编译器很傻的,编译器只知道a1的类型是Animal,只能去Animal找catchMouse()这个方法 ,而Animal没有这个方法自然会报错,所以说白了,就是程序还没有运行时就报错了
假如现在就是要让a1去抓老鼠,怎么办?
向下转型:downcasting(父--->子)
什么时候我们会考虑使用向下转型?
当调用的方法是子类中特有的方法。
2.向下转型(Downcasting)
向下转型是指将父类引用变量强制转换为子类引用变量。需要注意的是,向下转型必须确保该父类引用实际指向的是要转型的子类对象,否则会在运行时抛出 ClassCastException
异常。通常在向下转型之前会使用 instanceof
运算符进行类型检查。
(1).示例代码1
父类Animal:
// 父类
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
子类Dog:
// 子类
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗发出汪汪声");
}
public void bark() {
System.out.println("狗在汪汪叫");
}
}
子类Cat:
// 另一个子类
class Cat extends Animal {
@Override
public void sound() {
System.out.println("猫发出喵喵声");
}
public void meow() {
System.out.println("猫在喵喵叫");
}
}
测试类Main:
public class Main {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
// 向下转型前进行类型检查
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.sound();
dog.bark();
}
// 下面的代码会引发 ClassCastException
Animal animal2 = new Cat();
// 不进行类型检查直接转型
// Dog dog2 = (Dog) animal2;
// dog2.bark();
if (animal2 instanceof Dog) {
Dog dog2 = (Dog) animal2;
dog2.bark();
} else {
System.out.println("animal2 不是 Dog 类型的对象");
}
}
}
代码解释:
●首先创建了 Animal、Dog 和 Cat 三个类,Dog 和 Cat 类都继承自 Animal 类,且都重写了 sound() 方法,同时各自有特有的方法。
●Animal animal = new Dog(); 进行了向上转型,将 Dog 类的对象赋值给 Animal 类型的引用变量 animal。
●使用 if (animal instanceof Dog) 进行类型检查,判断 animal 引用的对象是否为 Dog 类型的实例。如果是,则进行向下转型 Dog dog = (Dog) animal;,转型成功后就可以调用 Dog 类特有的 bark() 方法。
●Animal animal2 = new Cat(); 同样进行了向上转型,但 animal2 实际指向的是 Cat 类的对象。若不进行类型检查直接将其转型为 Dog 类型,会在运行时抛出 ClassCastException 异常。使用 instanceof 检查后发现 animal2 不是 Dog 类型的对象,就不会进行转型操作,避免了异常的发生。
(2).示例代码2
父类Animal:
public class Animal {
public void move(){
System.out.println("动物在移动");
}
public void eat(){
System.out.println("正在吃东西");
}
}
子类Cat:
public class Cat extends Animal{
@Override
public void move() {
System.out.println("猫在走猫步");
}
/**
* 这个方法/行为是子类特有的。父类没有。
*/
public void catchMouse(){
System.out.println("猫在抓老鼠");
}
}
子类Bird:
public class Bird extends Animal{
@Override
public void move() {
System.out.println("鸟儿在飞翔");
}
/**
* 这个方法也是子类特有的。
*/
public void sing(){
System.out.println("鸟儿在歌唱!");
}
}
测试类Test01:
public class Test01 {
public static void main(String[] args) {
Animal a1=new Cat();
a1.move();
Cat c1=(Cat) a1;
c1.catchMouse();
}
}
运行结果:
注意点:
大前提:不管是向下还是向上,两种类型之间必须要有继承关系。 没有继承关系,编译器报错。
错误分析
Inconvertible types; cannot cast 'lianxi.oop16.Bird' to 'lianxi.oop16.Cat'
这个错误提示表明你尝试将一个 Bird
类型的对象强制转换为 Cat
类型,然而在 Java 里,这两种类型之间没有继承关系,属于不可转换的类型,所以不能进行这样的强制类型转换。
在上述代码中,Bird 和 Cat 类都继承自 Animal 类,但它们是 Animal 类的不同子类,彼此之间没有直接的继承关系,所以将 Bird 对象强制转换为 Cat 类型是不合法的,会导致编译错误。
添加
//多态 Animal x=new Cat(); //向下转型 Bird y=(Bird)x;public class Test01 { public static void main(String[] args) { Animal a1=new Cat(); a1.move(); Cat c1=(Cat) a1; c1.catchMouse(); //多态 Animal x=new Cat(); //向下转型 Bird y=(Bird)x; } }
运行结果:
分析:
●为什么编译的时候可以通过?因为x是Animal类型,Animal和Bird之间存在继承关系,语法没问题,所以编译通过了。
●为什么运行的时候出现ClassCastException(类型转换异常)?因为运行时堆中真实对象是Cat对象,Cat无法转换成Bird,则出现类型转换异常。
●instanceof运算符的出现,可以解决ClassCastException异常。
3.需求:程序运行阶段动态确定对象
如果对象是Cat,请抓老鼠。
如果对象是Bird,请唱歌。
测试类Test01
public class Test01 {
public static void main(String[] args) {
// 多态
Animal a = new Bird();
a.eat(); //父类特有方法
a.move();// 虽然是Animal a,绑定的是Bird
if (a instanceof Cat) {
Cat cat = (Cat)a;
cat.catchMouse();
}else if(a instanceof Bird){
Bird bird = (Bird)a;
bird.sing(); //子类特有方法
bird.move(); //父类,子类共有方法
}
}
}
运行结果:
代码分析:
(1).多态的实现:
Animal a = new Bird();
●这里,a 是一个 Animal 类型的引用,但它实际上指向的是一个 Bird 类型的对象。
●由于 Bird 继承自 Animal,这种用法是合法的。
(2).方法调用:
a.move();
●尽管 a 是 Animal 类型的引用,但在运行时,Java 会根据对象的实际类型(这里是 Bird)来决定调用哪个方法。
●因此,调用的是 Bird 类中重写的 move() 方法,输出 "鸟儿在飞翔"。
(3).方法重写:
●Bird 类重写了 Animal 类的 move() 方法:
@Override
public void move() {
System.out.println("鸟儿在飞翔");
}
●当调用 a.move() 时,Bird 类的 move() 方法被调用,而不是 Animal 类的 move() 方法。
(4).为什么不是调用父类方法?
●Java 的方法调用是基于 运行时类型(Run-time Type)而不是编译时类型(Compile-time Type)。
●编译时类型是 Animal,所以编译器确保 move() 方法存在(父类中有定义)。
●运行时类型是 Bird,所以实际调用的是 Bird 类中重写的 move() 方法。
(5).总结
Animal a = new Bird();
a.move();
●编译时类型:Animal,确保 a 有 move() 方法(继承自父类)。
●运行时类型:Bird,实际调用的是 Bird 类中的 move() 方法,输出 "鸟儿在飞翔"。
所以为什么 a.move() 调用的是子类方法?
①编译时类型 vs 运行时类型:
编译时类型:a 的类型是 Animal,编译器检查 Animal 类是否有 move() 方法。
运行时类型:a 实际指向的是 Bird 对象,JVM 在运行时动态绑定到 Bird 的 move() 方法。
②方法调用规则:
如果子类重写了父类的方法,调用时会优先执行子类的方法。
如果子类没有重写,则调用父类的方法。
这就是多态的核心思想:父类的引用可以指向子类的对象,并调用子类重写的方法。
在Java中,多态性允许一个引用类型变量指向其真实类型的对象,并且根据该对象的实际类型来决定调用哪个方法。这种行为是通过动态绑定(dynamic binding)实现的,它使得程序可以在运行时确定具体要执行的方法版本。
在代码示例中,Animal a = new Bird(); 这一行创建了一个 Bird 类型的对象,并将其赋值给一个 Animal 类型的引用 a。这里发生了向上转型(upcasting),即子类对象被当作父类类型来处理。尽管如此,a 实际上仍然指向的是一个 Bird 对象。
当你调用 a.move(); 时,尽管 a 的静态类型是 Animal,但 JVM 在运行时会检查 a 引用的实际对象类型,发现它是 Bird 类型的一个实例。由于 Bird 类重写了 move 方法,JVM 将调用 Bird 类中的 move 方法实现,而不是 Animal 类中的默认实现。因此,输出结果为“鸟儿在飞翔”。
这个过程涉及到以下几个关键概念:
①动态绑定:这是Java实现多态的方式之一,指的是在运行时根据对象的实际类型来选择调用的方法。对于非静态方法,Java使用虚方法表(vtable)或方法表(method table)来支持动态绑定。每个对象都有一个指向其类的方法表的指针,该表包含了所有可被动态调用的方法的地址。
②方法重写(Override):当一个子类提供了与父类相同签名(名称、参数列表)的方法定义时,就称为方法重写。在这个例子中,Bird 类重写了 Animal 类的 move 方法,从而改变了它的行为。
③向上转型:当你将一个子类对象赋值给父类类型的变量时,这被称为向上转型。这样做不会丢失任何信息,因为子类包含所有父类的信息,但它隐藏了子类特有的部分,除非你进行向下转型。
综上所述,a.move(); 输出“鸟儿在飞翔”的原因是由于Java的动态绑定机制确保了即使通过父类引用调用方法,也会根据实际对象的类型调用相应的方法实现。在这种情况下,尽管 a 是 Animal 类型的引用,但由于它实际上指向的是一个 Bird 对象,所以最终调用了 Bird 类中重写的 move 方法。这也正是面向对象编程中多态性的核心所在——同一接口可以根据对象的不同表现出不同的行为。
4.总结
●向上转型是自动类型转换,是安全的,它可以实现多态,但会失去访问子类特有方法的能力。
●向下转型是强制类型转换,需要谨慎使用,在转型前使用 instanceof 进行类型检查可以避免 ClassCastException 异常。
四、多态在开发中的应用
1.开闭原则(OCP)
开闭原则(Open-Closed Principle, OCP)是面向对象设计的基石,其核心目标是构建高内聚、低耦合的系统架构,使系统既能灵活扩展新功能,又能保持核心逻辑的稳定性。
(1)、原则本质
-
开放扩展:通过新增代码实现功能扩展(如新增类、接口实现)
-
关闭修改:避免修改已有运行良好的类、接口、方法
-
关键手段:抽象化(接口/抽象类) + 多态(实现类替换)
2.OCP与多态的示例
(1).问题描述
假设现有代码实现主人(Master类)喂养宠物猫(Cat类)的功能。现在需要扩展功能,让主人也能喂养宠物狗(Dog类)。但当前代码设计存在缺陷:每次新增宠物类型时,必须修改 Master 类的源码,这直接违反了开闭原则(OCP)。
代码如下:
Cat类:
/**
* 宠物猫
*/
public class Cat {
public void eat(){
System.out.println("猫吃鱼");
}
}
Dog类:
public class Dog {
public void eat(){
System.out.println("狗狗啃骨头!");
}
}
Master类:
/**
* 主人类
*/
public class Master {
public void feed(Cat c) {
c.eat();
}
public void feed(Dog d){
d.eat();
}
}
测试类Test:
/**
* 这个案例没有使用多态机制,看看设计上有什么缺陷?
* 不符合OCP。不符合开闭原则。(因为这个功能的扩展是建立在修改Master类的基础之上的。)
* OCP倡导的是什么?进行功能扩展的时候,最好不要修改原有代码,最好是以新增代码来完成扩展。
* 对修改关闭。对扩展开放。
*/
public class Test {
public static void main(String[] args) {
// 创建宠物猫对象
Cat c = new Cat();
Dog d = new Dog();
// 创建主人对象
Master zhangsan = new Master();
// 主人喂猫
zhangsan.feed(c);
zhangsan.feed(d);
}
}
(2).问题分析:
-
当新增
Dog
类时,必须修改Master
类,添加public void feed(Dog d)
方法。 -
如果未来需要喂养其他宠物(如狗、蛇、鸟),
Master
类将变得臃肿且难以维护。 -
根本原因:
Master
类直接依赖具体实现(Cat
),而非抽象层。
(3).解决方案:通过多态与OCP重构
步骤1:定义抽象层
创建抽象类 Pet,所有具体宠物继承它并实现 eat() 方法。
// Pet.java(抽象层)
public abstract class Pet {
public abstract void eat(); // 定义公共行为规范
}
public class Pet {
//更高的抽象,到具体类在实现
public void eat(){
}
}
步骤2:实现具体宠物类
Cat类:
public class Cat extends Pet{
public void eat(){
System.out.println("猫吃鱼");
}
}
Dog类:
public class Dog extends Pet
{
public void eat(){
System.out.println("狗狗在啃骨头");
}
}
步骤3:重构Master类
让 Master 依赖抽象类 Pet,而非具体宠物类。
import lianxi.oop17.Cat;
import lianxi.oop17.Dog;
public class Master {
public void feed(Pet p) //等同于Pet p =new Cat();
// 参数类型为抽象类
{
p.eat();// 编译时调用Pet,运行时则调用Cat或Dog
// 多态调用:运行时根据实际对象类型执行对应方法
}
}
步骤4:测试扩展性
新增宠物类型(如 Snake)时,无需修改 Master 类:
新增Snake类:
public class Snake extends Pet{
@Override
public void eat() {
System.out.println("蛇吞象");
}
}
测试类Test:
public class Test {
public static void main(String[] args) {
//创建宠物
Cat c=new Cat();
Dog d=new Dog();
//创建主人
Master zhangsan=new Master();
//喂养
zhangsan.feed(c);
zhangsan.feed(d);
zhangsan.feed(new Cat());
zhangsan.feed(new Dog());
zhangsan.feed(new Snake());
}
}
运行结果:
(4).扩展性验证
当需要新增宠物类型时:
①新增类:例如 Bird extends Pet。
②实现方法:重写 eat()。
③直接使用:zhangsan.feed(new Bird())。
无需修改任何已有类(Master、Cat、Dog 等),完全符合 OCP!
(5).对比原始代码与重构后代码
场景 | 原始代码 | 重构后代码 |
---|---|---|
新增宠物类型 | 必须修改 Master 类 | 只需新增类,无需修改已有代码 |
Master 类的职责 | 需要了解所有宠物类型 | 只依赖抽象层 Pet |
单元测试范围 | 每次新增宠物需重新测试 Master | 仅需测试新增的宠物类 |
系统稳定性 | 修改风险高 | 核心模块(Master )保持稳定 |
(6).总结
通过多态和抽象层设计:
-
彻底解耦:
Master
类与具体宠物类无关。 -
符合 OCP:功能扩展通过新增代码完成,而非修改旧代码。
-
提升可维护性:系统核心模块保持稳定,变更影响范围最小化。
最终效果:当主人想养新宠物时,只需“买新宠物”(写新类),无需“改造房子”(改 Master
类)!
五、多态总结
1.多态代码示例
(1). 父类 Animal
public class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}
(2). 子类 Cat
和 Dog
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃鱼");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗在啃骨头");
}
public void watchHouse() {
System.out.println("狗看家");
}
}
(3). 测试类 Test
public class Test {
public static void main(String[] args) {
// 向上转型:父类引用指向子类对象
Animal animal1 = new Cat(); // 多态
Animal animal2 = new Dog(); // 多态
// 调用重写的方法
animal1.eat(); // 输出:猫在吃鱼
animal2.eat(); // 输出:狗在啃骨头
// 无法调用子类特有的方法
// animal1.catchMouse(); // 编译错误
// animal2.watchHouse(); // 编译错误
// 向下转型:调用子类特有方法
if (animal1 instanceof Cat) {
Cat cat = (Cat) animal1; // 向下转型
cat.catchMouse(); // 输出:猫抓老鼠
}
if (animal2 instanceof Dog) {
Dog dog = (Dog) animal2; // 向下转型
dog.watchHouse(); // 输出:狗看家
}
}
}
2.多态的特点
向上转型:
父类引用指向子类对象。
只能调用父类中定义的方法,不能调用子类特有的方法。
如果子类重写了父类的方法,调用的是子类的方法。
向下转型:
将父类引用强制转换为子类类型。
可以调用子类特有的方法。
必须使用
instanceof
进行类型检查,避免ClassCastException
。方法调用规则:
编译时:检查父类是否有该方法。
运行时:调用子类重写的方法。
3、多态的优点
-
提高代码的扩展性:新增子类时,无需修改父类代码。
-
提高代码的复用性:通过父类引用调用方法,统一处理不同子类对象。
-
降低耦合性:父类和子类之间的依赖关系更松散。
4、注意事项
-
属性没有多态性:
-
多态仅适用于方法,不适用于属性。
-
属性的值取决于引用类型,而不是实际对象类型。
class Fu { int num = 10; } class Zi extends Fu { int num = 20; } public class Test { public static void main(String[] args) { Fu fu = new Zi(); System.out.println(fu.num); // 输出:10(属性没有多态性) } }
-
-
静态方法没有多态性:
-
静态方法与类绑定,不依赖于对象。
-
调用静态方法时,取决于引用类型。
class Fu { static void method() { System.out.println("父类静态方法"); } } class Zi extends Fu { static void method() { System.out.println("子类静态方法"); } } public class Test { public static void main(String[] args) { Fu fu = new Zi(); fu.method(); // 输出:父类静态方法 } }
-
5、总结
特性 | 说明 |
---|---|
向上转型 | 父类引用指向子类对象,只能调用父类方法。 |
向下转型 | 将父类引用强制转换为子类类型,需用 instanceof 检查。 |
方法重写 | 子类重写父类方法,运行时调用子类方法。 |
属性无多态 | 属性的值取决于引用类型,而非对象类型。 |
静态方法无多态 | 静态方法与类绑定,调用时取决于引用类型。 |
掌握多态,能够写出更灵活、可扩展的代码!