java&kotlin泛型语法详解

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

场景 Java 语法 Kotlin 语法 备注
泛型类/接口 class Box { T v; } class Box(val v: T) T 属于类型本身
泛型方法/函数 static T id(T x) fun id(x: T): T Java 静态方法必须自己声明
上界(单) ":`" 等同 extends
多重上界 <T extends Number & Serializable> where T : Number, T : Serializable Kotlin 用 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>() 目标类型参与推断
reified inline fun ... 运行时拿到 T

二、声明与使用

1) 泛型类/接口

csharp 复制代码
class Box<T> { private T v; T get(){return v;} void set(T x){v=x;} }
kotlin 复制代码
class Box<T>(var v: T)

2) 泛型方法/函数

javascript 复制代码
static <T,R> R map(T x, Function<T,R> f) { return f.apply(x); }
kotlin 复制代码
fun <T, R> map(x: T, f: (T) -> R): R = f(x)

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


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

单/多上界

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

递归约束(自限定类型)

scala 复制代码
class Node<T extends Comparable<T>> { ... }
csharp 复制代码
class Node<T> where T : Comparable<T>

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

PECSP roducer E xtends / C onsumer Super
生产者(只读)用 extends/out;消费者(只写)用 super/in。

Java:use-site wildcard

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

Kotlin:声明处与使用处

  • 声明处变型(在类型参数上标注,影响所有使用点)
kotlin 复制代码
interface Source<out T> { fun get(): T }   // 协变:只能"产出"T
interface Sink<in T>   { fun put(x: T) }   // 逆变:只能"消费"T
  • 使用处投影(临时把某实例视为只读/只写)
kotlin 复制代码
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 投影)。

函数类型(天然带变型)

css 复制代码
// (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")

kotlin 复制代码
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 创建:
ini 复制代码
@SuppressWarnings("unchecked")
T[] arr = (T[]) java.lang.reflect.Array.newInstance(componentClass, n);

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

Java:拷贝 & 过滤

javascript 复制代码
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

kotlin 复制代码
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 的泛型写得既安全又优雅。

相关推荐
前端缘梦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 小时前
面试技术栈 —— 简历篇
面试·职场和发展
程序员飞哥21 小时前
如何设计多级缓存架构并解决一致性问题?
java·后端·面试