前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了

From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了

原创
作者头像
Seachal
发布2023-06-06 09:59:54
4240
发布2023-06-06 09:59:54
举报
文章被收录于专栏:Seacahl

上期主要分享了 From Java To Kotlin 1 :空安全、扩展、函数、Lambda。

这是 From Java to Kotlin 第二期。

带来 表达式思维、子类型化、类型系统、泛型。

From Java to Kotlin 关键在于 **思维的转变**。

# 表达式思维

Kotlin 中大部分语句是**表达式**。

表达式思维是一种编程思维。 编程思维是一种非常抽象的概念,很多时候是只可意会不可言传的。

不过,从某种程度上看,学习编程思维,比学习编程语法更重要。**因为编程思维决定着我们的代码整体的架构与风格,而具体的某个语法反而没那么大的影响力**。当然,如果对 Kotlin 的语法没有一个全面的认识,编程思维也只会是空中楼阁。就像,我们学会了基础的汉字以后开始写作文:学了汉字以后,如果没掌握写作的技巧,是写不出好的文章的。同理,如果学了 Kotlin 语法,却没有掌握它的编程思维,也是写不出优雅的 Kotlin 代码的。

下面我们看一段 Kotlin 代码

```

//--- 1

var i = 0

if (data != null) {

i = data

}

//--- 2

var j = 0

if (data != null) {

j = data

} else {

j = getDefault()

println(j)

}

//--- 3

var k = 0

if (data != null) {

k = data

} else {

throw NullPointerException()

}

//--- 4

var x = 0

when (data) {

is Int -> x = data

else -> x = 0

}

//--- 5

var y = 0

try {

y = "Kotlin".toInt()

} catch (e: NumberFormatException) {

println(e)

y = 0

}

```

这些代码,如果我们用平时写 Java 时的思维来分析的话,是挑不出太多毛病的。但是站在 Kotlin 的角度,就完全不一样了。利用 Kotlin 的语法,我们完全可以将代码写得更加简洁,就像下面这样:

```

//--- 1

val i = data ?: 0

//--- 2

val j = data ?: getDefault().also { println(it) }

//--- 3

val k = data?: throw NullPointerException()

//--- 4

val x = when (data) {

is Int -> data

else -> 0

}

//--- 5

val y = try {

"Kotlin".toInt()

} catch (e: NumberFormatException) {

println(e)

0

}

```

这段代码看起来就简洁了不少,所以从 Java 转到 Kotlin 要格外注意思维转变,培养表达式思维。

这里有个疑问:Kotlin 为什么就能用这样的方式写代码呢?**其实这是因为:if、when、throw、try-catch 这些语法,在 Kotlin 当中都是表达式**。

那么,这个“表达式”到底是什么呢?其实,与表达式(Expression)对应的,还有另一个概念,我们叫做语句(Statement)。

- 表达式(Expression),是一段可以产生值的代码;

- 语句(Statement),则是一句不产生值的代码。

我们可以简单来概括一下:

**表达式(Expression)有值,而语句(Statement)不总有。**

用一个更详细的例子解释:

```

val a = 1 // statement

println(a) // statement

// statement

var i = 0

if (data != null) {

i = data

}

// 1 + 2 是一个表达式,但是对b的赋值行为是statement

val b = 1 + 2

// if else 整体是一个表达式

// a > b是一个表达式, 子表达式

// a - b是一个表达式, 子表达式

// b - a是一个表达式, 子表达式。

fun minus(a: Int, b: Int) = if (a > b) a - b else b - a

// throw NotImplementedError() 是一个表达式

fun calculate(): Int = throw NotImplementedError()

```

这段代码是描述了常见的 Kotlin 代码模式,从它的注释当中,我们其实可以总结出这样几个规律:

- 赋值语句,就是典型的 statement;

- if 语法,既可以作为语句,也可以作为表达式;

- 语句与表达式,它们可能会出现在同一行代码中,比如 val b = 1 + 2;

- 表达式还可能包含“子表达式”,就比如这里的 minus 方法;

- throw 语句,也可以作为表达式。

看到这里,可能又有一个疑问,那就是:calculate() 这个函数难道不会引起编译器报错吗?

