java&kotlin泛型语法详解

98 阅读4分钟

一、对照速查(最常用语法一眼记住)

场景Java 语法Kotlin 语法备注
泛型类/接口class Box { T v; }class Box(val v: T)T 属于类型本身
泛型方法/函数static T id(T x)fun id(x: T): TJava 静态方法必须自己声明
上界(单)“:`” 等同 extends
多重上界<T extends Number & Serializable> where T : Number, T : SerializableKotlin 用 where
通配/投影:读List<? extends Number>MutableList / List生产者 Extends/out
通配/投影:写List<? super Integer>MutableList消费者 Super/in
通配星号List<*>“未知 T 的只读视图”
原始类型List raw = ...无(不推荐)Java 兼容旧代码
创建数组不能 new T[]不能 arrayOf(...)(需具体类型)擦除所致
类型推断new HashMap<>()(diamond)val m = hashMapOf<K,V>()目标类型参与推断
reifiedinline fun ...运行时拿到 T

二、声明与使用

1) 泛型类/接口

class Box<T> { private T v; T get(){return v;} void set(T x){v=x;} }
class Box<T>(var v: T)

2) 泛型方法/函数

static <T,R> R map(T x, Function<T,R> f) { return f.apply(x); }
fun <T, R> map(x: T, f: (T) -> R): R = f(x)

静态上下文看不到“类上的 T”,所以 Java 的静态方法要自己写 。Kotlin 顶层函数天然“静态”,直接写 即可。


三、上界与多重约束(含递归约束)

单/多上界

<T extends Number>                    // 单
<T extends Number & Serializable>     // 多(第一个必须是类,其余是接口)
<T : Number>                          // 单
fun <T> f(...): T where T: Number, T: Serializable  // 多

递归约束(自限定类型)

class Node<T extends Comparable<T>> { ... }
class Node<T> where T : Comparable<T>

四、变型(协变/逆变):PECS 一句记住

PECSProducer Extends / Consumer Super

生产者(只读)用 extends/out;消费者(只写)用 super/in。

Java:use-site wildcard

void copy(List<? extends Number> src, List<? super Number> dst) {
  for (Number n: src) dst.add(n);   // src 只能读、dst 只能写
}

Kotlin:声明处与使用处

  • 声明处变型(在类型参数上标注,影响所有使用点)
interface Source<out T> { fun get(): T }   // 协变:只能“产出”T
interface Sink<in T>   { fun put(x: T) }   // 逆变:只能“消费”T
  • 使用处投影(临时把某实例视为只读/只写)
fun readAll(xs: MutableList<out Number>) { val n: Number = xs[0] }
fun writeAll(xs: MutableList<in Number>) { xs.add(1); val a: Any? = xs[0] }

常见集合差异

  • Java:List 不变;协变靠 ? extends。

  • Kotlin:List(只读接口协变);MutableList 不变(需要 in/out 投影)。

函数类型(天然带变型)

// (P) -> R 实际是 Function1<in P, out R>
val f: (Number) -> Any = { it }  // 参数逆变、返回协变

五、星投影与原始类型

  • Kotlin 星投影 List<*>

    表示“某种 T 的 List,但我不知道 T 是什么”;可以读到 Any?,不可写入除了 null

  • Java 原始类型 List raw = ...

    兼容旧代码;绕过类型检查,读写都不安全(会有 ClassCastException 风险)。能不用就别用。


六、类型擦除与那些限制

  • JVM 泛型大多被擦除:运行时看不到 List 的 String。

  • 直接后果:

    • 不能 new T() / new T[];

    • 不能 instanceof List(只能 instanceof List<?>);

    • Kotlin 里不能 is T(除 reified 场景)。

解决路径

  1. 显式传“类型令牌” :Class / Type(Java)、KClass / KType(Kotlin)。
  2. Kotlin reified(见下一节)。
  3. 从“声明处签名”反射:字段/方法上如果写死了 List,可反射拿到;若是 List 只能得到 TypeVariable。

七、Kotlin 的 reified(拿到“真实 T”)

inline fun <reified T> parse(json: String): T {
  // 可以做 T::class、is T、typeOf<T>() 等
}

val users: List<User> = parse(json)
  • reified 只能用于 inline 函数的类型参数;编译器把 T 的具体类型写进调用点。
  • 典型用法:filterIsInstance()、序列化/反序列化、反射工具。

八、更多语法与实务小贴士

Java 专属

  • 菱形语法:new HashMap<>()。

  • 通配符捕获:写辅助方法把 List<?> 捕获为 。

  • 可变参数 + 泛型:用 @SafeVarargs 或 @SuppressWarnings("varargs") 配合,避免堆污染告警。

Kotlin 专属

  • 默认类型实参:泛型类/接口可给默认类型参数:class Api<T: Any = Unit>。

  • 交叉类型(intersection) :T 同时满足多个上界时,推断类型可能是 A & B。

  • 空安全:List<String?> vs List?;读写时分清“元素可空”与“容器可空”。

Java ↔ Kotlin 互操作

  • 通配与通配抑制

    • Kotlin 调 Java:可能出现 MutableList 的签名 → 需要 @JvmSuppressWildcards/@JvmWildcard 调整生成的字节码通配符,便于 Java 端调用。
  • 平台类型:从 Java 来的类型在 Kotlin 里是 String!,要自己做好空值与泛型边界的防守。


九、常见坑与最佳实践

  1. PECS 记不住? 背口诀:生产者 Extends/out,消费者 Super/in
  2. 把可变集合投成 out 还想写 → 不行;out 基本变只读。
  3. 可变对象当 key(与泛型无关但常同场出现):修改后取不回,因为 hashCode/equals 改了。
  4. 滥用原始类型:用 List 代替 List 会把类型错误推迟到运行时。
  5. new T[]/Array 限制:用具体元素 Class 创建:
@SuppressWarnings("unchecked")
T[] arr = (T[]) java.lang.reflect.Array.newInstance(componentClass, n);

十、综合示例(两端各一段,覆盖高频语法)

Java:拷贝 & 过滤

static <T> void copy(List<? extends T> src, List<? super T> dst) {
  for (T e : src) dst.add(e);
}

static <T extends Number & Comparable<T>>
List<T> topK(List<T> xs, int k) {
  xs.sort(Comparator.reverseOrder());
  return xs.subList(0, Math.min(k, xs.size()));
}

Kotlin:只读/只写投影 + reified

fun <T> copy(src: List<out T>, dst: MutableList<in T>) {
  for (e in src) dst.add(e)
}

inline fun <reified T> Iterable<*>.only(): List<T> =
  this.filterIsInstance<T>()     // 调用点“实化”T

// 多重约束
fun <T> maxOf(a: T, b: T): T where T: Number, T: Comparable<T> =
  if (a >= b) a else b

一句话收束

Java:不变 + ? extends/? super 做使用处变型;靠 Class/Type 传“类型令牌”。

Kotlin:声明处 out/in + 使用处投影、List 协变;inline + reified 能在运行时拿到 T。

牢记 PECS、上界/多界规则与擦除限制,你就能把 Java & Kotlin 的泛型写得既安全又优雅。