Kotlin

修饰符

修饰符说明
private只能在当前类中访问
protected只能在当前类和子类中访问
public任何类都可以访问(默认)
internal只能在当前模块中访问

声明变量

Kotlin使用val声明不可变变量,使用var声明可变变量

1
2
val name: String = "小明"
var age: Int = 18

类型推导机制:自动推导变量的数据类型,不需要显式指定

1
2
val name = "小明"
var age = 18

但是如果延迟赋值,就必须显式指定类型

1
2
3
4
5
val name: String
val age: Int

name = "小明"
age = 18

逻辑控制

条件语句

if

Kotlinif语法和Java基本一致

1
2
3
4
5
6
7
8
9
10
11
12
13
fun getType(any : Any?) : String {
if (any is String) {
return "String"
} else if (any is Number) {
// double float long int short byte 都是Number的子类
return "Number"
} else if (any is Char) {
return "Char"
} else if (any == null) {
return "null"
}
return "Unknown"
}

Kotlinif-else语句是一个表达式,可以返回值,返回值即每个条件代码块中的最后一行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
fun getType(any : Any?) : String {
return if (any is String) {
"String"
} else if (any is Number) {
"Number"
} else if (any is Char) {
"Char"
} else if (any == null) {
"null"
} else {
"Unknown"
}
}

进一步简化

1
2
3
4
5
fun getType(any: Any?) = if (any is String) "String"
else if (any is Number) "Number"
else if (any is Char) "Char"
else if (any == null) "null"
else "Unknown"

when

when语句可以替代ifelse ifelse,并且也可以有返回值

1
2
3
4
5
6
7
8
9
fun getType(any: Any?) : String {
return when (any) {
is String -> {"String"}
is Number -> {"Number"}
is Char -> {"Char"}
null -> {"null"}
else -> {"Unknown"}
}
}

进一步简化

1
2
3
4
5
6
7
fun getType(any: Any?) = when (any) {
is String -> "String"
is Number -> "Number"
is Char -> "Char"
null -> "null"
else -> "Unknown"
}

when语句还可以用于范围判断

1
2
3
4
5
6
7
8
fun getSope(num : Number) {
when (num) {
in 0..10 -> println("0..10")
in 11..20 -> println("11..20")
in 21..30 -> println("21..30")
else -> println("out of range")
}
}
1
2
3
4
// 上面的 0..10 可以看作数学闭区间,即 [0, 10]
val r1: IntRange = 1..10
// 左闭右开区间,即 [1, 10)
val r2: IntRange = 1 until 10

for

for语句主要用于遍历

1
2
3
4
5
6
7
8
9
10
11
// 单列集合遍历
val list = listOf(1, 2, 3, 4, 5)
for (i in list) {
println(i)
}

// 双列集合遍历
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
for ((key, value) in map) {
println("$key -> $value")
}

while

while语句可以用于循环

1
2
3
4
5
var i = 0
while (i < 5) {
println(i)
i++
}

函数

Kotlin使用fun声明函数

1
2
3
fun test() {
println("test")
}

如果未注明返回值类型,会自动推导为Unit类型,即无返回值

1
2
3
fun test(): Unit {
println("test")
}

返回值类型放在形参列表后面,使用:分隔

1
2
3
fun add(i1: Int, i2: Int): Int {
return i1 + i2
}

函数只有一个表达式时,可以省略花括号和return关键字,用一个等号代替

1
fun add(i1: Int, i2: Int): Int = i1 + i2

因为类型推导机制的存在,可以省略返回值类型

1
fun add(i1: Int, i2: Int) = i1 + i2

Kotlin中函数可以接收另一个函数作为参数

1
2
3
4
5
6
7
8
9
10
11
// 接受一个函数作为参数,参数中的函数又接收两个Int类型的参数,并返回Int类型的值
fun execute(func : (num1: Int, num2: Int) -> Int) {
val result = func(1, 2)
println("result = $result")
}

// 调用execute
val func = fun (num1: Int, num2: Int): Int {
return num1 + num2
}
execute(func)

声明类

Kotlin使用class声明类

1
2
3
4
class User {
private var name: String = ""
private var age: Int = 0
}

类中没有属性和方法时,可以省略花括号

1
class User

Kotlin中创建对象不需要使用new关键字

1
2
3
4
5
val user = User()
user.setAge(18)
user.setName("xxin")

println(user.toString())

构造函数

Kotlin中构造函数分为主构造函数和次构造函数,主构造函数只能有一个,次构造函数可以有多个

主构造函数

主构造函数写在类名后面,使用constructor关键字声明,后面括号中的形参也作为类的属性,在类中可以直接调用,使用varval关键字修饰,constructor关键字也可以省略

