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

87次阅读

共计 14131 个字符,预计需要花费 36 分钟才能阅读完成。

上期次要分享了 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) 中,StudentPerson 的子类型,StringString?的子类型。

在 Kotlin 中,类和类型之间有肯定的对应关系,但并不完全相同。一个类能够用于结构多个类型,
例如泛型类 List<T> 能够结构出 List<String>List<Int> 等不同的类型。一个类型也能够由多个类实现,例如接口类型 Runnable 能够由多个实现了 run() 办法的类实现。

子类型化

先看一段代码:

非可空类型的 strNotNull:String,能够赋值给 可空类型的 strNullable:String?;
可空类型的 strNullable:String? 不能够赋值给 非可空类型的 strNotNull:String。

能够看出每一个 Kotlin 都能够用于结构至多两种 类型

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

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

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

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


子类型化,以下内容援用自维基百科

在编程语言实践中,子类型 (动名词,英语:subtyping(也有翻译为 子类型化 ))是一种类型多态) 的模式。这种模式下,子类型 (名词,英语:subtype)能够替换另一种相干的数据类型( 超类型,英语:supertype)。也就是说,针对超类型元素进行操作的子程序、函数等程序元素,也能够操作相应的子类型。如果 S 是 T 的子类型,这种子类型关系通常写作 S <: T,意思是在任何须要应用 T 类型对象的_环境中,都能够平安地应用_ S 类型的对象。

因为子类型关系的存在,某个对象可能同时属于多种类型,因而,子类型(英语:subtyping)是一种类型多态)的模式,也被称作 子类型多态 (英语:subtype polymorphism)或者 蕴含多态(英语:inclusion polymorphism)。

子类型与面向对象语言中(类或对象)的继承)是两个概念。子类型反映了类型(即面向对象中的接口)之间的_关系_;而继承反映了一类对象能够从另一类对象发明进去,是_语言个性 _的实现。因而,子类型也称 接口继承 ;继承称作 实现继承

子类型 – 维基百科,自在的百科全书


子类型化 可示意为:

S <:T

以上 S 是 T 的子类,这意味着在须要 T 类型 的中央,S 类型的 同样实用,能够用 S 类型的 替换。

所以在后面的例子中,尽管 String 与 String?看起来没有继承关系,然而在咱们须要用 String?类型值的中央,显然能够传入一个类型为 String 的值,这在编译上不会产生问题。反之却不然。所以 String?是 String 的父类型。

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


类型零碎

Kotlin 的类型还分为 可空类型 不可空类型 。Any 是所有非空类型的根类型;而 Any? 是所有可空类型的根类型。
咱们猜想 Kotlin 的类型体系可能是这样的:

那 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

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

理解了 Any 和 Any? 的关系,能够画出关系图

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 原本就有的语法个性。如果有须要,也能够用同样的格局来应用别的单例对象,是不会报错的:

包含也能够这样写:

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);。

理解了 UnitUnit?的关系后,能够画出关系图

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 类型作为函数参数时,一个乏味的景象就呈现了:

// 这是一个无奈调用的函数,因为找不到适合的参数
fun show(msg: Nothing) {}

show(null) // 报错
show(throw Exception()) // 尽管不报错,但办法依然不会调用

在这里,咱们定义了一个 show 函数,它的参数类型是 Nothing。因为 Nothing 的构造函数是公有的,咱们将无奈调用 show 函数,除非咱们抛出异样,但这没有意义。
这个概念在泛型星投影的时候是有利用的,具体前面会解释。

作用三

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

理解了 Nothing 和 Nothing?的关系后,能够画出关系图

平台类型

平台类型在 Kotlin 中示意为 type!(如 String!,Int!, CustomClass!)。
Kotlin 平台类型 实质 上就是 Kotlin 不晓得 可空性信息 的类型,即能够当作可空类型,也能够当作非空类型。平台类型只能来自 Java,因为 Java 中所有的援用都可能为 null,而 Kotlin 中对 null 有严格的检查和限度。
然而在 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 是平台类型,

因为它们来自于 Java 代码。Kotlin 编译器不会查看它们是否为 null,所以须要程序员 本人负责。如果要防止空指针异样,能够应用平安调用运算符(?.)或非空断言运算符(!!)来解决平台类型。

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
}

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。

泛型形参 & 泛型实参

泛型类:

泛型函数:

泛型的型变

不变

先看一段 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 {// 子类}

然而到了 Kotlin 这里咱们发现了一个奇怪的景象

fun main2(args: Array<String>) {val stringList:List<String> = ArrayList<String>()
    val anyList:List<Any> = stringList// 编译胜利
}

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

尽管都叫 List,也同样反对泛型,然而 Kotlin 的 List 定义的泛型参数后面多了一个 out 关键词 (加上 out 产生协变),这个关键词就对这个 List 的个性起到了很大的作用。
一般形式定义的泛型是不变的,简略来说就是不论类型 A 和类型 B 是什么关系,Generic 与 Generic(其中 Generic 代表泛型类)都 没有任何关系。比方,在 Java 中 String 是 Oject 的子类型,但 List<String> 并不是 List<Object> 的子类型,在 Kotlin 中泛型的原理也是一样的。Kotin 应用 out 才产生了变动。

out 地位与 in 地位

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

协变:保留子类型化关系

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

协变点(out 地位)

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

协变的特色

只能生产,只能取

  • 子类型化会被保留(Producer<Cat> 是 Producer<Animal> 的子类型)
  • T 只能用在 out 地位
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()}

协变小结

•子类型 Derived 兼容父类型 Base
•生产者 Producer<Derived>兼容 Producer<Base>

逆变:反转子类型化关系

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

逆变点 (in 地位)

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

逆变的特色

只能生产,只能放入

  • 子类型化会被反转(Consumer<Animal> 是 Consumer<Cat> 的子类型)
  • T 只能用在 in 地位

垃圾不能扔到干垃圾桶,然而能够扔到垃圾桶。
干垃圾能够扔到垃圾桶,也能够扔到垃圾桶。
由此能够看出垃圾桶能够代替干垃圾桶,所以干垃圾桶是父类型。

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 地位应用,是会报错的。

逆变小结
  • 子类型 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


系列

From Java To Kotlin:空平安、扩大、函数、Lambda 很具体,这次终于懂了

From Java To Kotlin 2:Kotlin 类型零碎与泛型

正文完
 0