[Kotlin] Kotlin学习笔记
Kotlin学习笔记
目录
菜鸟教程
HelloWorld
package dev.bysknight
fun main(args: Array<String>) {
println("Hello World!")
}
package dev.by_sknight
class Hello(val name: String) {
fun display() {
println("Hello $name!");
}
}
fun main(args: Array<String>) {
Hello("World").display()
}
Kotlin基础语法
文件
- Kotlin文件以
.kt
为后缀
包声明
package dev.bysknight
- 源文件不需要相匹配的目录和包,源文件可以放在任何文件目录,但是内部的函数、类的全名为
dev.bysknight.className
- 未声明包时默认为
default
包 - 默认导入以下包
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.*
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
函数定义
- 函数定义使用关键字
fun
,参数格式类型为参数: 类型
fun sum(a: Int, b: Int): Int { // 最后的Int是返回值类型 return a + b; }
- 表达式作为函数体的时候,返回类型可以自推断,但是如果是public方法,则必须明确指出返回值
fun sum(a: Int, b: Int) = a + b public fun sum(a: Int, b: Int): Int = a + b
- 无返回值的函数,默认返回值视为
Unit
,即使是public也可以fun printSum(a: Int, b: Int): Unit { println(a + b) } public fun printSum(a: Int, b: Int) { println(a + b) }
- 可变长参数函数,可以使用
vararg
关键字进行标识fun vars(vararg v: Int) { for(vt in v) { print(vt) } }
- lambda表达式(匿名函数)
fun main(args: Array<String>) { val sumLambda: (Int, Int) -> Int = {x,y -> x + y} println(sumLambda(1, 2)) // 输出值为3 }
常量与变量
- 可变变量使用
var
关键字定义var <标识符>: <类型> = <初始化值>
- 不可变变量使用
val
关键字定义,只可初始化一次。val <标识符>: <类型> = <初始化值>
- 可变变量与不可变变量都可以在定义时不初始化,但是在使用之前一定要初始化
- 编译器支持自动类型推断,即在声明时可以不用指定类型,但是Kotlin是强类型语言
- 如生命变量x
var x = 15
修改时x = 2
可以x = "hello"
不可以
注释
- 单行注释
// ...
- 多行注释
/* ... */
字符串模板
$
表示变量名或变量值$varName
表示变量值${varName.fun()}
表示变量的方法返回值var a = 1 val s1 = "a is $a" println(s1) a = 2 val s2 = "${s1.replace("is", "was")}, but now a is $a" println(s2)
NULL检查机制
- Kotlin对于声明为可为空的参数,在使用时会进行空判断处理,有两种处理方式
- 第一种,字段后加
!!
像java一样抛出空异常 - 第二种,字段后加
?
遇到空指针可不做处理,返回值为null,如果有?:
时,空指针时会返回后面的值var age: String? = null var ages = age!!.toInt() // 会抛出异常 println(ages) var ages1 = age?.toInt() // ages1为null println(ages1) val ages2 = age?.toInt() ?: 10 // ages2为10 println(ages2)
类型检测与自动类型转换
- 可以使用
is
来检测一个表达式是否为某类型的一个实例 if (obj is String) { /* 做过类型判断之后,obj会被系统自动转换为String类型 */ }
- 可以使用
!is
来检测是否不是某类型的一个实例if (obj is String) { /* 在这里 obj 被转换为 String */ } /* 之后的地方没有被转换为String */
if (obj !is String) { /* 在这里obj 没有被转换为 String */ } /* 在之后,obj被转换为String */
区间
- 区间表达式由
..
的rangeTo函数辅以in
和!in
形成 ..
是升序的,使用downTo
可以指定降序,使用until
可以不包含最大值 类似于[1, 4)
- 可以通过
step
来指定步长for (i in 1..4) println(i) for (i in 1..10 step 3) println(i) for (i in 4 downTo 1) println(i) for (i in 1 until 4) // 不包含4 println(i)
Kotlin基础数据类型
基本数值类型
Byte
8位Short
16位Int
32位Long
64位Float
32位Double
64位
字面常量
- 十进制 123
- 长整型以大写的L结尾 123L
- 16进制以0x开头 0x0F
- 2机制以0b开头 0x00010001
- 不支持8进制
- 单精度浮点以F或f结尾 123.5f
- 双精度浮点 123.5
- 可以使用下划线使数字常量更易读
var oneMillion = 1_000_000_000
比较数字
- Kotlin中任何变量都是一个对象,比较两个数字的时候分为两种,
===
比较对象地址==
比较值大小
类型转换
- 由于每个变量都是对象,较小类型不是较大类型的子类型,较小的类型不能隐式转换为较大的类型。
val b: Byte = 3 // 字面值是静态检测的 val a: Int = b // 错误,Byte类型不能转换为Int类型
- 由于每个变量都是对象,所有每种数据类型都有以下方法
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
- 有些情况可以根据上下文推断出类型
val x = 1L + 3 // Long + Int => Long
位操作符
shl(bits)
左移位 等价于Java的<<
shr(bits)
右移位 等价于Java的>>
ushr(bits)
无符号右移位 等价于Java的>>>
and(other)
与or(other)
或xor(other)
异或inv()
反向
字符
- Kotlin的
Char
类型不能直接和数字操作,Char
类型必须是单引号'
包含起来的 c.toInt() - '0'.toInt()
布尔类型
Boolean
类型的变量只有两个值true
和false
- 内置的布尔运算有
||
短路逻辑或||
短路逻辑与!
逻辑非
数组
- 数组用类
Array
实现,有size
属性及get
set
方法 []
重载了get
与set
方法- 数组的创建方式有两种,一种是使用函数arrayOf(),另一种是使用工厂函数,见代码实例
var array1: Array<Int> = arrayOf(1, 2, 3) var array2 = Array(3, {i -> i * 2})
- 其他相似的类有
ByteArray
ShortArray
IntArray
字符串
- 和Java一样,
String
是不可变的。方括号[]
可以获取字符串中的字符,也可以使用for
循环for (c in str) { println(c) }
- Kotlin支持多行字符串,使用三个引号
"""
val text = """ hello world ! """ println(text) // 前面有前置空格 val str = """ hello world ! """.trimIndent() println(str) // 去除了前置空格 val str1 = """ |hello |world |! """.trimMargin() println(str1) // 去除了前置空格,默认使用 | ,也可以自定义 trimMargin(">") 这种
字符串模板
- 字符串可以包含模板表达式,即一小段代码,会求值并将结果合并到字符串中
- 模板表达式一般以
$
开头,之后可以是变量名或以花括号括起来的表达式 val s = "i = $a"
如果变量a的值为10,s的值将会是"i = 10"
val s = "i = ${"100".toInt()}"
s的值为"i = 100"
- 如果需要使用
$
字符,可以这样val price = "${"$"}100"
Kotlin 条件控制
if表达式
- 传统用法和java类似
- if表达式的结果可以赋值给变量
val maxValue = if (a > b) a else b
- 可以使用区间
if (x in 1..4)
When表达式
- 类似于其他语言的
switch
其中的else
相当于default
when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> { print("x 不是 1,也不是 2") } }
- 多条分支需要同样的方式处理时,吧多个分支条件放在一起,用逗号分隔
when (x) { 1, 2 -> print("x == 1 or x == 2") else -> print("otherwise") }
- 也可以检测一个值在不在一个集合或区间中
in
!in
when (x) { in 0..9 -> print("x > 0 and x < 9") else -> print("otherwise") }
- 可以检测一个值是或不是一个特定类型的值
is
!is
Kotlin 循环控制
for循环
- for循环可以对任何提供迭代器的对象进行遍历
for (item in collection) print(item)
var items = listOf("apple", "banana", "peach") for (item in items) { println(item) } for (item in items.indices) { println("items[${item}] = ${items[item]}") }
while和do...while循环
- 判断条件都是布尔表达式,区别在于do...while至少会执行一次
var x: Int = 0 while (x < 5) { println(x) x++ } do { println(x) x++ } while (x < 5)
返回和跳转
return
直接跳出函数break
跳出直接包围它的循环continue
跳过后续代码,继续下一次最直接包围它的循环- 可以通过使用标签来跳出多重循环
flag@ for (i in 1..10) { for (j in 1..10) { println("($i, $j)") if (i == 3 && j == 1) break@flag } }
- lambda表达式中返回,默认
return
会直接从函数中返回,要想从包围它的lambda表达式中返回,可以使用加标签的方式或者使用与lambda表达式同名的标签,或者使用匿名函数fun foo() { ints.forEach lit@ { if (it == -1) return // 直接从foo函数返回 if (it == 0) return@forEach // lambda表达式同名标签 if (it == 1) return@lit // 从标签lit处返回 } } fun foo() { ints.forEach(fun(value: Int) { if (value == 0) return // 使用了匿名函数 }) }
Kotlin类和对象
类
- Kotlin类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明
- Kotlin中使用关键字
class
声明类,后面紧跟类名 - 类的属性可以根据是否为只读选择使用关键字
var
或val
声明 - Kotlin中没有new关键字,创建实例时可以像普通函数一样
class ClassName { // 定义类 ClassName var name: String = "name" // 类的属性 name } var obj = ClassName() // 创建一个类实例 obj.name // 使用.访问实例的属性
- Kotlin中的类,可以有一个主构造器,以及其他次构造器,主构造器是类头部的一部分
class Person constructor(firstName: String) {}
- 如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略掉
class Person(firstName: String) {}
- setter与getter 中filed关键字可以用于当前属性,属性声明的完整语法为
var <propertyName>[: <PropertyType>] [= <property_initializer>] [<getter>] [<setter>]
var lastName: String = "zhang" get() = field set(value) { field = value }
- 非空属性必须定义时进行初始化,可以使用
lateinit
关键字来延迟初始化lateinit var name: String
构造
- 主构造器中不含任何代码,初始化代码可以放在初始化代码段中,初始化代码段以
init
作为前缀 - 主构造器中的参数可以在初始化代码段中使用,也可以在类定义的属性初始化代码中使用
- 一种简洁语法,可以通过主构造器来定义属性并初始化属性值
class Person constructor(firstName: String) { init { println("first name is $firstName") } }
- 次构造函数,需要加前缀
constructor
,并且写在类体中 - 如果类有主构造函数,次构造函数需要使用另一个构造函数时,可以使用
this
关键字class Person constructor(firstName: String) { init { println("firstName is $firstName") } constructor(firstName: String, lastName: String): this(firstName) { println("lastName is $lastName") } }
抽象类、嵌套类、内部类、匿名内部类
- 抽象类中,类本身、类中的部分成员,都可以声明为
abstract
的。抽象成员在类中不存在具体的实现abstract class AClass { abstract var name: String }
- 嵌套类,可以将一个类,嵌套到另一个类中,外部类可以直接使用,嵌套类需要加外部类的前缀
Outer.Nested()
,就像嵌套类是外部类的一个函数那样 - 调用格式
外部类.嵌套类.嵌套类方法/属性
class Outer { class Nested { } }
- 内部类,使用
inner
关键字来表示,内部类会带有一个对外部类对象的引用,内部类可以使用外部类成员属性和方法 - 为了消除歧义,当访问外部类的引用时,使用
this@label
其中@label是一个指代this来源的标签class Outer { var v: Int = 10 inner class Inner { fun display() { var iv = this@Outer.v println(iv) } } }
- 匿名内部类,使用对象表达式可以创建匿名内部类
class Ca { fun callIc(obj: Ic) { obj.display() } } interface Ic { fun display() } fun main(args: Array<String>) { var a = Ca() a.callIc(object : Ic { override fun display() { println("匿名内部类") } }) }
类属性修饰符与访问权限修饰符
- 类的属性修饰符,标示类本身特征
abstract
抽象类final
类不可继承,默认属性enum
枚举类open
类可继承annotation
注解类
- 访问权限修饰符
private
仅在同文件可见protected
同文件中或同一子类可见public
任何地方都可见internal
在同一个模块中可见
Kotlin继承
继承
- Kotlin中所有的类,都继承自Any类,Any类默认提供了三个函数
equals()
hashCode()
toString()
- 如果一个类,想要成为基类,需要用
open
关键字进行修饰
继承与构造函数
- 子类有主构造函数:则基类必须在主构造函数中立即初始化
- 子类中没有主构造函数:则必须在每一个二级构造函数中用
super
关键字初始化基类open class Person(var name: String, var age: Int) { init { println("基类主构造方法") } } class Student1(name: String, age: Int, var no: String, var score: Int) : Person(name, age) { init { println("子类1主构造方法") } } class Student2 : Person { constructor(name: String, age: Int) : super(name, age) { println("子类2次构造方法") } } fun main(args: Array<String>) { var stu1: Student1 = Student1("stu1", 18, "123123", 80) var stu2: Student2 = Student2("stu2", 19) }
继承与重写
- 在基类中,使用
fun
修饰的方法,默认被final
修饰,无法重写,可以添加open
关键字来允许重写 - 在子类中,重写父类的方法要使用
override
关键字 - 在子类中,重写父类的属性也要使用
override
关键字,可以使用var属性重写val属性,反之不行
Kotlin接口
接口
- 使用
interface
关键字定义接口- 允许接口中的方法有默认实现
- 接口中的属性只能是抽象的,不允许初始化
- 一个类或者对象可以实现一个或多个接口,需要用
override
修饰从接口继承来的属性和方法 - 当实现多个接口时,必须实现多个接口继承的同名方法
interface AInt {
fun show() {
println("Interface AInt")
}
fun showA() {
println("Interface AInt")
}
}
interface BInt {
fun show() {
println("Interface BInt")
}
fun showB() {
println("Interface BInt")
}
}
class CInt: AInt, BInt {
override fun show() {
super<AInt>.show()
super<BInt>.show()
}
}
Kotlin 扩展
扩展
- Kotlin可以对类的属性和方法进行扩展,是一种静态行为,对于被扩展的类代码本身不会造成任何影响
扩展函数
-
扩展函数可以在已有类中添加新的方法,不会对原类做修改,格式如下
fun receiverType.functionName(params){ body }
receiverType
表示函数的接受者,也就是函数扩展的类functionName
扩展函数的名称params
扩展函数的参数,可以为空- 可以在代码体中使用
this
来指代当前类对象
class Personkz(name: String) { var name: String = name } fun Personkz.display() { println("name = $this.name") } fun main(args: Array<String>) { var p1: Personkz = Personkz("p1") p1.display() }
-
扩展函数是静态解析的,在调用扩展函数时,不是动态确定的
open class Akz class Bkz: Akz() fun Akz.show() = "akz" fun Bkz.show() = "bkz" fun show(akz: Akz) { println(akz.show()) } fun main(args: Array<String>) { show(Bkz()) // 结果为 akz }
-
如果扩展函数与成员函数一致,优先使用成员函数
class Ckz { fun show() = "成员函数" } fun Ckz.show() = "扩展函数" fun main(args: Array<String>) { println(Ckz().show()) // 结果为 成员函数 }
-
在扩展函数中,可以使用
this
来判断扩展对象是否为null
,然后自定义处理步骤
扩展属性
- Kotlin支持对属性的扩展
- 扩展属性允许定义在类或文件中,不允许定义在函数中
- 扩展属性不能被初始化,可以定义显式的getter/setter
class Dkz var Dkz.name: Int get() = field set(value) { field = value }
伴生对象的扩展
- 如果类有伴生对象,可以为伴生对象定义扩展函数或扩展属性
- 伴生对象声明的扩展函数,通过类名限定符来调用
class Ekz { companion object {} } fun Ekz.Companion.display() { println("伴生对象的扩展函数") } fun main(args: Array<String>) { Ekz.display() }
扩展的作用域
- 通常扩展函数定义或属性定义在顶级包下,使用时通过import导入
// 文件1 package dev.kuozhan fun Akz.show() { ... } // 文件2 package dev.user import dev.kuozhan.show // 导入所有名为 show 的扩展 // 或者 import dev.kuozhan.* // 从 dev.kuozhan 导入一切 fun show(akz: Akz) { akz.show() }
扩展声明为成员
- 不太理解,暂未做笔记
Kotlin数据类与密封类
数据类
- Kotlin可以只创建一个包含数据的类,关键字为
data
- 数据类默认会实现以下方法
equals()/hashCode()
toString()
componentN() functions
copy()
可以生成当前对象的一个拷贝,可以传参来修改对应值
- 数据类允许解构使用
- 标准数据类有
Pair
和Triple
,可以查阅相关资料去了解
// 只包含数据的类定义 (数据类)
data class User(var name: String, var age: Int)
// copy()使用
var jake: User = User("jake", 1)
var olderJake: User = jake.copy(age = 2)
// 解构
var (name, age) = jake // 此时 name = "jake", age = 1
密封类
- 密封类用来表示受限的类继承解构
- 密封类适用于when表达式
// 菜鸟教程的实例,未理解
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
Kotlin泛型
泛型类
- 同其他语言一样,将类型参数化
- 使用泛型类时,可以指定类型,或者自动类型推断
// 声明泛型类
class Boxfx<T> (t: T) {
var value: T = t;
}
// 泛型类实例
var box1 = Boxfx(1)
var box2: Boxfx<Int> = Boxfx<Int>(2)
泛型约束
- Kotlin中可以使用泛型约束来对泛型的类型上界进行约束
// 泛型约束,只有Comparable<T>的子类型才可以替代T
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
型变
- 声明处型变分为两种
in
和out
,in
只能作为参数,不能作为返回值,out
只能作为返回值,但不能作为入参
class Afx<in A> (a: A) {
fun display(a: A) {
println(a)
}
}
class Bfx<out B> (val b: B) {
fun show() : B {
return b
}
}
fun main() {
Afx(5).display(10) // 显示 10
println(Bfx(10).show()) // 显示 10
}
星号投射
- 先跳过
Kotlin枚举类
枚举类
- 枚举类使用关键字
enum
声明
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
fun main(args: Array<String>) {
var color:Color=Color.BLUE
println(Color.valueOf("RED")) // RED
println(color.name) // BLUE
println(color.ordinal) // 2
}
Kotlin对象表达式和对象声明
对象表达式
- 对象表达式可以用于参数中,来对某个类进行微量修改而无需创建一个新的类
- 对象表达式的类型使用
object
修饰,之后是继承的目标类 - 这种匿名对象可以作为私有方法的返回值,但是不能作为公有方法的返回值。
open class Adx(name: String) {
val name: String = name
}
interface Bdx {
fun show()
}
fun main() {
var a1 = object: Adx("a1"), Bdx {
override fun show() {
println("a1 show()")
}
}
}
对象声明
- 使用
object
来声明一个对象,主要用于单例,这种也可以有超类型(继承关系) - 如果声明在某个类中,类外访问它时需要加类名,它也无法访问到类外
object a2 {
val name = "a2"
fun show() {
println(name)
}
}
fun main() {
a2.show()
}
伴生对象
- 类内部的对象使用
companion
关键字修饰之后,就可以通过外部类直接使用内部对象的内部成员属性或方法 - 声明的时候,可以省略掉对象名
- 一个类中,最多只能有一个伴生对象
class Cdx {
companion object objName { // 这里的 objName 是可以省略的
fun show() {
println("伴生对象的方法")
}
}
}
fun main() {
Cdx.show();
}
Kotlin委托
委托
- 委托模式是软件设计模式中的一项基本技巧
- 在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理
- Kotlin 通过关键字
by
来实现委托
类委托
- 类委托就是一个类中定义的方法实际上是调用另一个对象中的方法来实现
interface Awt {
fun display()
}
class Bwt: Awt {
override fun display() {
println("Bwt类实现的display()方法")
}
}
// Cwt将要实现的Awt的接口方法,委托给了Bwt
class Cwt: Awt by Bwt()
fun main() {
var c: Cwt = Cwt()
c.display()
}
属性委托
- 属性委托是指一个类的某些属性值不是在当前类中直接定义,而是将其托付给另一个代理类,由其进行统一管理
- 属性委托不必实现任何接口, 但必须提供
getValue()
函数 - 还是不太理解,先跳过
// 想要将属性委托出去的类
class Ddx {
var name: String by Edx();
}
// 接受了属性委托的类
class Edx {
var name: String = ""
operator fun getValue(ddx: Ddx, property: KProperty<*>): String {
return "这里委托了 $ddx 对象的 ${property.name} 属性,其值为 $name"
}
operator fun setValue(ddx: Ddx, property: KProperty<*>, s: String) {
name = s
println("$ddx 对象的 ${property.name} 属性,被赋值为 $s")
}
}
fun main(args: Array<String>) {
val d = Ddx()
println(d.name) // 访问该属性,调用 getValue() 函数
d.name = "Hello" // 调用 setValue() 函数
println(d.name)
}
其他
- 后续的委托内容先暂时跳过