```

// 函数返回值类型是Int,实际上却抛出了异常,没有返回Int

// ↓ ↓

fun calculate(): Int = throw NotImplementedError()

```

要想搞清楚这个疑问, 需要理解Kotlin的**类型系统**。

## 小结

- **Koltin表达式思维**是指时刻记住 Kotlin **大部分**的语句**都是**表达式,它们可以**产生返回值**。利用这种思维,往往可以大大**简化**代码逻辑。

# Kotlin 的类型系统

## 类、类型和子类型

- 类(class)是指一种数据类型,类定义定义对象的属性和方法,可以用来创建对象实例,例如 `class Person(val name: String)`,用于表示一个人的属性和行为。

- 类型(type)是指一个_变量或表达式 _**的 **_**数据类型**_。类型可以用来描述变量或表达式的特征和**限制**(**取值范围**和**可用的操作)**。在Kotlin中,每个变量或表达式都有一个确定的类型,例如Int、String、Boolean等,类型可以是可空的或非空的,例如 `String?` 或 `String`。

- 子类型(subtype)是指一个类型的子集,即一个类型的值可以赋值给另一个类型的变量或表达式。例如 `class Student(name: String, val grade: Int) : Person(name)` 中,`Student` 是 `Person` 的子类型,`String` 是 `String?`的子类型 。

在 Kotlin 中,类和类型之间有一定的对应关系,但并不完全相同。一个类可以用于构造多个类型,

例如泛型类 `List<T>` 可以构造出 `List<String>`、`List<Int>` 等不同的类型。一个类型也可以由多个类实现,例如接口类型 `Runnable` 可以由多个实现了 `run()` 方法的类实现。

### 子类型化

先看一段代码:

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684916915123-43771342-aa44-4220-8837-79764b10c53e.png)

非可空类型的 strNotNull:String ,可以赋值给 可空类型的strNullable:String? ;

可空类型的strNullable:String? 不可以赋值给 非可空类型的 strNotNull:String。

可以看出每一个Kotlin**类**都可以用于构造至少两种**类型**。

根据**子类型化的定义**,String 是 String?的子类型。

**看到这里可能有个疑问**?没有继承关系,String 并没有 继承 String?,为啥String是 String? 的子类型。

其实我也有, 经常开发 Java 会有一个误区:认为只有**继承关系**的**类型**之间才可以有**父子类型关系。**

因为在Java中,类与类型大部分情况下都是“等价”的(在Java泛型出现前)。事实上,“继承”和“子类型化”是两个**完全不同的概念**。子类型化的核心是**一种类型的替代关系**。

---

> **子类型化**, 以下内容引用自维基百科