1
2
class User constructor(private var name: String, private var age: Int) {
}

如果类没有显式声明任何构造函数和构造函数,会自动生成一个无参的主构造函数,如下三种写法实际上完全一致

1
2
3
4
5
class User

class User()

class User constructor()

主构造函数没有函数体,如果要在主构造函数中写逻辑,可以写在init结构体中

1
2
3
4
5
6
7
8
9
class User(private var name: String, private var age: Int) {
init {
printInfo()
}

private fun printInfo() {
println("name: $name, age: $age")
}
}

主构造函数中的形参可以省略varval关键字,省略之后的形参不再作为类的属性,只能在init结构体中被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User(name: String, age: Int) {
private var name: String
private var age: Int

init {
this.name = name
this.age = age
printInfo()
}

private fun printInfo() {
println("name: $name, age: $age")
}
}

次构造函数

Kotlin中可以有多个次构造函数,而无主构造函数,使用constructor关键字声明次构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User {
private var name: String = ""
private var age: Int = 0

// 使用 this 调用两个参数的构造函数
constructor(name: String) : this(name, 18) {
println("一个参数的构造函数")
}

// 给属性赋值
constructor(name: String, age: Int) {
println("两个参数的构造函数")
this.name = name
this.age = age
}
}

当一个类既有主构造函数又有次构造函数时,所有次构造函数必须直接或间接的调用主构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 主构造函数中的形参省略了var或val关键字,只能在init结构体中使用
open class User(name: String, age: Int) {
private var name: String = ""
private var age: Int = 0

init {
// 初始化块中给属性赋值
println("初始化块")
this.name = name
this.age = age
}

// 次构造函数,直接调用主构造函数
constructor(name: String) : this(name, 18) {
println("一个参数的构造函数")
}

// 次构造函数,间接调用主构造函数
constructor(age: Int) : this("xxin") {
println("两个参数的构造函数")
}
}

继承

Kotlin中的类默认是不可继承的,如果要使一个类可以被继承,需要在类名前添加open关键字

1
2
3
open class User {

}

类的继承使用:关键字,Kotlin中所有类默认继承Any

1
2
3
class IKun : Any() {

}

下面代码中,让IKun类继承User类,被继承的类User之所以带有(),是因为Kotlinjava一样,在类继承时子类的构造函数会调用父类的构造函数,如果父类没有定义构造函数,子类继承时应该调用父类的无参构造函数,所以被继承的类带有()

1
2
3
class IKun : User() {

}

如果父类有多个构造函数,子类只需要选择调用一个构造函数即可,具体选择哪个构造函数,在继承时通过()中的参数指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 父类的主构造函数无形参
open class User() {
// 一个形参的次构造函数
constructor(name: String) : this() {
println("User的构造方法1")
}

// 两个形参的次构造函数
constructor(name: String, age: Int) : this(name) {
println("User的构造方法2")
}
}

// 子类调用父类空参的主构造函数
class IKun(name: String) : User() {

}

// 子类调用父类一个形参的次构造函数
class IKun(name: String) : User(name) {

}

如果子类中只有次构造函数,没有主构造函数,那么被继承的类User后不需要再带(),构造函数中使用super关键字调用父类的构造函数

1
2
3
4
5
class IKun : User {
constructor() : super() {
println("IKun constructor")
}
}

接口

java一样,Kotlin中的接口使用interface关键字声明

1
2
3
4
5
6
interface IKun {
fun sing()
fun dance()
fun rap()
fun basketball()
}

实现接口使用:,实现多个接口使用,分隔,重写方法时,需要使用override关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class XiaoHeiZi : IKun {
override fun sing() {
println("我会唱")
}
override fun dance() {
println("我会跳")
}
override fun rap() {
println("我会rap")
}
override fun basketball() {
println("我会篮球")
}
}

调用接口中的方法

1
2
3
4
5
val iKun: IKun = XiaoHeiZi()
iKun.sing()
iKun.dance()
iKun.rap()
iKun.basketball()

数据类

Kotlin中提供了数据类,使用data关键字声明,会自动生成equals()hashCode()toString()方法

1
data class User(private val name: String, private val age: Int)

等同于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class User(private val name: String, private val age: Int) {
override fun equals(other: Any?): Boolean {
// 三个等于号是比较地址,两个等于号是比较值
if (this === other) return true
// other不属于User类型
if (other !is User) return false

// 判断对象属性是否相等
if (age != other.age) return false
if (name != other.name) return false

return true
}

override fun hashCode(): Int {
var result = age
result = 31 * result + name.hashCode()
return result
}

override fun toString(): String {
return "User(name='$name', age=$age)"
}
}

