Polymorphism(多态)
1. 多态的优点
应用的两个条件:①父类的引用指向子类对象 ②:子类重写父类的方法
1.静态绑定(Static Binding)
- 引用变量绑定运行时不可变对象,方法调用与定义的绑定在编译期完成。即在编译阶段就知道调用的具体函数
- 英文:When a reference variable is bound to an unchangeable object at runtime, or method invocation binding is done at compile time.
2.动态绑定 /延迟绑定(Dynamic Binding / Late Binding)
- 绑定在运行时可变,如多态引用场景,绑定决策在执行阶段完成。即只有在执行阶段才能知道调用的具体函数
- 英文:Binding changeable at runtime (e.g., polymorphic references, binding decision during execution).
3.多态(Polymorphism)
-
面向对象编程基本原则,指同一事物具多种形态。
-
英文:Fundamental OOP principle; means something with multiple forms.
-
多态引用(Polymorphic Reference)
:
- 变量可在不同时间指向不同类型对象。
- 英文:A variable referring to different object types at different times.
- Java 创建方式:通过 inheritance(继承)或 interfaces(接口)。
4.多态作用
- 支持编写含 late binding references(晚绑定引用)的程序。
- 英文:Enables writing programs with late binding references in OOP.
5.继承与对象引用
-
继承体系中的引用规则
- 在继承层级中,若引用变量声明为层级树中的父类类型,该引用可指向层级内任意类的实例。(父类的引用指向子类的对象)
- 英文:In inheritance hierarchy, a reference (declared as parent class type) can point to instances of any class in the hierarchy.
-
Object 类的引用特性
- Java 中所有类隐式或显式继承自
Object
类,因此Object
类型的引用变量可指向任意类实例。 - 英文:All Java classes are subclasses of
Object
. Thus, anObject
-type reference variable can refer to any class instance.
- Java 中所有类隐式或显式继承自
-
代码示例说明
-
示例:
Account account; account = new CCBAccount(); // This is a valid assignment account = new ICBCAccount(); account.transferTo();
-
解释:声明为父类(
Account
)的引用,可动态指向子类(CCBAccount
、ICBCAccount
)实例,体现多态引用在继承体系中的灵活应用。
-
2. 多态实践的三个方法:
1. 方法重载(Method Overloading)
- 核心定义:
- 属于编译时多态(compile-time polymorphism),方法名相同,但形式参数列表(参数数量、类型、顺序)不同。
- 英文:Overloaded methods (compile-time polymorphism) share the same name but have different formal argument lists.
- 返回类型规则:
- 方法的返回类型可以相同,也可以不同,非重载的判定条件。=
- 英文:They may or may not have the same return type.
- 多态中的角色:
- 是 Java 中多态的三种实践形式之一(另外两种为通过继承的方法重写、通过接口的方法重写)。
- 英文:In Java, method overloading is one of the three distinct forms of polymorphism manifestation.
注意:
在 Java 中,函数参数相同但返回类型不同,不属于方法重载,原因如下:
- 重载的核心判定条件:方法重载要求方法名相同,且 参数列表(参数类型、数量、顺序)必须不同。编译器通过参数列表区分重载方法,而非返回类型。
- 编译逻辑限制:若仅返回类型不同,参数相同,编译器无法确定调用哪个方法(例如调用时无法通过返回值约束调用行为),会直接报错 “duplicate method”,因此这种情况不满足重载的定义。
代码示例
public class Overloading {
public int Caculate(int a, int b) {
return a + b;
}
public double Caculate(double a, double b) {
return a + b;
}
public int Caculate(int a, int b, int c) {
return a + b + c;
}
public static void main(String[] args) {
Overloading o = new Overloading();
System.out.println(o.Caculate(2, 3));
System.out.println(o.Caculate(3.56, 4.56));
System.out.println(o.Caculate(3,4,5));
}
}
2. 方法重写/覆盖(Override)
- 有效重写条件:
- 参数类型、顺序完全相同,返回类型一致,且访问权限不低于原方法(
identical argument types/order, return type, not less accessible
)。 - 不能抛出原方法未声明的受检异常(
must not throw undeclared checked exceptions
)。
- 参数类型、顺序完全相同,返回类型一致,且访问权限不低于原方法(
- 执行逻辑:决定调用哪个方法版本,基于对象的实际类型,而非引用变量的类型(
based on actual object type, not reference variable type
)。 - final修饰的方法不可以被覆盖
- 不允许缩小父类的放回类型
@Override 覆盖父类Account中的同名方法(withdraw)
public void withdraw(double amount) {
if(getBalance()<amount) {
System.out.println("Insufficient funds");
System.out.println("You have not enough money to withdraw");
}
else {
super.withdraw(amount);
}
}
3.接口(interface)
会单独介绍这里不做讲解
3.引用数据类型转换
(1)、向上转换(Upcasting)
- 定义:将子类对象转换为父类类型,属于自动类型转换,安全且无需额外操作。
- 核心作用:利用多态特性,统一处理不同子类对象,调用父类定义的方法(实际执行子类重写后的逻辑)。
- 代码示例(垃圾分类场景):
public class RecyclableRubbish extends Rubbish { // 改为静态内部类
@Override
public void Dispose() {
System.out.println("RecyclableRubbish");
}
}
public class Rubbish { // 改为静态内部类
public void Dispose() {
System.out.println("Rubbish");
}
}
public class Main {
public static void main(String[] args) {
RecyclableRubbish R1 = new RecyclableRubbish();
Rubbish R2 = R1;
R2.Dispose();
}
}
(2)、向下转换(Downcasting)
- 定义:将父类对象转换为子类类型,属于强制类型转换,需通过
instanceof
预判,否则可能引发ClassCastException
异常。 - 核心作用:当确认父类引用实际指向子类对象时,转换后可访问子类特有的属性或方法。
- 代码示例(垃圾分类场景):
public class RecyclableRubbish extends Rubbish { // 改为静态内部类
@Override
public void Dispose() {
System.out.println("RecyclableRubbish");
}
public void RecyclableMethod(){
System.out.println("With green method");
}
}
public class Main {
public static void main(String[] args) {
RecyclableRubbish R1 = new RecyclableRubbish();
Rubbish R2 = R1;
R1.RecyclableMethod();
R2.Dispose();
// R2.RecyclableMethod();父类无法使用子类特有的方法,需要类型转化
if(R2 instanceof RecyclableRubbish){
RecyclableRubbish R3=(RecyclableRubbish) R2;
R3.RecyclableMethod();
}
else {
System.out.println("error!");
}
}
}
(3)总结
- 向上转换:自动、安全,用于多态场景,统一调用父类定义的方法。
- 向下转换:强制转换,需
instanceof
预判,用于访问子类特有功能。通过合理运用两种转换,可在面向对象编程中灵活处理继承体系下的对象操作,实现代码的扩展性与复用性。
4. 多态中变量与方法的调用:
(1). 非静态变量调用
在多态情况下,非静态变量的调用遵循 “编译看左边,运行看左边” 原则。即编译时看引用变量所属类(父类 )中是否有该变量定义;运行时,实际使用的也是父类中定义的该变量值 。
示例代码:
class Parent {
String name = "parent";
}
class Child extends Parent {
String name = "child";
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.name);
}
}
上述代码中,Parent p = new Child();
,编译时 p
是 Parent
类型,检查 Parent
类有 name
变量可通过编译;运行时,输出的是 Parent
类中 name
变量的值 parent
。
(2). 非静态方法调用
非静态方法调用遵循 “编译看左边,运行看右边” 原则。编译时,看引用变量所属类(父类 )是否有要调用的方法;运行时,看实际创建对象所属类(子类 )中的方法,若子类重写了该方法,就调用子类重写后的方法。
示例代码:
class Animal {
public void speak() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.speak();
}
}
这里 Animal a = new Dog();
,编译时 a
是 Animal
类型,Animal
类有 speak
方法可通过编译;运行时,实际创建的是 Dog
对象,Dog
类重写了 speak
方法,所以输出 Dog barks
。
(3). 静态变量调用
静态变量属于类本身,在多态中,无论编译还是运行,都看引用变量所属类(父类 ) 。
示例代码:
class Animal {
static String name="Animal";
public void speak(){
System.out.println("I'm an animal");
}
}
class Dog extends Animal {
static String name="Dog";
@Override
public void speak(){
System.out.println("I'm a dog");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
System.out.println(a.name);
System.out.println(Animal.name);//推荐使用类名.属性来调用静态属性,Animal
System.out.println(Dog.name);//推荐使用类名.属性来调用静态属性,Dog
}
}
StaticClass sc = new SubStaticClass();
,编译和运行时,sc
作为 StaticClass
类型引用,使用的都是 StaticClass
类中的 staticVar
,输出 static in parent
。
(4). 静态方法调用
静态方法同样属于类,多态中,编译和运行都依据引用变量所属类(父类 ) 。
示例代码:
class Animal {
static String name="Animal";
public static void speak(){
System.out.println("I'm an animal");
}
}
class Dog extends Animal {
static String name="Dog";
public static void speak(){
System.out.println("I'm a dog");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.speak();
Animal.speak();
Dog.speak();
}
}
MathUtils mu = new AdvancedMathUtils();
,编译和运行时,以 MathUtils
类为准,调用的是 MathUtils
类中的 add
方法,输出 3
。
例题:
答案
(1) B
(2) D
(3) E
(4) D
(5) E
(6) F
解析
- 方法重载:在
BaseClassA
中,多个methodA
方法参数列表不同,构成方法重载。方法调用时,根据传入参数的数量、类型和顺序来匹配对应的方法。 - 方法重写:
DevrivedClassB
中的methodA(int a)
重写了BaseClassA
中的methodA(int a)
,当父类引用指向子类对象调用该方法时,执行子类重写后的逻辑。
- (1)
objA.methodA(12);
:objA
此时是BaseClassA
对象,传入int
类型参数12
,匹配BaseClassA
中public void methodA(int a)
,即 B 方法。 - (2)
objA.methodA(21.3);
:objA
是BaseClassA
对象,传入double
类型参数21.3
,匹配BaseClassA
中public void methodA(double a)
,即 D 方法。 - (3)
objA.methodA(90);
:objA
此时指向DevrivedClassB
对象,传入int
类型参数90
,DevrivedClassB
重写了BaseClassA
中methodA(int a)
,所以执行子类重写后的方法 E。 - (4)
objA.methodA(65.5);
:objA
指向DevrivedClassB
对象 ,传入double
类型参数65.5
,DevrivedClassB
未重写BaseClassA
中methodA(double a)
,所以调用BaseClassA
中的public void methodA(double a)
,即 D 方法。 - (5)
objB.methodA(21);
:objB
是DevrivedClassB
对象 ,传入int
类型参数21
,调用DevrivedClassB
中重写的public void methodA(int a)
,即 E 方法。 - (6)
objB.methodA(11.1, 78.3);
:objB
是DevrivedClassB
对象 ,传入两个double
类型参数,调用DevrivedClassB
中的public void methodA(double a, double b)
,即 F 方法。