在[编程语言理论](https://zh.wikipedia.org/wiki/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E7%90%86%E8%AE%BA)中,**子类型**([动名词](https://zh.wikipedia.org/wiki/%E5%8B%95%E5%90%8D%E8%A9%9E),英语:subtyping(也有翻译为**子类型化**))是一种[类型多态](https://zh.wikipedia.org/wiki/%E5%A4%9A%E6%80%81_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6))的形式。这种形式下,**子类型**([名词](https://zh.wikipedia.org/wiki/%E5%90%8D%E8%A9%9E),英语:subtype)可以[替换](https://zh.wikipedia.org/wiki/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99)另一种相关的[数据类型](https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B)(**超类型**,英语:supertype)。也就是说,针对超类型元素进行操作的[子程序](https://zh.wikipedia.org/wiki/%E5%AD%90%E7%A8%8B%E5%BA%8F)、函数等程序元素,也可以操作相应的子类型。如果 S 是 T 的子类型,这种子类型[关系](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%85%83%E5%85%B3%E7%B3%BB)通常写作 S <: T,意思是在任何需要使用 T 类型对象的_环境中,都可以安全地使用_ S 类型的对象。

由于子类型关系的存在,某个对象可能同时属于多种类型,因此,子类型(英语:subtyping)是一种[类型多态](https://zh.wikipedia.org/wiki/%E5%A4%9A%E6%80%81_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6))的形式,也被称作**子类型多态**(英语:subtype polymorphism)或者**包含多态**(英语:inclusion polymorphism)。

子类型与面向对象语言中(类或对象)的[继承](https://zh.wikipedia.org/wiki/%E7%BB%A7%E6%89%BF_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6))是两个概念。子类型反映了类型(即面向对象中的接口)之间的_关系_;而继承反映了一类对象可以从另一类对象创造出来,是_语言特性 _的实现。因此,子类型也称**接口继承**;继承称作**实现继承**。

> [子类型 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%AD%90%E7%B1%BB%E5%9E%8B)

---

**子类型化**可表示为:

```

S <:T

```

以上S是T的子类,这意味着在需要T类型 **值** 的地方,S类型的 **值** 同样适用,可以用 S 类型的 **值** 替换。

所以在前面的例子中, 虽然String与String?看起来没有继承关系,然而在我们需要用String?类型值的地方,显然可以传入一个类型为String的值,这在编译上不会产生问题。反之却不然。 所以String?是String的父类型。

继承强调的是一种“**实现**上的复用”,而子类型化是一种**类型语义的关系**,与实现没关系。对于 Java 语言,由于一般在声明父子类型关系的同时也声明了继承的关系,所以造成了某种程度上的混淆。

---

## 类型系统

Kotlin 的类型还分为**可空类型**和**不可空类型**。Any 是所有非空类型的根类型;而 Any? 是所有可空类型的根类型。

我们猜测 Kotlin 的类型体系可能是这样的:

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1682497918097-f8e06dc5-ac6c-4f25-b522-a39387c90e6a.png)

那Any 与 Any? 之间是什么关系呢?

### Any 、Any?与 Java 的 Object

Java 当中的 Object 类型,对应 Kotlin 的“Any?”类型。但两者并不完全等价,因为 Kotlin 的 Any 可以没有 wait()、notify() 之类的方法。因此,我们只能说 Kotlin 的“Any?”与 Java 的 Object 是大致对应的。

下面是Java 代码,它有三个方法,分别是可为空的 Object 类型、不可为空的 Object 类型,以及无注解的 Object 类型。

```

public class TestTypeJava {

@Nullable // 可空注解

public Object test() { return null; }

// 默认

public Object test1() { return null; }

@NotNull // 不可空注解

public Object test2() { return 1; }

}

```

上面的代码通过 `Convert Java File to Kotlin File` 转换成 Kotlin:

```

class TestTypeJava {

// 可空注解

fun test(): Any? {

return null

}

fun test1(): Any? { // 可以看出默认情况下, Java Object 对应 Kotlin Any?

return null

}

// 不可空注解

fun test2(): Any {

return 1

}

}

```

可以看出默认情况下,没有注解标记可空信息的时候, Java Object 对应 Kotlin Any?。

有些时候Java代码包含了可空性的信息,这些信息使用注解来表达。当代码中出现了这样的信息时,Kotlin就会使用它。因此Java中的@Nullable String被Kotlin当作String?,而@NotNull String就是String

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1683366380498-6f21566f-c274-47bd-af9a-fd4354032fbf.png)

如果没有是否可空注解, Java类型会变成 Kotlin 中的**平台类型(后面会解释)**。

了解了 Any 和 Any?的关系,可以画出关系图

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684931235868-19304527-7f29-4468-9d63-cf717ed6a699.png)

### Unit 与 Void 与 void

先看一段 Java 代码

```

public class PrintHello {

public void printHelloWorld() {

System.out.println("Hello World!");

}

}

```

转成 Kotlin

```

class PrintHello {

fun printHelloWorld():Unit { // Redundant 'Unit' return type

println("Hello World!")

}

}

```

Java 的 `void` 关键字在 Kotlin 里是没有的,取而代之的是一个叫做 `Unit` 的东西,

Unit 和 Java 的 `void` 真正的区别在于,`void` 是真的表示什么都不返回,而 Kotlin 的 `Unit` 却是一个真实存在的**类型**:

```

public object Unit {

override fun toString() = "kotlin.Unit"

}

```

它是一个 `object`,也就是 Kotlin 里的单例类型或者说单例对象。当一个函数的返回值类型是 `Unit` 的时候,它是需要返回一个 `Unit` 类型的对象的:

```

fun printHelloWorld():Unit {

println("Hello World!")

return Unit // return Unit 可以省略

}

```