单例类

Kotlin中提供了单例类,使用object关键字声明,会自动生成一个实例,并且是线程安全的

1
2
3
4
5
object Singleton {
fun singletonTest() {
println("singletonTest")
}
}

调用

1
Singleton.singletonTest()

等同于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton() {
companion object {
// 使用 @Volatile 保证变量的可见性
@Volatile
private var instance: Singleton? = null

fun getInstance(): Singleton {
// 第一次检查,避免不必要的同步
if (instance == null) {
synchronized(Singleton::class) {
// 第二次检查,确保只有一个实例被创建
if (instance == null) {
instance = Singleton()
}
}
}
return instance!!
}
}
}

调用

1
Singleton.getInstance().singletonTest()

Lambda表达式

Lambda表达式的结构如下

1
2
3
4
5
6
7
{ 参数名1: 参数类型, 参数名2: 参数类型 -> 函数体 }
// 例如

val lambda = { s1: String, s2: String ->
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}

函数式接口

函数式接口是指只包含一个抽象方法的接口,在Kotlin中,函数式接口可以使用fun interface关键字声明

Java中的函数式接口可以省略fun interface关键字,或者使用@FunctionalInterface注解声明

如果一个函数所需的参数是函数式接口,那么可以直接将Lambda表达式作为参数传递给函数

1
2
3
4
5
6
7
8
9
10
11
12
// 个人练习生,fun interface定义函数式接口
fun interface Trainee {
// 展示才艺
fun showTalent(s1: String, s2: String)
}

// 全民制作人,使用Trainee作为参数类型
class Producer (private val trainee: Trainee) {
fun start() {
trainee.showTalent("全民制作人大家好", "我喜欢")
}
}

创建全民制作人对象时,需要传入个人练习生的匿名类,如下

1
2
3
4
5
6
Producer(object : Trainee {
override fun showTalent(s1: String, s2: String) {
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}
}).start()

使用Lambda表达式可以简化代码

1
2
3
4
5
val lambda = { s1: String, s2: String ->
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}
Producer(lambda).start()

Lambda表达式不必声明为变量,直接写入

1
2
3
4
Producer({ s1: String, s2: String ->
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}).start()

Lambda表达式的参数类型可以省略,编译器会自动推导

1
2
3
4
Producer({ s1, s2 ->
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}).start()

如果Lambda表达式是最后一个参数,那么可以将其放在括号外面;如果Lambda表达式是唯一一个参数,那么可以省略括号

1
2
3
4
5
6
7
8
9
10
11
// 放在括号外面
Producer() { s1, s2 ->
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}.start()

// 省略括号
Producer { s1, s2 ->
println("${s1},我是练习时长两年半的个人练习生cxk")
println("${s2}唱跳rap篮球")
}.start()

此外如果Lambda表达式只有一个参数,参数可以使用it关键字代替

1
2
3
Producer {
println("${it},我是练习时长两年半的个人练习生cxk,我喜欢唱跳rap篮球")
}.start()

函数作为参数

Lambda表达式可以作为参数传递给函数,如下,在子线程中执行传入的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun execute(func: (str: String) -> String) {
// 若线程池为空,创建线程池
if (pool == null) {
pool = ThreadPoolExecutor(
5, 10, 10,
TimeUnit.SECONDS,
LinkedBlockingDeque(),
Executors.defaultThreadFactory(),
ThreadPoolExecutor.AbortPolicy()
)
}
// 执行任务
pool!!.execute {
func("cxk")
}
pool!!.shutdown()
}

正常调用时

1
2
3
execute(fun(name: String) {
println("全民制作人大家好,我是练习时长两年半的个人练习生${name},我喜欢唱跳rap篮球")
})

Lambda表达式
通过Lambda表达式简化代码

1
2
3
execute {
println("全民制作人大家好,我是练习时长两年半的个人练习生${it},我喜欢唱跳rap篮球")
}

Lambda表达式也可以作为函数的返回值,如下方法可以简化

1
2
3
4
5
6
7
8
9
10
11
12
fun getFunc(): (str: String) -> Unit {
return fun(str: String) {
println("全民制作人大家好,我是练习时长两年半的个人练习生${str},我喜欢唱跳rap篮球")
}
}

// Lambda简化
fun getFunc(): (str: String) -> Unit {
return {
println("全民制作人大家好,我是练习时长两年半的个人练习生${it},我喜欢唱跳rap篮球")
}
}

调用时

1
2
// 调用execute函数
execute{ getFunc()(it) }