界类型参数、递归边界与交叉类型

1) 界类型参数(上界 / 多重上界 / 下界通配)

1.1 上界(Upper Bounds)

目的:限制类型实参必须"至少是某个类型的子类型",从而在方法体内能安全调用上界的方法/属性。

  • Java
javascript 复制代码
<T extends Number> T sum(T a, T b) { ... } // T 至少是 Number
<T extends Number & Comparable<T>> T max(Collection<T> xs) { ... } // 多重上界
  • 规则:多个上界用 &,第一个若为类,其余必须是接口
  • Kotlin
kotlin 复制代码
fun <T : Number> sum(a: T, b: T): T = ...
fun <T> max(xs: Collection<T>): T
  where T : Number, T : Comparable<T> = ...
  • 多重上界用 where 子句;语义与 Java 一致。

何时用

  • 想在泛型算法里用到特定 API(如 compareTo、close()、length())。

  • 经典:Collections.max 的签名在上界约束里就要求 "T 可比较"。

1.2 下界(Lower Bounds)

注意Java/Kotlin 的"类型参数"只支持上界 ;"下界"出现在通配符/投影上:

  • Java wildcard(使用处)

    • 读:? extends T(生产者,只读)
    • 写:? super T(消费者,只写)
javascript 复制代码
void copy(List<? super T> dst, List<? extends T> src) { ... }
  • Kotlin 使用处投影

    • out T ≈ ? extends T,in T ≈ ? super T
kotlin 复制代码
fun <T> copy(src: List<out T>, dst: MutableList<in T>) { ... }

2) 递归边界(F-bounded Polymorphism / 自递归约束)

本质 :让类型参数在上界里引用自己 ,确保"同类比较/同类构建"等自一致性。也叫 CRTP(Curiously Recurring Template Pattern)

2.1 经典示例

  • Comparable:只比较同类型

    • Java:interface Comparable { int compareTo(T other); }
    • 你的类型:final class Money implements Comparable { ... }
  • Enum 的自约束

scala 复制代码
abstract class Enum<E extends Enum<E>> implements Comparable<E> { ... }
  • 确保 Color.compareTo(...) 的参数是 同一个枚举类型
  • Kotlin 等价
kotlin 复制代码
class Money(...) : Comparable<Money> { override fun compareTo(other: Money): Int = ... }
class Node<T : Node<T>>(val children: List<T>)

2.2 Fluent Builder 的"自返回类型"

避免子类链式 API 返回父类类型而需要强制转换:

  • Java
scala 复制代码
abstract class Self<T extends Self<T>> {
  @SuppressWarnings("unchecked")
  protected T self() { return (T) this; }

  public T name(String n) { /*...*/ return self(); }
}
final class UserBuilder extends Self<UserBuilder> {
  public UserBuilder age(int a) { /*...*/ return this; }
}
  • Kotlin
kotlin 复制代码
abstract class Self<T : Self<T>> {
  @Suppress("UNCHECKED_CAST")
  protected fun self(): T = this as T
  fun name(n: String): T = self()
}
class UserBuilder : Self<UserBuilder>() {
  fun age(a: Int): UserBuilder = this
}

使用场景

  • 比较器/枚举/节点树等"同类交互"。

  • Fluent API/Builder 保持子类链式返回正确类型。

易错点

  • 递归上界不是 协变:T extends Base 不会让 T 之间产生子类型关系;只是约束"自己与自己"一致。
  • 小心层层继承导致的复杂度;必要时用组合代替继承。

3) 交叉类型(Intersection Types,A & B)

含义 :同时满足多个类型约束的类型;编译器视为"既是 A 又是 B"。

在 Java 里主要出现在类型参数上界交叉类型强制转换 ;在 Kotlin 里以 where 多上界出现,也可作为类型推断结果(写作 A & B 的抽象类型,不常显式书写)。

3.1 在"上界"声明交叉类型

  • Java
java 复制代码
static <T extends Closeable & Runnable> void use(T x) throws IOException {
  x.run(); x.close();
}
  • Kotlin
kotlin 复制代码
fun <T> use(x: T) where T : Closeable, T : Runnable {
  x.run(); x.close()
}

3.2 交叉类型强制转换(Java 独有的显式 cast)

ini 复制代码
Object o = ...;
Runnable r = (Runnable & Closeable) o;  // 同时视作二者

常与动态代理/混入(mixin)配合:生成一个同时实现多接口的对象,再用交叉转换获得"多接口视图"。