只不过因为它是个 `object` ,所以唯一能返回的值就是 `Unit` 本身。

这两个 `Unit` 是不一样的,上面的是 `Unit`这个类型,下面的是 `Unit`这个单例对象,它俩长得一样但是是不同的东西。注意了,这个并不是 Kotlin 给`Unit` 的特权,而是 `object` 本来就有的语法特性。如果有需要,也可以用同样的格式来使用别的单例对象,是不会报错的:

包括也可以这样写:

```kotlin

val unit: Unit = Unit

```

也是一样的道理,等号左边是类型,等号右边是对象——当然这么写没什么实际作用啊,**单例**可以直接用。

```

object Zhangsan

fun getZhangsan(): Zhangsan { // 单例可以直接使用

return Zhangsan

}

```

因此,在结构上,`Unit` 并没有任何特别之处,它只是 Kotlin 的 `object`。除了对于函数返回值类型和返回值的自动补充之外,它的特殊之处更多地在于语义和用途的角度。它是由官方规定的,用于表示**「什么也不返回」**的场景的**返回值类型**。但这只是它被规定的用法而已,本质上它是一个实实在在的类型。在 Kotlin 中,不存在真正没有返回值的函数,所有「没有返回值」的函数实质上的返回值类型都是 Unit,而返回值也都是 Unit 这个单例对象。这是 Unit 和 Java 的 void 在本质上的不同之处。

#### Unit 相比 void 带来什么不同

Unit 去除了无返回值函数的**特殊性**和有返回值函数之间的本质区别,从而使得很多事情变得更加简单,这种通用性为我们带来了便利。

##### 例子: 函数类型的函数参数

虽然不能说Java中的所有函数调用都是表达式,但是可以说Kotlin中的所有函数调用都是表达式。

是因为存在特例void,在Java中如果声明的函数没有返回值,那么它就需要用void来修饰。如:

```

public void printHelloWorld() {

System.out.println("Hello World!");

}

```

因为 void 不是类型,所以 函数printHelloWorld()无法匹配 () -> Unit 函数类型

```

class VoidTest {

fun printHelloWorld1():Unit { // 作为参数时,就有函数类型 () -> Unit

println("Hello World!")

}

fun runTask(task: () -> Any) {

when (val result = task()) {

Unit -> println("result is Unit")

String -> println("result is a String: $result")

else -> println("result is an unknown type")

}

}

@Test

fun main1() {

val var1 = ::printHelloWorld1 // () -> Unit

runTask (var1) // () -> Unit

runTask { "This is string" } //:() -> String

runTask { 42 } // () -> Int

}

}

```

现在有了 Unit , fun printHelloWorld1():Unit 作为参数时,就有函数类型 () -> Unit 。

**注意**:在 Java 当中,Void 和 void 不是一回事(注意大小写),前者是一个 Java 的类,后者是一个用于修饰方法的关键字。如下所示:

```

public final class Void {

@SuppressWarnings("unchecked")

public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");

private Void() {}

}

```

JAVA中Void类是一个**不可实例化的占位符类**,用来保存一个引用代表Java关键字void的Class对象。它的作用是在反射或泛型中表示void类型。

例如:Map接口的put方法需要两个类型参数,如果我们只需要存储键而不需要存储值,就可以使用Void类作为类型参数

```

Map<String, Void> map = new HashMap<>(); map.put("key", null);。

```

了解了 `Unit` 和 `Unit?`的关系后,可以画出关系图

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684931116315-468df2d9-4ed0-4340-a000-6a70e4ddd096.png)

### Nothing

Nothing 是 Kotlin 所有类型的子类型。 Noting 的概念与 Any? 恰好相反。

Nothing 也叫底类型(BottomType)。

Nothing的源码是这样的:

```

public class Nothing private constructor()

```

可以看到它本身虽然是 public 的,但它的构造函数是 private 的,这就导致我们没法创建它的实例;而且它不像 Unit 那样是个 object:

```

public object Unit {

override fun toString() = "kotlin.Unit"

}

```

而是个普**通的 class**;并且在源码里 Kotlin 也**没有**帮我们创建它的**实例**。

