一、对照速查(最常用语法一眼记住)
场景 | 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 一句记住
PECS :P 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 场景)。
-
解决路径
- 显式传"类型令牌" :Class / Type(Java)、KClass / KType(Kotlin)。
- Kotlin reified(见下一节)。
- 从"声明处签名"反射:字段/方法上如果写死了 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!,要自己做好空值与泛型边界的防守。
九、常见坑与最佳实践
- PECS 记不住? 背口诀:生产者 Extends/out,消费者 Super/in。
- 把可变集合投成 out 还想写 → 不行;out 基本变只读。
- 可变对象当 key(与泛型无关但常同场出现):修改后取不回,因为 hashCode/equals 改了。
- 滥用原始类型:用 List 代替 List 会把类型错误推迟到运行时。
- 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 的泛型写得既安全又优雅。