文章目录

  • 前言
  • 主要参考资料:
  • 语法基础
  • 修饰符
  • class
  • fun
  • param
  • 原生字符串
  • 迭代器
  • item ==in== array
  • array==.indices==
  • array==.withIndex()==
  • array==.forEach { }==
  • array.==forEachIndexed { }==
  • Range条件
  • 正向区间
  • 逆向区间
  • 步长
  • 字符串
  • 换行输出
  • replace
  • 等号
  • 遍历
  • 数值类
  • → \rightarrow
  • Double格式化
  • 集合类
  • 类型
  • 常用集合类
  • 继承层次
  • List
  • 去重
  • list.removeIf{ … }
  • Map
  • 创建
  • 安全索引
  • 函数式编程在集合类中的应用
  • 映射函数 map、flatMap
  • 过滤函数 filter
  • 组合函数 zip、fold
  • 生成指定序列
  • 运算符重载
  • 常用操作符
  • 示例
  • 在集合类中的使用
  • 解构语法特性
  • list
  • 数据类
  • 函数
  • 可变参数
  • lambda
  • 背景
  • 内联
  • 背景
  • 示例
  • 未用内联
  • 使用内联
  • 注意
  • 标准函数
  • apply、also、let
  • run、with
  • takeIf、takeUnless
  • 扩展函数
  • 示例
  • 实战
  • 场景:
  • 用法:
  • 扩展属性
  • 面向对象
  • 声明
  • 初始化顺序
  • 继承
  • 不可继承 —— 默认
  • 可继承 —— open
  • 实现
  • object对象
  • 对象声明
  • 对象表达式
  • 伴生对象
  • 内部类 inner class
  • 普通嵌套
  • 内部嵌套
  • 数据类 data class
  • 枚举类 enum class
  • 简单使用
  • 进阶使用
  • ❗❗注意
  • 密封类 sealed class
  • 泛型
  • 简单使用
  • 泛型类型约束
  • 类型上界
  • 类型擦除
  • 擦除的基本过程
  • 获取擦除的类型 —— 实化类型 reified
  • in & out
  • 综述
  • 意义
  • 示例
  • 协变 out:生产
  • 逆变 in:消费
  • 不变:产 + 消
  • 通配符
  • 星投影


前言

以下仅记录了对我个人而言有需要的部分,并不详尽。

主要参考资料:

书籍:

《Kotlin从基础到实战》黑马程序员 ISBN:9787115494405

《Kotlin从入门到进阶实战》陈光剑 ISBN:9787302508724

语法基础

修饰符

class


说明

abstract

抽象

final

不可被继承

enum

枚举

open

可继承

annotation

注解

sealed

密封

data

数据

fun


说明

tailrec

尾递归

operator

运算符重载

infix

中缀

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串

inline

内联

external

外部

suspend

挂起协程

param


说明

vararg

变长

noinline

不内联

crossinline

原生字符串

使用3对引号括起来,输出字符串中全部原有内容,不会发生转义。

fun main() {
    val str = "hello \nworld!"
    println(str)
}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_02

迭代器

item in array

for (item in array) {
    println(item)
}

array==.indices==

for (i in array.indices) {
    println(array[i])
}

array==.withIndex()==

for ((index, value) in array.withIndex()) {
    println("the element at ${index} is ${value}")
}

array==.forEach { }==

forEach是一个单参数的匿名函数,故用it关键词。

array.forEach {
    println(it)
}

array.forEachIndexed { }

array.forEachIndexed { index, i -> println("$index, $i") }

Range条件

判断包含关系

正向区间

1 <= a <= 4

// 1 2 3 4
a in 1..4
a in 1.rangTo(4)

1 <= a < 4

// 1 2 3
a in 1 until 4

逆向区间

// 4 3 2 1
a in 4 downTo 1

步长

//1 3
a in 1..4 step 2
//4 2
a in 4 downTo 1 step 2

