# 类与继承
[TOC]
## 类
Kotlin 中使用关键字`class`声明类
```kotlin
class Invoice { /*……*/ }
```
类声明由**类名**、**类头**(指定其类型参数、主构造函数等)以及由花括号包围的**类体**构成。**类头与类体都是可选**的;
如果一个类没有类体,可以省略花括号。
```kotlin
class Empty
```
### 构造函数
在 Kotlin 中的一个类可以有一个**主构造函数**以及一个或多个**次构造函数**。
* 主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后。
```kotlin
class Person constructor(firstName: String) { /*……*/ }
```
如果主构造函数没有任何注解或者可见性修饰符(默认的可见性修饰符时`public`。可以省略不写),可以省略这个 *constructor*关键字。
```kotlin
class Person(firstName: String) { /*……*/ }
```
* 主构造函数不能包含任何的代码。初始化的代码可以放到以 *init*关键字作为前缀的**初始化块(initializer blocks**)中。
在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起:
```kotlin
//sampleStart
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
// 主构造函数参数可以在初始化块中使用
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
//sampleEnd
fun main() {
InitOrderDemo("hello")
}
```
>[info]**注意**,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:
```kotlin
class Customer(name: String) {
// 主构造函数参数也可以在属性初始化器中使用
val customerKey = name.toUpperCase()
}
```
* 事实上,声明属性以及从主构造函数初始化属性(在主构造函数中声明类属性(类成员变量/字段)以及初始化属性),Kotlin 有简洁的语法:
```kotlin
class Person(val firstName: String, val lastName: String, var age: Int) { /*……*/ }
```
与普通属性一样,主构造函数中声明的属性可以是可变的(*var*)或只读的(*val*)。
如果构造函数有注解或可见性修饰符,这个 *constructor*关键字是必需的,并且这些修饰符在它前面:
```kotlin
class Customer public @Inject constructor(name: String) { /*……*/ }
```
更多详情,参见[可见性修饰符](http://www.kotlincn.net/docs/reference/visibility-modifiers.html#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0)
#### 次构造函数
类也可以声明前缀有 *constructor*的**次构造函数**:
```kotlin
class Person {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(parent: Person) {
parent.children.add(this)
}
}
```
* **同时存在主构造函数和二级构造函数时的情况**
如果类有一个主构造函数,每个次构造函数**需要委托给主构造函数**,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 *this*关键字即可:
```kotlin
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
```
请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此**所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块**:
```kotlin
//sampleStart
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
//sampleEnd
fun main() {
Constructors(1)
}
```
运行结果
```
Init block
Constructor
```
* **当类的主构造函数都存在默认值时的情况**
如果一个非抽象类没有声明任何(主或次)构造函数,它会生成一个不带参数的主构造函数,构造函数的可见性是 public。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性(比如private)的空的主构造函数:
```kotlin
class DontCreateMe private constructor () { /*……*/ }
```
> **注意**:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值。这使得Kotlin 更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。
```
class Customer(val customerName: String = "")
```
由此可见:当类存在主构造函数并且有默认值时,次构造函数也适用。
* **示例**
~~~
fun main(args: Array<String>) {
println("--------构造函数使用默认值--------")
var test = Test()
println("--------构造函数使用柱构造函数--------")
var test1 = Test(1, 2)
println("--------构造函数使用次构造函数--------")
var test2 = Test(4, 5, 6)
}
class Test constructor(num1: Int = 10, num2: Int = 20) {
init {
println("num1 = $num1\t num2 = $num2")
}
constructor(num1: Int = 1, num2: Int = 2, num3: Int = 3) : this(num1, num2) {
println("num1 = $num1\t num2 = $num2 \t num3 = $num3")
}
}
~~~
运行结果
```
--------构造函数使用默认值--------
num1 = 10 num2 = 20
--------构造函数使用柱构造函数--------
num1 = 1 num2 = 2
--------构造函数使用次构造函数--------
num1 = 4 num2 = 5
num1 = 4 num2 = 5 num3 = 6
```
### 创建类的实例
要创建一个类的实例,我们就像调用普通函数一样调用构造函数:
```kotlin
val invoice = Invoice()
val customer = Customer("Joe Smith")
```
>[warning]注意: Kotlin 并没有 *new*关键字。
创建嵌套类、内部类与匿名内部类的类实例在[嵌套类](http://www.kotlincn.net/docs/reference/nested-classes.html)中有述。
### 类成员
类可以包含:
* [构造函数与初始化块](http://www.kotlincn.net/docs/reference/classes.html#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0)
* [函数](http://www.kotlincn.net/docs/reference/functions.html)
* [属性](http://www.kotlincn.net/docs/reference/properties.html)
* [嵌套类与内部类](http://www.kotlincn.net/docs/reference/nested-classes.html)
* [对象声明](http://www.kotlincn.net/docs/reference/object-declarations.html)
## 继承
在 Kotlin 中所有类都有一个共同的超类 `Any`,这对于没有超类型声明的类而言是默认超类,可参考[根类型“Any”](https://www.kancloud.cn/alex_wsc/android_kotlin/1046286):
```kotlin
class Example // 从 Any 隐式继承
```
`Any` 有三个方法:`equals()`、 `hashCode()` 与 `toString()`。因此,所有 Kotlin 类都定义了这些方法。
如需声明一个显式的超类型(open表示允许其它类继承,和Java中final相反,**默认情况下,Kotlin所有类都是final**),请在类头中把超类型放到冒号之后:
```kotlin
open class Base(p: Int)
class Derived(p: Int) : Base(p)
```
### 继承类的构造函数
如果派生类有一个主构造函数,其基类型可以(并且必须)用基类的主构造函数参数就地初始化。
如果派生类没有主构造函数,那么每个次构造函数必须使用 *super* 关键字初始化其基类型,或委托给另一个构造函数做到这一点。
>[info]注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
```kotlin
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
```
#### 无主构造函数
如果派生类没有主构造函数,那么每个次构造函数必须使用 *super* 关键字初始化其基类型,或委托给另一个构造函数做到这一点。
例:这里举例在`Android`中常见的自定义View实现,我们熟知,当我们指定一个组件是,一般实现继承类(基类型)的三个构造函数。
```
class MyView : View(){
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
}
```
可以看出,当实现类无主构造函数时,分别使用了`super()`去实现了基类的三个构造函数。
#### 存在主构造函数
如果派生类有一个主构造函数,其基类型可以(并且必须)用基类的主构造函数参数就地初始化。
当存在主构造函数时,主构造函数一般实现基类型中参数最多的构造函数,参数少的构造函数则用`this`关键字引用即可了
```
class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
: View(context, attrs, defStyleAttr) {
constructor(context: Context?) : this(context,null,0)
constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}
```
### 覆盖方法
我们之前提到过,Kotlin 力求清晰显式。因此,Kotlin 对于可覆盖的成员(我们称之为*open*)以及覆盖后的成员需要**显式修饰符(open)**:
```kotlin
open class Shape {
open fun draw() { /*……*/ }
fun fill() { /*……*/ }
}
class Circle() : Shape() {
override fun draw() { /*……*/ }
}
```
`Circle.draw()` 函数上必须加上 *override* 修饰符。如果没写,编译器将会报错。如果函数没有标注 *open*如`Shape.fill()`,那么子类中不允许定义相同签名的函数,不论加不加 **override**。将 *open*修饰符添加到 final 类(即没有 *open*的类)的成员上不起作用。
标记为 *override*的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用 *final* 关键字:
```kotlin
open class Rectangle() : Shape() {
final override fun draw() { /*……*/ }
}
```
1. 当基类中的函数,没有用`open`修饰符修饰的时候,实现类中出现的函数的函数名不能与基类中没有用`open`修饰符修饰的函数的函数名相同,不管实现类中的该函数有无`override`修饰符修饰。
```
open class Demo{
fun test(){} // 注意,这个函数没有用open修饰符修饰
}
class DemoTest : Demo(){
// 这里声明一个和基类型无open修饰符修饰的函数,且函数名一致的函数
// fun test(){} 编辑器直接报红,根本无法运行程序
// override fun test(){} 同样报红
}
```
当一个类不是用`open`修饰符修饰时,这个类默认是`final`的,`class A{}`等价于`final class A{}`
注意,则的`final`修饰符在编辑器中是灰色的,因为Kotlin中默认的类默认是final的
2. 标记为 *override*的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次被覆盖,使用 *final* 关键字。
### 覆盖属性
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 *override*开头,并且父类和子类它们必须具有兼容的数据类型。每个声明的属性可以由具有初始化器的属性或者具有 `get` 方法的属性覆盖。
```kotlin
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4//覆盖了vertexCount,赋予它新的数值4
}
```
* 你也可以用一个 `var` 属性覆盖一个 `val` 属性,但反之则不行。这是允许的,因为一个 `val` 属性本质上声明了一个 `get` 方法,而将其覆盖为 `var` 只是在子类中额外声明一个 `set` 方法。
* 请注意,你可以在主构造函数中使用 *override*关键字作为属性声明的一部分。
```kotlin
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
class Polygon : Shape {
override var vertexCount: Int = 0 // 以后可以设置为任何数
}
```
* **Getter()函数慎用super关键字**
~~~
open class Demo {
open val valStr = "我是用val修饰的属性"
}
class DemoTest : Demo() {
/*
* 这里介绍重写属性是,getter()函数中使用`super`关键字的情况
*/
override var valStr: String = "abc"
get() = super.valStr
set(value) {
field = value
}
}
fun main(arge: Array<String>) {
println(DemoTest().valStr)
val demo = DemoTest()
demo.valStr = "1212121212"
println(demo.valStr)
}
~~~
运行结果
```
我是用val修饰的属性
我是用val修饰的属性
```
从上面的执行结果,得出:在实际的项目中在重写属性的时候不用`get() = super.xxx`,因为这样的话,不管你是否重新为该属性赋了新值,它还是支持`setter()`,在使用的时候都调用的是基类中的属性值。
* **在主构造函数中重写**
```
open class Demo {
open val num = 0
open val valStr = "我是用val修饰的属性"
}
class DemoTest2(override var num: Int, override val valStr: String) : Demo()
fun main(args: Array<String>) {
val demo2 = DemoTest2(1, "构造函数中重写")
println("num = ${demo2.num} \t valStr = ${demo2.valStr}")
}
```
运行结果
```
num = 1 valStr = 构造函数中重写
```
### 派生类(子类)初始化顺序
在构造派生类的新实例的过程中,第一步完成其基类的初始化(在此之前只有对基类构造函数参数的检测),因此发生在派生类的初始化逻辑运行之前。
~~~
//sampleStart
open class Base(val name: String) {
init {
println("Initializing Base")//二
}
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }//三
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {//一
init {
println("Initializing Derived")//四
}
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }//五
}
//sampleEnd
fun main() {
println("Constructing Derived(\"hello\", \"world\")")
val d = Derived("hello", "world")
}
~~~
执行结果,如上面代码标注的顺序一样
```
Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10
```
这意味着,基类(父类)构造函数执行时,派生类(子类)中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑中(直接或通过另一个覆盖的 *open*成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 *init*块中使用 *open*成员。
![](https://img.kancloud.cn/13/a8/13a84bd8c99178e4edd685a9ed483911_1257x707.png)
### 调用超类(父类)实现
派生类(子类)中的代码**可以使用 *super*关键字调用其超类的函数与属性访问器的实现**:
```kotlin
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
```
在一个内部类中访问外部类的超类,可以通过由外部类名限定的 *super*关键字来实现:`super@Outer`:
```kotlin
class FilledRectangle: Rectangle() {
fun draw() { /* …… */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { /* …… */ }
fun drawAndFill() {
super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
fill()
println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
}
}
}
```
### 覆盖规则
在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类(父类)继承相同成员的多个实现,它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。
为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 *super*,如 `super<Base>`:
```kotlin
open class Rectangle {
open fun draw() { /* …… */ }
}
interface Polygon {
fun draw() { /* …… */ } // 接口成员默认就是“open”的
}
class Square() : Rectangle(), Polygon {
// 编译器要求覆盖 draw():
override fun draw() {
super<Rectangle>.draw() // 调用 Rectangle.draw()
super<Polygon>.draw() // 调用 Polygon.draw()
}
}
```
可以同时继承 `Rectangle` 与实现接口 `Polygon`,但是二者都有各自的 `draw()` 实现,所以我们必须在 `Square` 中覆盖 `draw()`,并提供其自身的实现以消除歧义。
示例
~~~
open class A {
open fun test1() {
println("基类A中的函数test1()")
}
open fun test2() {
println("基类A中的函数test2()")
}
}
interface B {
fun test1() {
println("接口类B中的函数test1()")
}
fun test2() {
println("接口类B中的函数test2()")
}
}
class C : A(), B {
override fun test1() {
super<A>.test1()
super<B>.test1()
}
override fun test2() {
super<A>.test2()
super<B>.test2()
}
}
fun main(args: Array<String>) {
val C = C()
C.test1()
C.test2()
}
~~~
运行结果
```
基类A中的函数test1()
接口类B中的函数test1()
基类A中的函数test2()
接口类B中的函数test2()
```
## 抽象类
类以及其中的某些成员可以声明为 *abstract*。抽象成员在本类中可以不用实现。
需要注意的是,我们并不需要用 `open` 标注一个抽象类或者函数——因为这不言而喻。
我们可以用一个抽象成员覆盖一个非抽象的开放成员
```kotlin
open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
override abstract fun draw()
}
```
## 伴生对象
如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内[对象声明](http://www.kotlincn.net/docs/reference/object-declarations.html)中的一员。
更具体地讲,如果在你的类内声明了一个[伴生对象](http://www.kotlincn.net/docs/reference/object-declarations.html#%E4%BC%B4%E7%94%9F%E5%AF%B9%E8%B1%A1),你就可以调用其成员,只是以类名作为限定符。
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android