kotlin语法

文章目录

  • 前言
    • 主要参考资料:
  • 语法基础
    • 修饰符
      • class
      • fun
      • param
    • 原生字符串
    • 迭代器
      • item ==in== array
      • array==.indices==
      • array==.withIndex()==
      • array==.forEach { }==
      • array.==forEachIndexed { }==
    • Range条件
      • 正向区间
      • 逆向区间
      • 步长
    • 字符串
      • 换行输出
      • replace
      • 等号
      • 遍历
    • 数值类
      • → \rightarrow Int
      • 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

视频:

B站 动脑学院:2021最新最全Kotlin教程Android程序员定制版,Java转Kotlin学它就够了!更新完毕

语法基础

修饰符

class

说明
abstract 抽象
final 不可被继承
enum 枚举
open 可继承
annotation 注解
sealed 密封
data 数据

fun

说明
tailrec 尾递归
operator 运算符重载
infix 中缀
如 使 用 t o 函 数 定 义 一 个 p a i r : " j a c k " . t o ( 18 ) , 可 直 接 写 " j a c k "   t o   18 \color{grey}如使用to函数定义一个pair:"jack".to(18),可直接写"jack"\ to\ 18 使topair"jack".to(18)"jack" to 18
inline 内联
external 外部
suspend 挂起协程

param

说明
vararg 变长
noinline 不内联
crossinline

原生字符串

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

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

kotlin语法_第1张图片

迭代器

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()

kotlin语法_第2张图片

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)

kotlin语法_第3张图片

等号

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}")

kotlin语法_第4张图片

遍历

可使用forEach遍历

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

kotlin语法_第5张图片

数值类

→ \rightarrow Int

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()	//去重,原理见下图

kotlin语法_第6张图片

list.removeIf{ … }

Map

创建

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

image-20220105210643328

安全索引

函数 说明 示例
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]
    })
}

kotlin语法_第7张图片

使用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") }
}

kotlin语法_第8张图片

生成指定序列

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代码
kotlin语法_第9张图片
从图中可知,“==”被编译为了Intrinsics.areEqual()方法。再点进去,可以看到用的就是equals()方法
在这里插入图片描述

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代码:> image-20220106101455580
kotlin语法_第10张图片

解构语法特性

list

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

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

image-20220105185638242

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

kotlin语法_第11张图片

kotlin语法_第12张图片

数据类

data class LoginUser(val name: String, val password: String)

变量权限默认为private
kotlin语法_第13张图片
编译时会自动添加一些方法:get()、copy()、toStirng()、hashCode()、equals()…
kotlin语法_第14张图片

函数

可变参数

实质:长度可变的数组

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

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}")
}

image-20220104133444572

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

 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代码:
kotlin语法_第15张图片

使用内联

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代码:

kotlin语法_第16张图片

注意

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

标准函数

apply、also、let

定义:

kotlin语法_第17张图片

kotlin语法_第18张图片使用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")

kotlin语法_第19张图片

使用2:对比let、also

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

kotlin语法_第20张图片

run、with

定义:

kotlin语法_第21张图片

使用:

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

image-20220104220740702

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代码(右)

kotlin语法_第22张图片

kotlin语法_第23张图片

继承

不可继承 —— 默认

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

kotlin语法_第24张图片

可继承 —— open

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

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

实现

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

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

object对象

∵ kotlin 没有static关键字 → \rightarrow 没有静态属性、静态方法

∴ 替代方法:使用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 { ... }
}

数据类 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
}

kotlin语法_第25张图片

❗❗注意

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

密封类 sealed class

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

密封类的构造函数是私有的 → \rightarrow 密封类的子类只能定义在其内部 or 同一文件中

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 和 List ,在编译之后都会变成 List。
JVM 看到的只是 List,而由泛型附加的类型信息对JVM来说是不可见的。

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

擦除的基本过程

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

T get( ) → \rightarrow Object get( )
List → \rightarrow List。

  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

kotlin语法_第27张图片

意义

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

若 List中可以添加所有 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
写入:A
A A
A A

你可能感兴趣的:(其他,kotlin,android)