字符串

换行输出

val text = """
	|first line
	|second line""".trimMargin()

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_03

replace

var str = "she is a outgoing girl"
val str1 = str.replace(Regex("[aeiou]"),"0")
val str2 = str.replace(Regex("[aeiou]")){
    when (it.value) {
        "a" -> "1"
        "e" -> "2"
        "i" -> "3"
        "o" -> "4"
        "u" -> "5"
        else -> "0"
    }
}
println(str1)
println(str2)

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_04

等号

Kotlin

Java

==

两个字符串中的字符是否匹配

==

做引用比较

===

两个变量是否指向内存堆上同一对象

equals

做结构比较

var str = "ABCD"
    val str1 = "ABCD"
    val str2 = "aBCD".capitalize()

    println("$str == $str1 is ${str == str1}")
    println("$str === $str1 is ${str === str1}")	//JVM 字符串常量池
    println("$str == $str1 is ${str == str2}")
    println("$str === $str1 is ${str === str2}")

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_05

遍历

可使用forEach遍历

"a b c d e".forEach {
    println(it)
}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_集合类_06

数值类

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_07

1.23.roundToInt()	//四舍五入
1.23.toInt()		//去尾法
"1.23".toInt()		//抛出异常
"1.23".toIntOrNull()	//输出null

Double格式化

"%.2f".format(1.2345)	//四舍五入,保留两位(String类型)

集合类

类型

分为两大类型:只读、不可变

常用集合类

List:集合元素可重复

Set:集合元素不可重复

Map:键值对

var set = setOf(11,22,33,22)
print(set)	// [11, 22, 33]

继承层次


1











Collection

MutableList

List

MutableSet

Set

MutableCollection

MutableIterable

Iterable

2


Map

MutableMap


List

去重
list.distinct()	//去重,原理见下图

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_kotlin_08

list.removeIf{ … }

Map

创建
var m = mapOf("linr" to 10, "uuln" to 20)	//key to value
print(m)	//{linr=10, uuln=20}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_09

安全索引

函数

说明

示例

getOrElse()

list、map

l.getOrElse(index) { value }

m.getOrElse(key) { value }

getOrNull()

list

l.getOrNull(index)

getOrDefault()

map

m.getOrDefault(key, value)

elementAtOrElse()

set

s.elementAtOrElse(index, value)

elementAtOrNull()

set

s.elementAtOrNull(index)

使用1:

// getOrElse
var list0 = listOf<Int>(11,22,33,44,55)
for (i in 0..8){
    println("i = $i: " + list0.getOrElse(i){
        list0[i % list0.size]
    })
}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_10

使用2:

// 两者等价
list0.getOrElse(5) {"there is noting"}
list0.getOrNull(5)?: "there is noting"

函数式编程在集合类中的应用

设计理念
不可变数据 的副本,在链上的 函数间 传递(即,不会改变原对象)

映射函数 map、flatMap

有点儿像scala

fun main() {
    val pets = listOf("rabbit", "spider", "snake", "gecko")
    pets.also { println("pets: $it") }  //pets: [rabbit, spider, snake, gecko]
        .map { pet -> "a baby $pet" }
        .also { println("pets: $it") }  //pets: [a baby rabbit, a baby spider, a baby snake, a baby gecko]
        .run { println("pets: $pets") } //pets: [rabbit, spider, snake, gecko]
}

从第一个also和最后的run输出的结果可以看出,没有改变原对象。函数式编程都是如此,后续函数不在赘述。

flatMap{ it } could be simplified to flatten()

fun main() {
    val pets = listOf(
        listOf("rabbit"), listOf("spider", "snake", "gecko")
    )
    pets.also { println("pets: $it") }  //pets: [[rabbit], [spider, snake, gecko]]
        .flatten()
        .also { println("pets: $it") }  //pets: [rabbit, spider, snake, gecko]
}
过滤函数 filter
fun main() {
    val pets = listOf("rabbit", "spider", "snake", "gecko")
    pets.also { println("pets: $it") }  //pets: [rabbit, spider, snake, gecko]
        .filter { it.contains("r") }
        .also { println("pets: $it") }  //pets: [rabbit, spider]
}

