1) 类型推断(Type Inference)
1.1 Java 的推断(JDK 8+)
- 变量/菱形:目标类型参与推断
javascript
Map<String, Integer> m = new HashMap<>(); // <> 由左侧推断
- 泛型方法:从实参 & 目标类型同时推断类型形参 <T,...>
scss
static <T> T id(T x) { return x; }
Integer a = id(1); // T := Integer
Number b = id(1); // 目标类型作用:T := Number(可向上)
- 最小上界(LUB) :混合实参取公共父型
ini
var x = List.of(1, 2.0); // List<Number> ? 不是。JDK 选择 LUB:List<Serializable & Comparable<?> & Number>
- Lambda/方法引用的"目标类型" :
javascript
void f(Predicate<String> p) {}
void f(Comparator<String> c) {}
f(s -> s.isEmpty()); // 需要目标类型才能决定是哪个 f;否则二义性
list.sort(String::compareToIgnoreCase); // 方法引用同理
- 通配符捕获(capture)辅助推断:把 List<?> 交给私有辅助方法 ,让编译器"捕获"到确切 T。
1.2 Kotlin 的推断
- 局部/属性推断:val x = 1 → Int;函数返回可推断则省略返回类型(顶层公共函数建议显式写)。
- 函数/泛型 :从实参 + 期望类型(expected type)共同推断
kotlin
fun <T> id(x: T): T = x
val a: Number = id(1) // 期望类型驱动 T := Number
-
Lambda 参数类型:由目标函数类型反向推断:list.map { s -> s.length } 中 s:String。
-
Builder/上下文推断("builder inference"):buildList { add(1); add(2) } 体内的 add 反推 List。
-
协/逆变影响:List 读、MutableList 写;投影会限制你能否写入,从而影响推断空间。
2) 重载(Overload Resolution)
2.1 Java 的匹配顺序(简化记忆)
适用性 → 最具体 → tie-breaker
- 适用性:能用"方法调用转换"把实参转成形参(包含原始/装箱/拆箱/可变参)。
- 最具体:形参类型更"窄"的优先。
- 决胜顺序 (大致):精确匹配 > 基本类型扩展 > 装箱/拆箱 > varargs。
scss
void f(int) {}
void f(Integer) {}
f(1); // 选 f(int)
f(Integer.valueOf(1)); // 选 f(Integer)
void g(long) {}
void g(Integer) {}
g(1); // 选 g(long)(原始拓宽优于装箱)
-
返回类型不参与重载决议(必要条件:参数列表不同)。
-
Lambda/方法引用常见二义性 :两个重载都接受函数式接口时,必须加显式目标类型或强转。
2.2 Kotlin 的匹配规则要点
- 参数列表不同才构成重载(返回类型同样不参与)。
- 默认参数/命名实参 :同一函数会在 JVM 生成若干合成重载(@JvmOverloads 可控),可能与手写重载冲突。
- 扩展函数 vs 成员函数:成员优先于扩展;同名扩展按可见范围/导入优先。
- SAM 转换 :传 Lambda 到 Java 方法的两种函数式接口重载也会二义;需加类型标注或命名参数 disambiguate。
3) 桥方法(Bridge Method)
3.1 是什么 & 为什么需要
编译器生成的 synthetic + bridge 方法,用来在 泛型擦除 或 协变返回类型 场景下维持 多态一致性与二进制兼容 。JVM 判定"重写"依赖方法签名 (参数类型 + 名称;返回类型也参与匹配规则),泛型擦除可能让子类的方法不再匹配父类声明;桥方法用来"补一层适配"。
典型例子 1:泛型参数擦除导致的方法签名不匹配
csharp
interface Cmp<T> { int compareTo(T o); } // 擦除后:compareTo(Object)
final class Money implements Cmp<Money> {
public int compareTo(Money o) { ... } // 这行并不匹配 compareTo(Object)
// 编译器生成桥:
public /*bridge*/ int compareTo(Object o) {
return compareTo((Money)o);
}
}
典型例子 2:协变返回(子类返回更具体)
scala
class A { A clone() {...} }
class B extends A { @Override B clone() {...} } // 允许的协变返回
// 某些组合(含泛型)下为保持二进制兼容也会生成 bridge
你能在 javap -v 或 IDE 反编译里看到 ACC_BRIDGE, ACC_SYNTHETIC 标志的方法。
3.2 Kotlin 也会生成 bridge
- 覆盖 协变返回 、带泛型的重写 、以及 接口默认方法 等情况,字节码同样会出现桥方法,保证从 Java 调用时行为一致。
4) 三者如何互相影响(高频坑位)
-
推断 × 重载
- Lambda/方法引用要靠"目标类型"推断;两个可行重载 都能接收 Lambda 时 → 二义性。
- 解决:显式类型实参 、目标类型强转 、或为 Lambda 参数/返回标注类型。
javascript
void h(Function<String,Integer> f) {}
void h(Predicate<String> p) {}
h(s -> 1); // 二义 → 强转:h((Function<String,Integer>) (s -> 1));
-
bridge × 重载
- bridge 只为保证"重写"一致性,不参与你写的重载选择 ;但反编译时看到多一个同名方法别误以为是你定义的重载。
-
Kotlin 默认参数 × 重载 × SAM
- @JvmOverloads 生成的 JVM 重载容易与已有 Java 重载冲突。
- 同一函数既有默认参数又想接 Lambda(SAM)时,建议不混搭或显式区分函数名。
-
通配符/投影影响推断
- List<? extends T>/MutableList 基本只读,写入受限会让推断失败;必要时引入中间变量/显式类型帮助推断。
5) 可抄用示例
5.1 Java:安全的 copy(PECS + 推断)
scss
static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (T e : src) dst.add(e);
}
List<Number> dst = new ArrayList<>();
List<Integer> src = List.of(1,2,3);
copy(dst, src); // T 推断为 Integer
5.2 Java:Lambda 二义性消歧
javascript
void f(Predicate<String> p) {}
void f(Function<String,Integer> g) {}
f((Predicate<String>) s -> true); // 或:
f(((Function<String,Integer>) s -> s.length()));
5.3 Kotlin:Builder 推断 + 显式期望类型
scss
val xs = buildList { add(1); add(2) } // List<Int>
val ys: MutableList<Number> = buildList { // 期望类型驱动为 Number
add(1); add(2.0)
}
5.4 Kotlin/Java:观察桥方法(简化)
csharp
interface Box<T> { T get(); }
class StrBox implements Box<String> {
public String get() { return "x"; }
// 编译器生成:
public /*bridge*/ Object get() { return get(); }
}
6) 排坑清单
- Java 返回类型不参与重载;Kotlin 也一样(少数场景会借助"期望类型"挑选,但声明上仍需参数差异)。
- Lambda/方法引用造成的重载二义性 :加 强转/类型参数/命名参数 消歧。
- 看到 bridge 不要手动写;那是编译器为多态/擦除兜底生成的。
- Kotlin 暴露给 Java 的 API:留意 @JvmOverloads/@JvmName/@JvmWildcard,避免重载/通配冲突。
- 泛型推断失败就显式 :、as T(Kotlin 慎用)、或引入具名临时变量承载期望类型。
- 反编译时"多出来的方法"先看标志位:bridge, synthetic 即为编译器生成。
一句话总结
类型推断 让你少写类型;重载 决定"同名不同参"选谁;桥方法 保证"擦除后仍能正确重写"。碰到 Lambda/泛型时,这三者会相互作用:先用期望类型 与显式标注 把推断稳定下来,再让重载选择清晰,余下交给编译器生成的 bridge 去维护多态一致性与二进制兼容。