这些条件加起来,结果就是:Nothing 这个类既**没有**、**也不会**有任何的**实例对象**。

基于这样的前提,当我们写出这个函数声明的时候:

```

fun nothing(): Nothing {

}

```

我们可能无法找到一个合适的值来返回,但是在编写代码时,我们必须返回一个值。这种情况下,我们遇到了一个悖论,即必须返回一个值,但却永远找不到合适的返回值

#### Nothing的作用: 作为函数 `永远不会返回结果` 的提示

```

fun nothing() : Nothing {

throw RuntimeException("Nothing!")

}

```

根据Nothing的特性, Nothing 专门用于抛异常。

```

public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)

@kotlin.internal.InlineOnly

public inline fun TODO(): Nothing = throw NotImplementedError()

```

从上面这段代码可以看出,Kotin 源码中 throw 表达式的返回值类型是 Nothing。

throw 这个表达式的返回值是 Nothing 类型。而既然 Nothing 是所有类型的子类型,那么它当然是可以赋值给任意其他类型的。

所以表达式思维中的问题就可以解答了

```

// 函数返回值类型是Int,实际上却抛出了异常,没有返回Int

// ↓ ↓

fun calculate(): Int = throw NotImplementedError()

```

####

#### 作用二

Nothing 类的构造函数是私有的,因此我们无法构造出它的实例。当 Nothing 类型作为函数参数时,一个有趣的现象就出现了:

```kotlin

// 这是一个无法调用的函数,因为找不到合适的参数

fun show(msg: Nothing) {}

show(null) // 报错

show(throw Exception()) // 虽然不报错,但方法仍然不会调用

```

在这里,我们定义了一个 show 函数,它的参数类型是 Nothing。由于 Nothing 的构造函数是私有的,我们将无法调用 show 函数,除非我们抛出异常,但这没有意义。

这个概念在泛型星投影的时候是有应用的,具体后面会解释。

#### 作用三

而除此之外,Nothing 还有助于编译器进行代码流程的推断。比如说,当一个表达式的返回值是 Nothing 的时候,就往往意味着它后面的语句不再有机会被执行。如下图所示:

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684930660829-949d3831-8067-4bb9-ae54-20532ec59c13.png)

了解了 Nothing 和 Nothing?的关系后,可以画出关系图

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684931156673-69ab4c91-d1d2-4f52-bcf3-4db2d34715ed.png)

## 平台类型

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1683366766751-a3178272-713f-4ce2-8201-c75d2bac6b8f.png)

平台类型在Kotlin中表示为type!(如String!,Int!, CustomClass!)。

Kotlin平台类型**本质**上就是Kotlin不知道**可空性信息**的类型,即可以当作可空类型,也可以当作非空类型。平台类型只能来自Java,因为Java中所有的引用都可能为null,而Kotlin中对null有严格的检查和限制。

但是在Kotlin中是**禁止声明**平台类型的变量的。

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1682673799549-17741eee-4f4c-4325-a440-98994f329525.png)

具体的代码示例如下:

```kotlin

// Java 代码

public class Person {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

// Kotlin 代码

fun main() {

val person = Person() //

val name = person.name // name 是 String! 类型

println(name.length) // 可能抛出空指针异常

person.name = null // 允许赋值为 null

}

```

在这个例子中, name 是平台类型,

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1682673551630-4dafbd59-39de-4306-88da-22851ad4df8b.png)

因为它们来自于 Java 代码。Kotlin 编译器不会检查它们是否为 null,所以需要程序员**自己负责**。如果要避免空指针异常,可以使用安全调用运算符(?.)或非空断言运算符(!!)来处理平台类型。

```kotlin

println(name?.length) // 安全调用,如果 name 为 null 则返回 null

println(name!!.length) // 非空断言,如果 name 为 null 则抛出异常

```

平台类型是指 Kotlin 和 Java 的互操作性问题, 在混合项目中要多加注意。

### 小结

- Any 是所有非空类型的根类型,而 Any? 才是所有类型的根类型。

- Unit 与 Java 的 void 类型相似,代表一个函数不需要返回值;而 Unit? 这个类型则没有太多实际的意义。

- 当 Nothing 作为函数返回值时,意味着这个函数**永远不会返回结果**,而且还会截断程序的后续流程。Kotlin 编译器也会根据这一点进行流程分析。