和flatMap组合使用:

fun main() {
    val pets = listOf(
        listOf("rabbit"), listOf("spider", "snake", "gecko")
    )
    pets.also { println("pets: $it") }  //pets: [[rabbit], [spider, snake, gecko]]
        .flatMap{ it.filter { it.contains("r") }}
        .also { println("pets: $it") }  //pets: [rabbit, spider]
}

和map组合使用,找素数:

fun main() {
    val numbers = listOf(7, 4, 8, 33, 17, 1, 2, 94, 83)
    numbers.also { println("numbers: $it") }  //numbers: [7, 4, 8, 33, 17, 1, 2, 94, 83]
        .filter { num ->
            (2 until num).map { num % it }.none { it == 0 } && num >= 2
        }
        .also { println("numbers: $it") }  //numbers: [7, 17, 2, 83]
}
组合函数 zip、fold
fun main() {
    val pets = listOf("rabbit", "spider", "snake", "gecko")
    val num = listOf(10, 20, 30, 40)
    pets.also { println("pets: $it") }  //pets: [rabbit, spider, snake, gecko]
        .zip(num).also { println("pets: $it") }  //pets: [(rabbit, 10), (spider, 20), (snake, 30), (gecko, 40)]
        .toMap().also { println("pets: $it") }  //pets: {rabbit=10, spider=20, snake=30, gecko=40}
}
fun main() {
    val num = listOf(1, 2, 3, 4)
    num.also { println("num: $it") }
        .fold(11) { res, n ->
            println("current res is $res")
            res + (n * 10)
        }	//res:初始值为fold()中所给的值,n:num中取出的元素值
        .also { println("res = $it") }
}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_11

生成指定序列

generateSequence( 起始值 ) { 后续值的规则 } .自定义迭代器

val num = generateSequence(3) {
    it + 1
}.filter { it % 2 == 0 }.take(5).toList() 	// [4, 6, 8, 10, 12]

运算符重载

常用操作符

操作符

函数名

+

plus

+=

plusAssign

==

equals

>

compareTo

[ ]

get


rangeTo

in

contains

以“==”为例:

左侧是kotlin源码,右侧为反编译后的Java代码

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_12

从图中可知,“==”被编译为了Intrinsics.areEqual()方法。再点进去,可以看到用的就是equals()方法

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_13

Intrinsics是kotlin的一个内部类,包括了判空、判等、断言等方法。

示例

sealed class Ball {
    class BlueBall(val color: String?) : Ball()
    class RedBall(val color: String?) : Ball()
    class GreenBall(val color: String?) : Ball()
}

class Box<T : Ball>(vararg item: T) {
    var available = false
    private var subject: Array<out T> = item
    
    operator fun get(index: Int): T? = subject[index]?.takeIf { available }
}

fun main() {
    val b0: Ball = Ball.BlueBall("#111111")
    val b1: Ball = Ball.RedBall("#222222")
    val b2: Ball = Ball.GreenBall("#333333")

    val mBox = Box<Ball>(b0, b1, b2)
    mBox.available = true
    println("you get ${mBox[1]}")	//you get Ball$RedBall@6e0be858
}
在集合类中的使用

可使用 +=、-= 修改

var l = listOf(11,22,33,44)
//var l = mutableListOf(11,22,33,44)
l += 0
反编译后的java代码:>

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_集合类_14


Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_15

解构语法特性

list

list支持解构语法特性:在一个表达式里给多个变量赋值。

var str = "he is a,student"
val (s1,s2,s3,s4) = str.split(" ",",")	//split返回List集合
print("$s1: $s4")	//输出student

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_16

