方法链与绑定
method-chaining、静态/动态绑定。
25 分钟
方法链与绑定
这一课你将能做到
- 手写一个返回
this的链式 Builder,并说出链式调用不改变绑定规则的原因 - 区分静态绑定与动态绑定,指出 static 方法「看起来被覆盖了」其实是隐藏
- 在 Stream 链中标出中间操作与终结操作,解释惰性求值何时触发
为什么要学
builder.name("x").amount(3).build()、list.stream().filter(...).map(...).toList()——企业代码里两处最常见的链式写法,都依赖同一个机制:方法返回自身引用或同类新对象。看不懂绑定规则,就会在「父类引用调静态方法」这类问题上踩坑还找不出原因。
面试角度:「static 方法能被重写吗」「动态绑定的实现原理」「Builder 模式解决什么问题」是 OOP 追问链的标配第三问,答到 vtable 一层就是加分项。
先建立图像
链式调用的本质:
obj.a() → 返回 this(还是 obj)
.b() → 继续在 obj 上调用
.build() → 返回最终产品
绑定规则速查:
┌────────────────────┬──────────┬────────────────────────┐
│ 调用类型 │ 绑定时机 │ 看谁 │
├────────────────────┼──────────┼────────────────────────┤
│ static 方法 │ 编译期 │ 声明(引用)类型 │
│ private/final 方法 │ 编译期 │ 声明类型(不可覆盖) │
│ 重载的取舍 │ 编译期 │ 参数的编译期类型 │
│ 普通实例方法 │ 运行期 │ 实际对象类型(多态) │
└────────────────────┴──────────┴────────────────────────┘
跟着做
步骤 1:静态绑定 vs 动态绑定——静态方法是「隐藏」不是覆盖
新建 BindingDemo.java,完整可运行:
public class BindingDemo {
static class Animal {
static String kind() { return "animal"; } // 静态方法
String speak() { return "..."; } // 实例方法
}
static class Dog extends Animal {
static String kind() { return "dog"; } // 这是「隐藏」,不是覆盖
@Override
String speak() { return "汪"; } // 覆盖,动态绑定
}
public static void main(String[] args) {
Animal a = new Dog();
System.out.println(a.speak()); // 汪:运行期看实际对象 Dog
System.out.println(a.kind()); // animal:编译期看声明类型 Animal
Dog d = new Dog();
System.out.println(d.kind()); // dog:声明类型是 Dog 才走 Dog.kind()
}
}
运行:java BindingDemo.java。第三行输出是 animal——a.kind() 等价于 Animal.kind(),编译器按声明类型解析静态调用。IDE 会对此给出黄色警告,别忽略它。
步骤 2:手写链式 Builder
继续在 BindingDemo.java 里追加内部类与 main 片段:
static class OrderBuilder {
private String sku;
private int amount;
private boolean urgent;
OrderBuilder sku(String v) { this.sku = v; return this; }
OrderBuilder amount(int v) { this.amount = v; return this; }
OrderBuilder urgent(boolean v) { this.urgent = v; return this; }
String build() {
if (sku == null || amount <= 0) throw new IllegalStateException("参数未就绪");
return "Order[" + sku + ", " + amount + ", urgent=" + urgent + "]";
}
}