以下仅记录了对我个人而言有需要的部分,并不详尽。
书籍:
《Kotlin从基础到实战》黑马程序员 ISBN:9787115494405
《Kotlin从入门到进阶实战》陈光剑 ISBN:9787302508724
视频:
B站 动脑学院:2021最新最全Kotlin教程Android程序员定制版,Java转Kotlin学它就够了!更新完毕
符 | 说明 |
---|---|
abstract | 抽象 |
final | 不可被继承 |
enum | 枚举 |
open | 可继承 |
annotation | 注解 |
sealed | 密封 |
data | 数据 |
符 | 说明 |
---|---|
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 如使用to函数定义一个pair:"jack".to(18),可直接写"jack" to 18 |
inline | 内联 |
external | 外部 |
suspend | 挂起协程 |
符 | 说明 |
---|---|
vararg | 变长 |
noinline | 不内联 |
crossinline |
使用3对引号括起来,输出字符串中全部原有内容,不会发生转义。
fun main() {
val str = "hello \nworld!"
println(str)
}
for (item in array) {
println(item)
}
for (i in array.indices) {
println(array[i])
}
for ((index, value) in array.withIndex()) {
println("the element at ${index} is ${value}")
}
forEach是一个单参数的匿名函数,故用it关键词。
array.forEach {
println(it)
}
array.forEachIndexed { index, i -> println("$index, $i") }
判断包含关系
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()
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 | 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}")
可使用forEach遍历
"a b c d e".forEach {
println(it)
}
1.23.roundToInt() //四舍五入
1.23.toInt() //去尾法
"1.23".toInt() //抛出异常
"1.23".toIntOrNull() //输出null
"%.2f".format(1.2345) //四舍五入,保留两位(String类型)
分为两大类型:只读、不可变
List:集合元素可重复
Set:集合元素不可重复
Map:键值对
var set = setOf(11,22,33,22)
print(set) // [11, 22, 33]
list.distinct() //去重,原理见下图
var m = mapOf("linr" to 10, "uuln" to 20) //key to value
print(m) //{linr=10, uuln=20}
函数 | 说明 | 示例 |
---|---|---|
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]
})
}
使用2:
// 两者等价
list0.getOrElse(5) {"there is noting"}
list0.getOrNull(5)?: "there is noting"
设计理念:
不可变数据 的副本,在链上的 函数间 传递(即,不会改变原对象)
有点儿像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]
}
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]
}
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") }
}
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代码
从图中可知,“==”被编译为了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
list支持解构语法特性:在一个表达式里给多个变量赋值。
var str = "he is a,student"
val (s1,s2,s3,s4) = str.split(" ",",") //split返回List集合
print("$s1: $s4") //输出student
此处无需用s2、s3,可用 “ _ ” 代替:
data class LoginUser(val name: String, val password: String)
变量权限默认为private
编译时会自动添加一些方法:get()、copy()、toStirng()、hashCode()、equals()…
实质:长度可变的数组
特点:参数类型确定,参数个数不确定
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}")
}
若要直接传递数组为参数,需使用”*“前缀操作符,意为将数组展开(不可用于集合)。
val scores = intArrayOf(83, 97, 91)
countTotalScore("张三", *scores)
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))
}
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代码:
使用lambda的递归函数内联,编译会发出警告,因为会导致复制粘贴无限循环。
定义:
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")
使用2:对比let、also
val res = 2.let{
it + 1
}
val res1 = 2.also{
it + 1
}
println("let: $res")
println("also: $res1")
定义:
使用:
val res = "1234567".run {
length >= 10
}
val res1 = with("1234567"){
length > 0
}
print("res = $res ; res1 = $res1")
目标对象.takeIf { 条件判断句 }.后续操作
takeIf效果类似if,但可直接在对象实例上调用,故有如下优势:
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工具类
var 类型参数 目标类型.属性名: 扩展属性的类型
定义在:
声明类的同时,声明构造函数。
下图:kotlin源码(左),反编译java代码(右)
所有类默认使用final关键字修饰,不可被继承,方法同理。
可继承的类、方法,均用open修饰
open class Person {
open fun breath() = "活着就要呼吸"
}
接口及其方法默认使用open、abstract关键字修饰。
可以有默认实现,但一般不这样使用。
∵ kotlin 没有static关键字 → \rightarrow → 没有静态属性、静态方法
∴ 替代方法:使用object关键字(常写在伴生对象中)
相当于创建单例类
因为只有一个实例,故类名也是其实例的名称,调用时无需自己再实例一次。
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 //类名,成员名
若类Aa仅对类AA有用,则可将Aa嵌套在AA中,如此更合乎逻辑。
不足:Aa无法使用AA的数据
class AA {
...
class Aa { ... }
}
自带一个对外部类的对象引用,解决了上述不足。
class AA {
...
inner class Aa { ... }
}
语法:
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 Color{
Green, REF, BLUE, PINK
}
fun main() {
val c1 = Color.BLUE
println("c1 = $c1") //c1 = BLUE
}
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
}
可以封装其他类同时发挥数据类的作用。
密封类的构造函数是私有的 → \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
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
因此泛型类并没有自己独有的Class类对象。
比如,Java中并不存在List
对应地在Kotin中并不存在MutableList
T get( ) → \rightarrow → Object get( )
List→ \rightarrow → List。
泛型在运行时会发生类型擦除,若想获得泛型的真实类型:
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
}
对应Java中的PECS(Producer-Extends, Consumer-Super)
Java 通配符 | Kotlin 投射类型 | |
---|---|---|
Producer | ? extends T 指定参数类型上界 |
out T 只保证读安全 |
Consumer | ? super T 指定参数类型下界 |
in T 只保证写安全 |
val f: Food = Hamburger() // ✔
val f: List<Food> = List<Hamburger>() // ✘
val f: List<Food> = List<Hamburger>() // out
val f: List<Hamburger> = List<Food>() // in
对于泛型类,编译器承担了全部的类型检查工作。
编泽器禁止某些泛型的使用方式,也是为了确保类型的安全性。
若 List
中可以添加所有 Food类及其子类对象,
则 List中可能既有Hamburger又有Noodles,
故 造成List中的元素类型混乱。
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
}
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
}
若使用时不知道泛型的具体类型,使用泛型通配符:
A<*> 的读写都是不安全的,若要安全还是要将星号投影为out或in关键字。
T未规定类型上下界:
原泛型类 | 星投影 |
---|---|
A | 读取:A 写入:A |
A | A |
A | A |