此处无需用s2、s3,可用 “ _ ” 代替:

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_kotlin_17

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_kotlin_18

数据类

data class LoginUser(val name: String, val password: String)
变量权限默认为private

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_19


编译时会自动添加一些方法:get()、copy()、toStirng()、hashCode()、equals()…


Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_20

函数

可变参数

实质:长度可变的数组

特点:参数类型确定,参数个数不确定

fun main() {
    countTotalScore("张三", 83, 97, 91)
    countTotalScore("李四", 99)
}

fun countTotalScore (name: String, vararg scores: Int) {
    var totalScore = 0
    scores.forEach {
        totalScore += it
    }
    println("${name}的总成绩:${totalScore}")
}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_21

若要直接传递数组为参数,需使用”*“前缀操作符,意为将数组展开(不可用于集合)。

val scores = intArrayOf(83, 97, 91)
 countTotalScore("张三", *scores)

lambda

背景

lambda也可用希腊字符λ表示,是lambda演算的简称。

lambda演算是一套数理演算逻辑,由数学家Alonzo Church于20世纪30年代提出,在定义匿名函数时,使用了lambda演算记法。

内联

背景

在JVM中,Lambda表达式都会被编译成一个匿名类,每次调用Lambda表达式时,都会创建新的对象实例。
故,JVM会为所有同lambda打交道的变量分配内存,这就造成来额外的内存开销,进而导程序性能降低。

为此,Kotlin提供了一种优化机制——内联,使用”inline“关键字修饰。
有了内联,编译器会将函数体复制粘贴到调用的地方,直接执行。不再使用lambda对象实例,避免出入栈的操作。
在不影响程序可读性的同时优化了性能,但在实际运行时会增加代码量。
(可类比C语言中的预编译指令,宏定义。)

示例
未用内联

Kotlin代码:

fun main() {
    showDiscount("纸巾"){ goodName: String, hour: Int ->
        "今年${goodName}促销还剩:${hour}小时"
    }
}
fun showDiscount(goodName: String, getDiscountInfo: (String,Int)->String){
    val hour = (1..24).shuffled().last()
    println(getDiscountInfo(goodName, hour))
}
反编译后的java代码:

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_集合类_22

使用内联

Kotlin代码:

fun main() {
    showDiscount("纸巾"){ goodName: String, hour: Int ->
        "今年${goodName}促销还剩:${hour}小时"
    }
}

inline fun showDiscount(goodName: String, getDiscountInfo: (String,Int)->String){
    val hour = (1..24).shuffled().last()
    println(getDiscountInfo(goodName, hour))
}

反编译后的java代码:

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_23

注意

使用lambda的递归函数内联,编译会发出警告,因为会导致复制粘贴无限循环。

标准函数

apply、also、let

定义:

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_24

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_kotlin_25

使用1:3者等效使用

var list0 = ArrayList<String>().apply {
    add("A")
    add("B")
    add("C")
}
var list1 = ArrayList<String>()
list1.also {
    it.add("A")
    it.add("B")
    it.add("C")
}
var list2 = ArrayList<String>()
list2.let {
    it.add("A")
    it.add("B")
    it.add("C")
}
println("apply: $list0")
println("also: $list1")
println("let: $list2")

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_26

使用2:对比let、also

val res = 2.let{
    it + 1
}
val res1 = 2.also{
    it + 1
}
println("let: $res")
println("also: $res1")

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_kotlin_27

run、with

定义:

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_28

使用:

val res = "1234567".run {
    length >= 10
}
val res1 = with("1234567"){
    length > 0
}
print("res = $res ; res1 = $res1")

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_29

takeIf、takeUnless

目标对象.takeIf { 条件判断句 }.后续操作

takeIf效果类似if,但可直接在对象实例上调用,故有如下优势:

  1. 避免创建临时变量并赋值
  2. 可链式调用

