Java/Kotlin 泛型

泛型是 Java 和 Kotlin 中用于实现 "参数化类型" 的核心机制,允许类、接口、方法在定义时不指定具体类型,而是在使用时动态指定,从而实现代码复用和类型安全。

基本定义

泛型类/接口

Java:通过 class 类名<T> 或 interface 接口名<T> 定义,类型参数放在类/接口名后。

java 复制代码
// 泛型类
public class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

// 泛型接口
public interface List<T> {
    void add(T element);

    T get(int index);
}

kotlin:语法类似,通过 class 类名<T> 或 interface 接口名<T> 定义,类型参数同样放在名称后。

kotlin 复制代码
// 泛型类
class Box<T>(private var value: T) {
    fun set(value: T) {
        this.value = value
    }

    fun get(): T = value
}

// 泛型接口
interface List<T> {
    fun add(element: T)
    fun get(index: Int): T
}

泛型方法

Java:类型参数声明在方法返回值前,需显式声明。

java 复制代码
public static <T> T getFirstElement(List<T> list) {
    return list.get(0);
}

kotlin:类型参数声明在方法名前<T>,语法更紧凑,且编译器类型推断更智能。

kotlin 复制代码
fun <T> getFirst(list: List<T>): T = list[0]

类型擦除

Java 和 Kotlin 的泛型都基于类型擦除实现:编译时检查类型安全,运行时泛型类型信息被擦除(仅保留原始类型)。

Java

java 复制代码
 List<String> strList = new ArrayList<>();
 List<Integer> intList = new ArrayList<>();

 // 运行时泛型信息被擦除,均为 ArrayList 类型
 System.out.println(strList.getClass() == intList.getClass()); // 输出 true

Kotlin

kotlin 复制代码
val strList: List<String> = listOf()
val intList: List<Int> = listOf()

// 同样擦除,运行时类型相同
println(strList.javaClass == intList.javaClass) // 输出 true

无法在运行时检查泛型具体类型(如 obj instanceof List<String> 编译错误)。

变异

变异描述泛型类型与子类型的关系,核心问题:若 A 是 B 的子类型,Generic<A> 与 Generic<B> 是什么关系?

Java 需在每次使用泛型时通过通配符控制变异(使用处处理),而 Kotlin 在定义泛型时一次性声明(声明处处理),更简洁且避免重复逻辑。

协变

Java:Generic<A> 是 Generic<B> 的子类型(A 是 B 的子类型),用 ? extends B 表示。

java 复制代码
List<Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // 合法:Integer 是 Number 子类型,协变

Number n = numList.get(0); // 合法:能读(输出 Number)
numList.add(1); // 编译错误:无法确定具体类型,写操作不安全

泛型类型只能输出 B 及其子类型(读操作安全,写操作受限)。

Kotlin:用 out 标记类型参数,表示泛型类型是生产者,只能输出 T,即作为返回值。不能输入 T,会导致类型不安全。即若 A : B,则 Generic<A> : Generic<B>

kotlin 复制代码
val intList: List<Int> = listOf(1, 2, 3)
val numList: List<Number> = intList // 合法:协变允许这种赋值

Kotlin 通过 out 关键字在泛型定义时声明协变,如下所示:

kotlin 复制代码
// 协变接口:T 只能作为输出(返回值)
interface DataReader<out T> {
    fun read(): T // 读取数据,返回 T 类型(输出操作,合法)
}

class IntReader : DataReader<Int> {
    private var current = 1
    override fun read(): Int {
        return current++
    }
}

val intReader: DataReader<Int> = IntReader()

// 利用协变特性,将 Int 读取器赋值给 Number 读取器变量,(因为 Int 是 Number 的子类型,协变允许)
val numberReader: DataReader<Number> = intReader // 合法

逆变

Java:Generic<B> 是 Generic<A> 的子类型(A 是 B 的子类型),用 ? super A 表示。泛型类型只能输入 A 及其子类型(写操作安全,读操作受限)。

java 复制代码
List<Number> numList = new ArrayList<>();
List<? super Integer> intSuperList = numList; // 合法:Number 是 Integer 父类型,逆变

intSuperList.add(1); // 合法:能写(输入 Integer)
Number n = intSuperList.get(0); // 编译错误:只能读为 Object,类型不安全

kotlin:用 in 标记类型参数,表示泛型类型是消费者(只能输入 T,即作为参数)。即若 A : B,则 Generic<B> : Generic<A>。

kotlin 复制代码
// 逆变接口:T 只能作为输入(方法参数)
interface DataProcessor<in T> {
    fun process(data: T) // 处理数据,接收 T 类型参数(输入操作,合法)
}

// 处理所有 Number 类型(父类型处理器)
class NumberProcessor : DataProcessor<Number> {
    override fun process(data: Number) {
        println("NumberProcessor process: $data")
    }
}