3.3 Kotlin 的"推断得到交叉类型"

kotlin 复制代码
fun useBoth(x: Any) {
  if (x is Runnable && x is Closeable) {  // 智能类型判断
    // 这里 x 的静态类型被推断为 Runnable & Closeable
    x.run(); x.close()
  }
}

注意 :交叉类型是编译期概念;运行时仍是擦除后的类。你不能"创建一个 A & B 的实例",只能创建实现了这两个接口/继承结构的实例。


4) 三者如何组合到"集合/算法签名"里

4.1 上界 + 交叉:要求元素可比较、可关闭

  • Java
scss 复制代码
static <T extends Comparable<? super T> & Closeable>
T maxAndClose(Collection<? extends T> xs) throws IOException {
  T best = xs.stream().max(Comparator.naturalOrder()).orElseThrow();
  best.close();
  return best;
}
  • Kotlin
kotlin 复制代码
fun <T> maxAndClose(xs: Collection<out T>): T
  where T : Comparable<T>, T : Closeable {
  val best = xs.maxOrNull() ?: error("empty")
  best.close()
  return best
}

4.2 自递归约束 + PECS:拷贝并排序

  • Java
javascript 复制代码
static <T extends Comparable<? super T>>
void sortInto(List<? extends T> src, List<? super T> dst) {
  List<T> tmp = new ArrayList<>(src);
  Collections.sort(tmp);              // T 可比较
  dst.addAll(tmp);                    // super: 写入父类型容器
}
  • Kotlin
kotlin 复制代码
fun <T> sortInto(src: List<out T>, dst: MutableList<in T>)
  where T : Comparable<T> {
  val tmp = src.toMutableList()
  tmp.sort()
  dst.addAll(tmp)
}

5) 常见误区与排坑

  1. 把下界写到类型参数上

    • class Box(Java)/不存在;类型参数只有上界。
    • 下界只在通配符/投影:? super Foo / in Foo。
  2. 认为递归上界带来子类型传递

    • class A<T extends A> 与 A**/ A 之间没有子类型关系,它只是"自一致"的约束。**

*** 交叉类型=联合类型?

  • 不是 。交叉 A & B 表示"同时是 A 和 B";联合 A | B(Java/Kotlin 均不支持)表示"是 A 或 B"。
  • 多重上界顺序随意?
  • Java 中若包含类上界,它必须在最前;其余是接口:<T extends Class & I1 & I2>。
  • 擦除导致的运行时限制
  • instanceof List、new T[] 都不行。需要 类型令牌 (Class/Type、Kotlin KType)或 inline + reified(Kotlin)辅助。**

6) 选型与写法速查

  • 只是要用到一些 API → 给 上界: / fun <T: Closeable> ...
  • 要求同时满足多个接口交叉上界:<T extends A & B> / where T: A, T: B
  • 链式/子类保类型递归上界(CRTP)
  • 集合只读/只写 → 用 PECS/投影:? extends T(读) / ? super T(写)
  • 返回值类型 → 一般避免通配与过多上界,把复杂性留在参数与约束上

一句话收束

界类型参数 用"上界/多上界"把可用 API 锁定;递归边界 保证"同类交互/自返回"保持类型一致;交叉类型 让一个类型同时满足多接口/多约束。三者配合 PECS(extends/out 与 super/in) 使用在集合与算法签名里,就能写出既类型安全表达力强的泛型 API。**

相关推荐
南北是北北7 小时前
java&kotlin泛型语法详解
面试
前端缘梦7 小时前
Webpack 5 核心升级指南:从配置优化到性能提升的完整实践
前端·面试·webpack
LL_break8 小时前
线程1——javaEE 附面题
java·开发语言·面试·java-ee
王中阳Go8 小时前
面试官:“聊聊最复杂的项目?”90%的人开口就凉!我面过最牛的回答,就三句话
java·后端·面试
聪明的笨猪猪9 小时前
Java Spring “Bean” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
绝无仅有10 小时前
面试真实经历某节跳动大厂Java和算法问答以及答案总结(一)
后端·面试·github
绝无仅有10 小时前
某大厂跳动面试:计算机网络相关问题解析与总结
后端·面试·github
Cosolar18 小时前
FunASR 前端语音识别代码解析
前端·面试·github
躬身入世,以生证道19 小时前
面试技术栈 —— 简历篇
面试·职场和发展