ture 返回接收者对象(即,执行后续操作)
flase 返回null

takeUnless效果类似takeIf,但只在结果为false时执行后续操作

扩展函数

fun 目标类型.扩展函数名( ) { … }

为现有类自由添加自定义的函数,即使是使用private、final修饰,也可以扩展。

现有类:自定义的、标准库里的。

示例

/** 在字符串后增加n个"!" */
fun String.addExt(n: Int = 1) = this + "!".repeat(n)

fun main() {
    println("abc".addExt(3))	//abc!!!
}

标准函数都是扩展函数,使用的是泛型方法。

fun <T> T.functionName() { ...}

实战

场景:

类似用java写个String工具类

用法:
  1. 单独建立扩展文件Strings.kt,在需要用到地方导入。
  2. 命名:“目标类名+s后缀”。
  3. 改名:import …. as …

扩展属性

var 类型参数 目标类型.属性名: 扩展属性的类型

定义在:

  • class ✔
  • file ✔
  • function ✘

面向对象

声明

声明类的同时,声明构造函数。

初始化顺序

  1. 主构造函数
  2. 类级别属性赋值 / init初始化块(由代码顺序决定)
  3. 次构造函数

下图:kotlin源码(左),反编译java代码(右)

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_集合类_30

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_31

继承

不可继承 —— 默认

所有类默认使用final关键字修饰,不可被继承,方法同理。

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_集合类_32

可继承 —— open

可继承的类、方法,均用open修饰

open class Person { 
    open fun breath() = "活着就要呼吸"
}

实现

接口及其方法默认使用open、abstract关键字修饰。

可以有默认实现,但一般不这样使用。

object对象

∵ kotlin 没有static关键字 Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_07

∴ 替代方法:使用object关键字(常写在伴生对象中)

  1. 对象声明
  2. 对象表达式
  3. 伴生对象

对象声明

相当于创建单例类

因为只有一个实例,故类名也是其实例的名称,调用时无需自己再实例一次。

object Student {
    ...
}

对象表达式

相当于匿名内部类

open class Student {
    open fun study() = "学习中。。。"
}

// 1.实例一个大学生对象,继承Student。2.将这个对象赋值给s。 
val s = object: Student() {
    override fun study() = "学习高数中。。。"
}

伴生对象

1. 初始化时间:在类加载时才初始化,而非编译时。并且无论实例化类多少次,伴生对象都只初始化一次。

2. 作用:Kotlin中没有静态变量,因此Java中的静态变量、静态方法都可以写在伴生对象里。

// Define
class Student{
    ...
    companion object XiaoMing {
        val name = "XiaoMing"
        val stuId = 001
        fun sayHello() {...}
    }
}

// Invoke(两种方法都可)
val id1 = Student.XiaoMing.stuId	//类名.对象名.成员名
val id2 = Student.stuId	//类名,成员名

3. 无名:每个类有且仅有一个伴生对象,因此可以不指定对象名

// Define
class Student{
    ...
    companion object {
        val name = "XiaoMing"
        val stuId = 001
        fun sayHello() {...}
    }
}

// Invoke(两种方法都可)
val id1 = Student.Companion.stuId	//类名.Companion.成员名, ❗companion首字母大写
val id2 = Student.stuId	//类名,成员名

内部类 inner class

普通嵌套

若类Aa仅对类AA有用,则可将Aa嵌套在AA中,如此更合乎逻辑。

不足:Aa无法使用AA的数据

class AA {
    ...
    class Aa { ... }
}

内部嵌套

自带一个对外部类的对象引用,解决了上述不足。

class AA {
    ...
    inner class Aa { ... }
}

以下是Aa类编译之后的代码:

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_字符串_34

数据类 data class

语法:

  • 可实现,kotlin1.1之后可继承
  • 主构造函数必须有参数
  • 不能为abstract、open、sealed、inner
  • 自动创建函数:hashCode()、equals()、copy()、toString()