- 当 Nothing 作为函数参数时,就意味着这个**函数永远无法被正常调用**。这在泛型星投影的时候是有一定应用的。

- Nothing 可以看作是 Nothing? 的子类型,因此,Nothing 可以看作是 Kotlin 所有类型的底类型。

- 正是因为 Kotlin 在类型系统中加入了 Unit、Nothing 这两个类型,才让大部分无法产生值的**语句**摇身一变,成为了**表达式**。这也是“Kotlin 大部分的语句都是表达式”的根本原因。

# 泛型:让类型更加安全

Kotlin 的泛型与 Java 一样,都是一种语法糖,即只在源代码中有泛型定义,到了class级别就被**擦除**了。 泛型(Generics)其实就是把**类型参数化**,真正的名字叫做**类型参数**,它的引入给强类型编程语言加入了更强的灵活性。

### 泛型的优点

1. 类型安全:泛型可以在编译时检查类型,从而避免了在运行时出现类型不匹配的错误。这可以提高程序的可靠性和稳定性。

2. 代码重用:泛型可以使代码更加通用和灵活,从而可以减少代码的重复和冗余。例如,我们可以编写一个通用的排序算法,可以用于任何实现了 Comparable 接口的类型。

在 Java 中,我们常见的泛型有:泛型类、泛型接口、泛型方法和泛型属性,Kotlin 泛型系统继承了 Java 泛型系统,同时添加了一些强化的地方。

### 泛型接口/类(泛型类型)

定义泛型类型,是在类型名之后、主构造函数之前用尖括号括起的大写字母类型参数指定:

### 声明泛型接口

> Java:

```

//泛型接口

interface Drinks<T> {

T taste();

void price(T t);

}

```

> Kotlin:

```

//泛型接口

interface Drinks<T> {

fun taste(): T

fun price(t: T)

}

```

### 声明泛型类

> Java

```

abstract class Color<T> {

T t;

abstract void printColor();

}

class Blue {

String color = "blue";

}

class BlueColor extends Color<Blue> {

public BlueColor(Blue1 t) {

this.t = t;

}

@Override

public void printColor() {

System.out.println("color:" + t.color);

}

}

```

> Kotlin

```

abstract class Color<T>(var t: T/*泛型字段*/) {

abstract fun printColor()

}

class Blue {

val color = "blue"

}

class BlueColor(t: Blue) : Color<Blue>(t) {

override fun printColor() {

println("color:${t.color}")

}

}

```

### 泛型字段

定义泛型类型字段,可以完整地写明类型参数,如果编译器可以自动推定类型参数,也可以省略类型参数:

```

abstract class Color<T>(var t: T/*泛型字段*/) {

abstract fun printColor()

}

```

### 声明泛型方法

Kotlin 泛型方法的声明与 Java 相同,类型参数要放在方法名的前面:

> Java

```

public static <T> T fromJson(String json, Class<T> tClass) {

T t = null;

try {

t = tClass.newInstance();

} catch (Exception e) {

e.printStackTrace();

}

return t;

}

```

> Kotlin

```

fun <T> fromJson(json: String, tClass: Class<T>): T? {

/*获取T的实例*/

val t: T? = tClass.newInstance()

return t

}

```

### 泛型约束

Java 中可以通过有界类型参数来限制参数类型的边界,Kotlin中泛型约束也可以限制参数类型的上界:

> Java

```

public static <T extends Comparable<T>> T maxOf(T a, T b) {

if (a.compareTo(b) > 0) return a;

else return b;

}

```

> Kotlin

```

fun <T : Comparable<T>> maxOf(a: T, b: T): T {

return if (a > b) a else b

}

```

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684985942701-91964de2-2c70-4251-b65f-27d6c8b6f96e.png)

#### where关键字: 多个上界用 where

Java 中多约束: &

```

public static <T extends CharSequence & Comparable<T>> List<T> test(List<T> list, T threshold) {

return list.stream().filter(it -> it.compareTo(threshold) > 0).collect(Collectors.toList());

}

```

Kotin 中多约束:where