val numberProcessor: DataProcessor<Number> = NumberProcessor()
val intProcessor: DataProcessor<Int> = numberProcessor 

不变

默认情况下,Generic<A> 与 Generic<B> 无父子关系(即使 A 是 B 的子类型)。对于 kotlin 来说,未用 out/in 标记时,泛型类型默认不变。

通配符与星投影

Java:无界通配符 ?,表示任意类型,读写均受限,可读为 Object,可写 null(无意义)

java 复制代码
List<?> anyList = new ArrayList<String>();
Object obj = anyList.get(0); // 合法:读为 Object
anyList.add(null); // 合法:只能加 null
anyList.add("a"); // 编译错误:类型不确定

Kotlin:星投影 *,是 Kotlin 对未知类型参数的简化表示,行为与 Java 通配符类似,但规则更明确:

  • 对于 Generic<out T : Upper>(协变且有上界):Generic<*> 等价于 Generic<out Upper>。
  • 对于 Generic<in T>(逆变):Generic<*> 等价于 Generic<in Nothing>(Nothing 是 Kotlin 所有类型的子类型)。
  • 对于 Generic<T : Upper>(不变且有上界):Generic<*> 等价于 Generic<Upper>。

Kotlin 标准库的 List 是协变的(List<out T>),其星投影 List<*> 等价于 List<out Any?>

kotlin 复制代码
val anyList: List<*> = listOf("a", 1) // 等价于 List<out Any?>
val obj: Any? = anyList[0] // 合法:读为 Any?
// anyList.add("b") // 编译错误:星投影不可写(类似 Java ?)

泛型约束

泛型约束用于限制类型参数的范围(如只能是某类的子类)。

上界约束(限制为某类型的子类型)

Java:用 extends 声明,类型参数必须是指定类型的子类型。

java 复制代码
// T 必须是 Number 的子类型(如 Integer、Double)
public class NumberBox<T extends Number> {
    private T value;
}

NumberBox<Integer> intBox = new NumberBox<>();

Kotlin:用 : 声明,语法更简洁。

kotlin 复制代码
// T 必须是 Number 的子类型
class NumberBox<T : Number>(private var value: T)

// 合法
val intBox = NumberBox(1) // 推断 T 为 Int
// 不合法
// val strBox = NumberBox("a")

多个上界约束

Java:用 & 连接,且类必须放在接口前(只能有一个类上界)。

java 复制代码
// T 必须是 Number 子类且实现 Comparable<T>
public class ComparableNumberBox<T extends Number & Comparable<T>> {
}

Kotlin:用 where 子句,支持多个上界,顺序无限制。

kotlin 复制代码
// T 必须是 Number 子类且实现 Comparable<T>
class ComparableNumberBox<T> where T : Number, T : Comparable<T>

Java 中「类型参数」不能声明下界,仅通配符支持 ? super A(见变异部分)。Kotlin 中「类型参数」也不直接支持下界,但可通过 in 逆变间接实现类似效果(本质是消费型参数)。

Java 和 kotlin 都不直接支持下界约束,但是,Java 可以通过通配符 ? super A,kotlin 可以通过 in 逆变实现类似的效果。

Kotlin 独有的泛型特性:reified

由于类型擦除,Java 无法在运行时获取泛型类型信息(如 T.class 不合法)。但 Kotlin 通过 inline 函数配合 reified 关键字,可在运行时保留类型信息。

kotlin 复制代码
inline fun <reified T : Activity> startActivity(
    context: Context,
    extras: Bundle? = null
) {
    val intent = Intent(context, T::class.java)
    extras?.let { intent.putExtras(it) }
    context.startActivity(intent)
}
相关推荐
胡致和14 小时前
配置变更后,弹窗为什么飞到了最左边?
kotlin
zhangphil20 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
小书房20 小时前
Kotlin使用体验及理解1
android·开发语言·kotlin
Kapaseker21 小时前
我想让同事知道我很懂 Compose 怎么办?
android·kotlin
jinanwuhuaguo1 天前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
jinanwuhuaguo2 天前
OpenClaw协议霸权——从 MCP 标准到意图封建化的政治经济学(第十八篇)
android·人工智能·kotlin·拓扑学·openclaw
zhangphil2 天前
Android sql查媒体数据封装room Dao构造AndroidViewModel,RecyclerView宫格展示,Kotlin
android·kotlin
jinanwuhuaguo2 天前
反熵共同体——OpenClaw的宇宙热力学本体论(第十七篇)
大数据·人工智能·安全·架构·kotlin·openclaw
pengyu2 天前
【Kotlin 协程修仙录 · 筑基境 · 中阶】 | 身份证与通行证:CoroutineContext 的深度解剖
android·kotlin
夏沫琅琊2 天前
android 短信读取与导出技术
android·kotlin