copy:可以在copy的同时,修改值。

data class LoginUser(val name: String, val password: String) {
    constructor(name: String) : this(name, "00000")
}

fun main() {
    val p1 = LoginUser("张三","12345")
    val p2 = p1.copy("李四")
    val p3 = LoginUser("李四")

    println(p1)	//LoginUser(name=张三, password=12345)
    println(p2)	//LoginUser(name=李四, password=12345)
    println(p3)	//LoginUser(name=李四, password=00000)
}

枚举类 enum class

简单使用

同:字符串常量
异:较之实现了类型安全

enum class Color{
    Green, REF, BLUE, PINK
}

fun main() {
    val c1 = Color.BLUE
    println("c1 = $c1")	//c1 = BLUE
}

进阶使用

  1. 在主构造函数中加入参数
  2. 自定义方法(记得枚举对象后加“ ; ”)
enum class Color(val rgb: String) {
    Green("#7ED321"), REF("#D0021B"), BLUE("#4A90E2"), PINK("#FFD5DC");
    fun getInfo() = "name = ${this.name}, ordinal = ${this.ordinal}, rgb = ${this.rgb}"
}

fun main() {
    println(Color.Green.getInfo())	//name = Green, ordinal = 0, rgb = #7ED321
    println(Color.BLUE.getInfo())	//name = BLUE, ordinal = 2, rgb = #4A90E2
}

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_35

❗❗注意

  1. 每个枚举常量都是该类的一个对象
  2. 在编译枚举类时,会自动添加一些属性和方法,如下图所示:

密封类 sealed class

可以封装其他类同时发挥数据类的作用。

密封类的构造函数是私有的 Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_android_07

sealed class UserStatus {
    object Unregister : UserStatus()
    class Register(val userId: String) : UserStatus()

    fun check(): String = when (this) {
        is Unregister -> "未注册"
        is Register -> "用户:${this.userId}"
    }
}

fun main() {
    val s0 = UserStatus.Unregister
    val s1 = UserStatus.Register("13525")
    println(s0.check())	// 未注册
    println(s1.check())	// 用户:13525
}

泛型

多用于创建容器类

简单使用

//单泛型类
class Box<T>(item: T) {
    fun getItemInfo(): T?{ ... }	//单泛型方法
    fun<R> changeItemInfos(change: (T) -> R): R?{ ... }	//多泛型方法
}

泛型类型约束

假设有如下4个类,现在规定上述的Box中只能装入三色的小球,
此时可以限制泛型类型为Ball





Ball

BlueBall

RedBall

GreenBall


class Box<T: Ball>() { ... }

类型上界

fun <T: Comparable<T>> gt(x: T, y: T): Boolean = x > y

T: Comparable,表示 Comparable 是类型T的上界。
相当于告诉编译器,类型参数 T 代表的都是实现了 Comparable 接口的类。

类型擦除

泛型是在编译器层次上实现的,
Java和Kotlin的泛型实现,都是采用了运行时类型擦除的方式。
即,生成的class字节码文件中不包含泛型中的类型信息的。

例如,在代码中定义的 List<Object> 和 List<String> ,在编译之后都会变成 List。
JVM 看到的只是 List,而由泛型附加的类型信息对JVM来说是不可见的。

因此泛型类并没有自己独有的Class类对象。
比如,Java中并不存在List<String>.class、List<lnteger>.class,而只有List.class
对应地在Kotin中并不存在MutableList<Fruit>::class,而只有MutableList::class。

擦除的基本过程

  1. 替换:
    把代码中的类型参数,替换成具体的类。
    去掉出现的类型声明,即去掉<Xxx>的内容。
    (java中默认是Object,如果指定了类型参数的上界的话,则使用这个上界。)

T get( ) Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_37 Object get( )
List<String> Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_37

  1. 桥接:
    擦除了类型之后的类可能缺少某些必须的方法,此时编泽器会动态生成桥接方法。