```

//多个上界的情况

fun <T> test(list: List<T>, threshold: T): List<T>

where T : CharSequence,

T : Comparable<T> {

return list.filter { it > threshold }.map { it }

}

```

所传递的类型T必须同时满足 where 子句的所有条件,在上述示例中,类型 T 必须既实现了 CharSequence 也实现了 Comparable。

### 泛型形参&泛型实参

泛型类:

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684985074858-60c11a16-7858-413a-b547-84a2c834259e.png)

泛型函数:

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684985520639-f9e76eb2-df09-4ad1-8657-7a59eda860f5.png)

### 泛型的型变

#### 不变

先看一段 Java 代码,我们知道在Java中 ,List<Apple>无法赋值给List<Fruit>

```

public class JavaGeneryc {

public static void main(String[] args) {

List<Apple> apples = new ArrayList<>();

apples.add(new Apple());

List<Fruit> fruits = apples; // 编译错误

for (Fruit fruit : fruits) {

System.out.println(fruit);

}

}

}

class Fruit {

// 父类

}

class Apple extends Fruit {

// 子类

}

```

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684995160311-d6e34816-1ab4-4fd5-a8c3-5bab538d8a90.png)

但是到了Kotlin这里我们发现了一个奇怪的现象

```

fun main2(args: Array<String>) {

val stringList:List<String> = ArrayList<String>()

val anyList:List<Any> = stringList//编译成功

}

```

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684995439604-1d90c0c6-3e4a-4f02-97f7-57a3459a80c8.png)

在Kotlin中竟然能将List<String>赋值给List<Any>,不是说好的Kotlin和Java的泛型原理是一样的吗?怎么到了Kotlin中就变了?其实我们前面说的都没错,关键在于这两个List并不是同一种类型。我们分别来看一下两种List的定义:

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684995530661-7666a6e4-521c-4378-87f8-09b72ae86c0b.png)

虽然都叫List,也同样支持泛型,但是Kotlin的List定义的泛型参数前面多了一个 **out关键词**(加上out 发生协变 ),这个关键词就对这个List的特性起到了很大的作用。

普通方式定义的泛型是不变的,简单来说就是不管类型A和类型B是什么关系,Generic<A>与Generic<B>(其中Generic代表泛型类)都**没有任何关系**。比如,在Java中String是Oject的子类型,但List<String>并不是List<Object>的子类型,在Kotlin中泛型的原理也是一样的。Kotin 使用 out 才发生了变化。

#### out 位置与 in 位置

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1685341816554-430aeda3-908b-4ab0-b8cf-34034848e930.png)

函数参数的类型叫作in位置,而函数返回类型叫作out位置

#### 协变 :保留子类型化关系

如果在定义的泛型类和泛型方法的泛型参数前面加上out关键词,说明这个泛型类及泛型方法是协变,简单来说类型A是类型B的子类型,那么Generic<A>也是Generic<B>的子类型,

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684997594522-bc38b148-6a19-4b59-b769-b1fa8caf89ae.png)

##### 协变点 (out 位置)

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684996091161-424abf75-8ca4-4d90-a6d9-ca41a619a4d9.png)

函数返回值类型为泛型参数。

##### 协变的特征

只能消费,只能取

- 子类型化会被保留(Producer<Cat>是Producer<Animal>的子类型)

- T只能用在out位置

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1685004695301-84cc19c7-ef12-4464-8cf6-58650b84c710.png)

```

interface Book

interface EduBook : Book

class BookStore<out T : Book> {

fun getBook(): T {

TODO()

}

}

fun covariant(){

// 教材书店

val eduBookStore: BookStore<EduBook> = BookStore<EduBook>()

// 书店

val bookStore: BookStore<Book> = eduBookStore // 协变,教辅书店是书店的子类型

val book: Book = bookStore.getBook()

val eduBook : EduBook = eduBookStore.getBook()

}

```

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684999812851-7f05f9d9-67e0-43ef-9231-8644a8545fb5.png)

##### 协变小结

•子类型 Derived 兼容父类型 Base

•生产者 Producer<Derived>兼容 Producer<Base>

#### 逆变: 反转子类型化关系

