目录
1. 动态绑定
1.1 定义
动态绑定(Dynamic Binding)是面向对象编程中实现多态性的核心机制,它允许在运行时根据对象的实际类型(而非引用类型)来决定调用哪个方法。Java 通过动态绑定实现了方法重写(Override)的运行时行为。
1.2 为什么需要动态绑定?
动态绑定解决了继承体系中方法调用的灵活性问题,主要目的是:
支持多态性:允许父类引用指向子类对象,并在运行时调用子类重写的方法。
遵循开闭原则:对扩展开放,对修改关闭。当新增子类时,无需修改现有代码即可实现新行为。
代码复用与扩展性:通过统一接口(父类方法)处理不同子类的特殊实现。
1.3 静态绑定 vs 动态绑定
维度 | 静态绑定(Static Binding) | 动态绑定(Dynamic Binding) |
---|---|---|
绑定时机 | 编译时 | 运行时 |
依据 | 变量的静态类型(声明类型) | 对象的实际类型(实例化类型) |
适用场景 | 方法重载(Overload)、私有方法、静态方法 | 方法重写(Override) |
灵活性 | 低(编译后无法改变) | 高(可根据运行时对象类型动态调整) |
1.4 示例
1.4.1 静态绑定
class Person {
// 静态方法(静态绑定)
public static void staticTalk() {
System.out.println("Person的静态talk");
}
// 成员属性(静态绑定)
public String name = "Person的名字";
}
class Teacher extends Person {
// 静态方法(隐藏父类方法,不是重写)
public static void staticTalk() {
System.out.println("Teacher的静态talk");
}
// 成员属性(隐藏父类属性,不是重写)
public String name = "Teacher的名字";
}
// 测试类
public class BindingTest {
public static void main(String[] args) {
// 父类引用指向子类对象(和之前多态语法一样)
Person p = new Teacher();
// 1. 调用静态方法:静态绑定,看引用声明类型(Person)
p.staticTalk(); // 输出:Person的静态talk(不是Teacher的!)
// 2. 访问成员属性:静态绑定,看引用声明类型(Person)
System.out.println(p.name); // 输出:Person的名字(不是Teacher的!)
}
}
1.4.2 动态绑定
class Person {
// 非静态、非final、非private方法(可重写,动态绑定)
public void talk() {
System.out.println("人说");
}
}
class Teacher extends Person {
// 重写父类talk()(满足重写条件)
@Override
public void talk() {
System.out.println("老师说");
}
}
class Boss extends Person {
// 重写父类talk()
@Override
public void talk() {
System.out.println("老板说");
}
}
// 测试类
public class BindingTest {
public static void main(String[] args) {
Person p; // 父类引用(声明类型固定为Person)
// 1. p实际指向Teacher对象(运行时确定)
p = new Teacher();
p.talk(); // 动态绑定,看对象类型(Teacher)→ 输出:老师说
// 2. p实际指向Boss对象(运行时改变)
p = new Boss();
p.talk(); // 动态绑定,看对象类型(Boss)→ 输出:老板说
}
}
1.5 从不同角度分析动态绑定
1.5.1 从面向对象的角度:
动态绑定指的是:一个类指向多个实现子类
1.5.2 从面向函数式编程的角度:
动态绑定指的是:一个函数式接口可以指向多个函数表达式(符合参数列表相同,返回值类型相同)
- 例1:
package com.hy.demo3;
//函数式接口注解(强制检查:仅允许一个抽象方法)
@FunctionalInterface
public interface Convert {
// 抽象方法:定义“int转String”的行为标准
public String intToString(int num);
}
package com.hy.demo3;
public class Operator {
// 静态方法:实现“int转String”的具体逻辑
public static String changeType(int num) {
// 核心逻辑:调用JDK方法将int转为String
return String.valueOf(num);
}
}
package com.hy.demo3;
public class Test {
// 核心方法:show(行为参数化的载体)
// 参数1:num(要转换的数字,数据)
// 参数2:c(Convert接口对象,代表“int转String”的行为)
public static void show(int num, Convert c) {
// 执行行为:调用Convert接口的方法,传入数据num,打印结果
System.out.println(c.intToString(num));
}
public static void main(String[] args) {
// 调用方式1:Lambda表达式(自定义行为,忽略传入的num)
Test.show(100, (int n) -> { return "200"; });
// 调用方式2:Lambda表达式(调用Operator的静态方法,使用传入的num)
Test.show(100, (int n) -> { return Operator.changeType(n); });
// 调用方式3:方法引用(简化方式2的Lambda,格式:类名::静态方法)
Test.show(100, Operator::changeType);
}
}
输出结果:
200
100
100
- 例2:
package com.hy.demo4;
// 函数式接口注解:强制校验“仅包含一个抽象方法”
@FunctionalInterface
public interface Operator {
// 抽象方法:定义“二元运算”的标准
// 参数:a、b(两个待运算的int);返回值:运算结果(int)
public int operatorNum(int a, int b);
}
package com.hy.demo4;
public class Test {
// 核心方法:show(实现“数据+行为”的绑定)
// 参数1/2:a、b(待运算的具体数据)
// 参数3:op(Operator接口对象,代表“要执行的运算行为”)
public void show(int a, int b, Operator op) {
// 执行运算:调用Operator接口的抽象方法,传入数据a、b,得到结果
int result = op.operatorNum(a, b);
System.out.println("result值为:" + result);
}
public static void main(String[] args) {
// 1. 创建Test实例(非静态方法show需要实例调用)
Test t = new Test();
// 2. 第一次调用show:传递“加法行为”(Lambda表达式)
t.show(10, 20, (a, b) -> { return a + b; });
// 3. 第二次调用show:传递“除法行为”(Lambda表达式)
t.show(10, 20, (a, b) -> { return b / a; });
}
}
输出结果:
result值为:30
result值为:2
2. 方法重写与方法重载
2.1 定义与目的
-
方法重写(Override)
发生在父子类之间,子类重新实现父类中已有的同名、同参数、同返回值类型的方法,用于修改或扩展父类行为。
核心目的:实现多态性(运行时动态绑定)。 -
方法重载(Overload)
发生在同一个类中(或父子类中),多个方法具有相同名称但参数列表不同。
核心目的:通过参数的差异化提供相似功能的多种实现方式。
2.2 方法重写的条件
方法重写,也叫运行时多态,在继承关系中,满足以下三个条件的方法叫做方法重写:
① 子类和父类具有相同的方法名
② 相同的参数列表(包含相同的参数)
③ 相同的返回值类型
package com.hy.chapter5;
// 父类
public class Person {
public void talk() {
System.out.println("人说");
}
}
// 子类
public class Boss extends Person{
// 方法重写
public void talk() {
System.out.println("老板说");
}
}
public class Teacher extends Person{
// 方法重写
public void talk() {
System.out.println("老师说");
}
}
package com.hy.chapter5;
public class BindObject {
public void bindTalk(Person p) {
p.talk();
}
}
package com.hy.chapter5;
public class Test {
public static void main(String[] args) {
// Person p = new Boss();
// p.talk();
// p = new Teacher();
// p.talk();
BindObject bindObject = new BindObject();
bindObject.bindTalk(new Teacher());
bindObject.bindTalk(new Boss());
//方法重写(运行机制),就是在运行期间,根据实际产生的对象,来决定调用那个重写方法
}
}
2.3 关键区别对比
维度 | 方法重写(Override) | 方法重载(Overload) |
---|---|---|
发生范围 | 父子类之间 | 同一个类中(或父子类中) |
方法签名 | 必须与父类方法完全相同(名称、参数、返回值) | 方法名称相同,但参数列表必须不同(类型、顺序或数量) |
返回值类型 | 必须与父类方法相同(或协变类型,Java 5+) | 可以不同 |
访问修饰符 | 子类方法不能比父类方法更严格(如父类protected,子类不能是private) | 无限制 |
异常声明 | 子类方法不能抛出比父类更多的异常(子类异常≤父类异常) | 无限制 |
调用机制 | 运行时根据对象实际类型决定(动态绑定) | 编译时根据参数类型和数量决定(静态绑定) |
2.4 示例代码
2.4.1 方法重写示例
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}
class Cat extends Animal {
@Override
public void makeSound() { // 重写父类方法
System.out.println("Meow");
}
}
2.4.2 方法重载示例
class Calculator {
public int add(int a, int b) { // 两个整数相加
return a + b;
}
public double add(double a, double b) { // 两个小数相加
return a + b;
}
public int add(int a, int b, int c) { // 三个整数相加
return a + b + c;
}
}
2.5 常见混淆点
-
重写 vs 重载
重写是 “父子类的垂直关系”,重载是 “同类的水平关系”。 -
参数列表不同
重载要求参数类型、数量或顺序至少有一项不同,仅返回值不同无法构成重载。例如:public void method(int a) {} public int method(int a) { return a; } // 编译错误:仅返回值不同