获取擦除的类型 —— 实化类型 reified

泛型在运行时会发生类型擦除,若想获得泛型的真实类型:

  • java:反射
  • kotlin:在内敛函数中,使用 reified 关键字,修饰泛型参数
inline fun <reified T> Any.isType(): Boolean = 
	if (this is T) true else false

fun main(arg: Array<String>) {
    println("123".isType<String>())	//true
    println(123.isType<String>())	//false
}

in & out

综述

对应Java中的PECS(Producer-Extends, Consumer-Super)

Java 通配符

Kotlin 投射类型

Producer

? extends T

指定参数类型上界

out T

只保证读安全

Consumer

? super T

指定参数类型下界

in T

只保证写安全



Food

Hamburger

Noodles


val f: Food = Hamburger()				// ✔
val f: List<Food> = List<Hamburger>()	// ✘
val f: List<Food> = List<Hamburger>()	// out
val f: List<Hamburger> = List<Food>()	// in

Android kotlin 校验两个ArrayList数据是否变化 kotlin replaceall_ide_39

意义

对于泛型类,编译器承担了全部的类型检查工作。
编泽器禁止某些泛型的使用方式,也是为了确保类型的安全性。

若 List<Food>中可以添加所有 Food类及其子类对象,
则 List中可能既有Hamburger又有Noodles,
故 造成List中的元素类型混乱。

示例

协变 out:生产
interface Factory<out T> {
    fun product(type: Int): T
}    //泛型做返回值

class FoodFactory : Factory<Food> {
    override fun product(type: Int): Food = when (type) {
        1 -> Noodles("一份牛肉面", 12.00)
        2 -> Hamburger("一个香辣鸡腿堡", 15.00)
        else -> Food("食物")
    }
}

open class Food(val name: String, val price: Double? = null) {
    override fun toString(): String =
        if (price == null) {
            "生产:${this.name}"
        } else {
            "生产:${this.name},定价:${this.price}"
        }
}
class Noodles(name: String, price: Double) : Food(name, price)
class Hamburger(name: String, price: Double) : Food(name, price)

fun main() {
    val mFactory = FoodFactory()
    val production0 = mFactory.product(0)
    val production1 = mFactory.product(1)
    val production2 = mFactory.product(2)
    println(production0)    //生产:食物
    println(production1)    //生产:一份牛肉面,定价:12.0
    println(production2)    //生产:一个香辣鸡腿堡,定价:15.0
}
逆变 in:消费
interface People<in T> {
    fun consume(item: T): String
}

class Consumer : People<Food> {
    override fun consume(f: Food) =
        if (f.price == null) {
            "消费:${f.name}"
        } else {
            "消费:${f.name}, 花费:${f.price}"
        }
}

open class Food(val name: String, val price: Double? = null)
class Noodles(name: String, price: Double) : Food(name, price)
class Hamburger(name: String, price: Double) : Food(name, price)

fun main() {
    val consumer = Consumer()
    val consumption0 = consumer.consume(Food("食物"))
    val consumption1 = consumer.consume(Noodles("一份牛肉面", 12.00))
    val consumption2 = consumer.consume(Hamburger("一个香辣鸡腿堡", 15.00))
    println(consumption0)    //消费:食物
    println(consumption1)    //消费:一份牛肉面, 花费:12.0
    println(consumption2)    //消费:一个香辣鸡腿堡, 花费:15.0
}
不变:产 + 消
interface Consumer<T> {
    fun consume(item: T): T
}

通配符

若使用时不知道泛型的具体类型,使用泛型通配符:

  • java:<?>
  • kotlin:<*>

星投影

A<*> 的读写都是不安全的,若要安全还是要将星号投影为out或in关键字。

T未规定类型上下界:

原泛型类

星投影

A

读取:A<out Any?>

写入:A

A

A<out Any?>

A

A