如果在定义的泛型类和泛型方法的泛型参数前面加上in关键词,说明这个泛型类及泛型方法是逆变,简单来说类型A是类型B的子类型,那么Generic<B>是Generic<A>的子类型,类型父子关系反转。

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1685005522434-1b3d24a7-aaa6-4197-94ca-8f5403f7ef73.png)

##### 逆变点 (in 位置)

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684998210163-a7709c77-7441-415a-a20d-1b9e9f63d0bc.png)

函数参数类型为泛型参数。

##### 逆变的特征

只能生产,只能放入

- 子类型化会被反转(Consumer<Animal> 是 Consumer<Cat>的子类型)

- T只能用在in位置

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1685354258255-08e5d931-dc46-421c-8969-4d2e7a4bf333.png)

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1685004813975-7ea6d7a7-79b0-4ea1-8953-4be362ec8d06.png)

垃圾不能扔到干垃圾桶,但是可以扔到垃圾桶。

干垃圾可以扔到垃圾桶,也可以扔到垃圾桶。

由此可以看出垃圾桶可以替代干垃圾桶, 所以干垃圾桶是父类型。

```

open class Waste

// 干垃圾

class DryWaste : Waste()

// 垃圾桶

class Dustbin<in T : Waste> {

fun put(t: T) {

TODO()

}

}

fun contravariant(){

val dustbin: Dustbin<Waste> = Dustbin<Waste>()

val dryWasteDustbin: Dustbin<DryWaste> = dustbin

val waste = Waste()

val dryWaste = DryWaste()

dustbin.put(waste)

dustbin.put(dryWaste)

// dryWasteDustbin.put(waste)

dryWasteDustbin.put(dryWaste)

}

```

声明为 in ,在 out 位置使用,是会报错的。

### ![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684999307641-c8022537-c35e-4d4e-9faf-2bb47506eb8f.png)

![image.png](https://cdn.nlark.com/yuque/0/2023/png/317107/1684999722919-f6add4a7-89ac-46d5-8704-55d8a367372c.png)

##### 逆变小结

- 子类型 Derived 兼容父类型 Base

- 消费者 Consumer<Base>兼容 Consumer< Derived>

- 记忆小技巧: in 表示逆变, in 倒序过来是 ni(逆)。

#### 型变小结

| 协变 | 逆变 | 不变型 |

| --- | --- | --- |

| Producer <out T> | Consumer<in T> | MutableList:<T> |

| 类的子类型化保留了:Producers<Cat>是 Producer<Animal>的子类型 | 子类型化反转了:Consumer<Animal> 是 Consumer<Cat>的子类型 | 没有子类型化 |

| T只能在out 位置 | T只能在 in 位置 | T可以在任何位置 |

### 泛型中的out与in与 Java 上下界通配符关系

在Kotlin中out代表协变,in代表逆变,为了加深理解我们可以将Kotlin的协变看成Java的上界通配符,将逆变看成Java的下界通配符:

```

//Kotlin使用处协变

fun sumOfList(list: List<out Number>)

//Java上界通配符

void sumOfList(List<? extends Number> list)

//Kotlin使用处逆变

fun addNumbers(list: List<in Int>)

//Java下界通配符

void addNumbers(List<? super Integer> list)

```

### 小结

| Java 泛型 | Java 中代码示例 | Kotlin 中代码示例 | Kotlin 泛型 |

| --- | --- | --- | --- |

| 泛型类型 | class Box<T> | class Box<T> | 泛型类型 |

| 泛型方法 | <T> T fromJson(String json, Class<T> tClass) | fun <T> fromJson(json: String, tClass: Class<T>): T? | 泛型函数 |

| 有界类型参数 | class Box<T extends Comparable<T> | class Box<T : Comparable<T>> | 泛型约束 |

| 上界通配符 | void sumOfList(List<? extends Number> list) | fun sumOfList(list: List<out Number>) | 使用处协变 |

| 下界通配符 | void addNumbers(List<? super Integer> list) | fun addNumbers(list: List<in Int>) | 使用处逆变 |

总的来说,Kotlin 泛型更加简洁安全,但是和 Java 一样都是有类型擦除的,都属于编译时泛型。

---

下期分享:

星投影

注解 @UnsafeVariance

内联特化(内联强化) reified

---

**系列**

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档