Kotlin基本语法

修饰符

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

声明变量

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

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

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

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

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

1
2
3
4
5
6
7
// 只声明不赋值
val name: String
val age: Int

// 赋值
name = "小明"
age = 18

kotlin中默认不允许变量为null,如有必要,必须使用?修饰

1
2
val name: String? = null
var age: Int? = null

逻辑控制

if

if语法常规使用和Java基本一致

1
2
3
4
5
6
7
8
9
10
11
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"
}
return "Unknown"
}

if-else语句可以作为表达式给变量赋值,每个条件代码块中的最后一行代码作为该条件的返回值

1
2
3
4
5
6
7
8
9
10
11
val isRange: Boolean = if (value in 0..100) {
true
} else {
false
}


// 上面的 0..100 可以看作数学闭区间,即 [0, 100]
val r1: IntRange = 0..100
// 对于左闭右开区间,即 [0, 100)
val r2: IntRange = 0 until 100

进一步简化

1
val isRange: Boolean = if (value in 0..100) true else false

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"}
(any == 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"
}

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 getString(): String {
return "Hello"
}

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

1
fun getString(): String = "Hello"

使用=号时,由于类型推导机制的存在,可以省略返回值类型

1
fun getString() = "Hello"

函数也可以作为参数传递给其他函数

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val func = fun(): String {
return "Hello"
}
printString(func)
}

// 接受一个函数作为参数
fun printString(func : () -> String) {
println(func.invoke())
}

类和接口

声明类

声明类使用class

1
2
class User() {
}

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

1
class User

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

1
val user = User()

构造函数

Kotlin中构造函数分为构造函数和构造函数

主构造函数

主构造函数写在类名后面,使用constructor(...)关键字声明,constructor关键字可以省略,括号中是主构造函数的形参列表

1
class User constructor()

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

1
2
3
4
5
class User

class User()

class User constructor()

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

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

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

主构造函数中使用varval声明的形参,和常规成员变量的调用完全一致

1
class User(var name: String, var age: Int)

但如果主构造函数中的形参没有varval声明,则只能在init结构体和类中调用

1
2
3
4
5
6
class User(name: String, age: Int){
init {
// 主构造函数中的形参在结构体中被使用
println("name = $name, age = $age")
}
}

次构造函数

声明次构造函数使用constructor关键字,允许有多个构造函数,而无构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 注意此时的类名后没有括号,说明没有显式声明主构造函数
// 而类中又声明了次构造函数,所以不会自动生成主构造函数
class User {
// 由于在构造函数中一定会对两个成员变量进行赋值
// 所以在声明时可以省略赋予初始值
private var name: String
private var age: Int

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

// 给属性赋值
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User() {
// 此时在主构造函数中未对两个成员变量进行赋值
// 所以在声明时必须赋予初始值,或者使用 ? 表示可为null
private var name: String? = null
private var age: Int? = null

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

// 给属性赋值
constructor(name: String, age: Int) : this() {
this.name = name
this.age = age
}
}

继承

Kotlin中的类默认不可继承,类和其中的方法都是final的,在类名前添加open关键字使类可以被继承

1
open class User

类的继承使用:符号,Kotlin中所有类默认继承Any

1
class IKun() : Any()

父类User之所以带有(),是因为在类继承时子类的构造函数会调用父类的构造函数,如果父类没有显式定义构造函数,子类继承时调用父类的无参构造函数

1
class IKun : User()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 父类的主构造函数无形参
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)

如果子类中只有次构造函数,没有主构造函数,那么继承父类时不能使用(),而是在次构造函数中使用super关键字调用父类的构造函数

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

数据类

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

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

单例类

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

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

在其他kotlin类中调用

1
Singleton.singletonTest()

经过反编译发现,等同于

1
2
3
4
5
6
7
8
9
class Singleton {
companion object {
var INSTANCE: Singleton = Singleton()
}

fun singletonTest() {
println("singletonTest")
}
}

java类中的调用object关键字声明的类,用如下方式

1
Singleton.INSTANCE.singletonTest()

接口

声明接口使用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中没有static关键字,伴生对象类似于Java中的静态,使用companion object声明伴生对象

1
2
3
4
5
6
7
8
9
10
class User {
companion object {
const val TYPE_ADMIN = 1
const val TYPE_GUEST = 2
}
}

// 调用伴生对象中的成员变量
val adminType = User.TYPE_ADMIN
val guestType = User.TYPE_GUEST

集合

List

创建只读List使用listOf()函数。在kotlin中,List是只读的
创建可变MutableList, 使用mutableListOf()函数。在kotlin中,MutableListList的子接口,可以进行增删改操作

1
2
val mutableList: MutableList<String> = mutableListOf("a", "b")
val list: List<String> = listOf("a", "b")

获取list中的第一个或最后一个元素,分别使用.first().last()函数。不过.first().last()不是List特有的函数,而是kotlin.collections包中定义的扩展函数,适用于所有实现Collection的类

1
2
val firstItem: String = list.first()
val lastItem: String = list.last()

获取list中元素的数量,使用.count()函数

1
val itemCount: Int = list.count()

检测元素是否在list中,使用in关键字,其背后实际上是通过contains(E)实现的

1
2
val containsA: Boolean = "a" in list
val containsC: Boolean = "c" in list

addremove就不说了

Set

List,创建只读Set使用setOf()函数。在kotlin中,Set是只读的
创建可变MutableSet, 使用mutableSetOf()函数。在kotlin中,MutableSetSet的子接口,可以进行增删改操作

1
2
val mutableSet: MutableSet<String> = mutableSetOf("a", "b")
val set: Set<String> = setOf("a", "b")

Map

创建只读Map使用mapOf()函数。在kotlin中,Map是只读的
创建可变MutableMap, 使用mutableMapOf()函数。在kotlin中,MutableMapMap的子接口,可以进行增删改操作

1
2
val mutableMap: MutableMap<String, String> = mutableMapOf("a" to "1", "b" to "2")
val map: Map<String, String> = mapOf("a" to "1", "b" to "2")

向可变MutableMap中添加或修改键值对,可以使用如下类似给数组赋值的语法,其背后实际上是通过put(K, V)方法实现的

1
2
mutableMap["c"] = "3"   // 添加键值对
mutableMap["a"] = "one" // 修改键值对

从可变map中移除元素,使用.remove()函数

1
mutableMap.remove("b")

获取map中元素的数量,使用.count()函数

1
val itemCount: Int = map.count()

检测map中是否已包含特定键,使用.containsKey()函数

1
2
val containsKeyA: Boolean = map.containsKey("a")
val containsKeyC: Boolean = map.containsKey("c")

检测键或值是否在map中,使用in操作符

1
2
val containsKeyA: Boolean = "a" in map.keys
val containsValue1: Boolean = "1" in map.values

获取map的键或值的集合,分别使用keysvalues属性

1
2
val keys: Set<String> = map.keys
val values: Collection<String> = map.values

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中的函数式接口使用@FunctionalInterface注解声明

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 函数式接口,用于定义内容传输的回调方法
*/
fun interface OnContentTransmitter {
/**
* 传输指定的内容
*/
fun transmitContent(content: String)
}

/**
* 执行器,用于执行传输操作
*/
class TransmitExecutor(private val onContentTransmitter: OnContentTransmitter) {
/**
* 执行传输操作,传输内容
*/
fun executeTransmit() {
onContentTransmitter.transmitContent("hello world")
}
}

创建TransmitExecutor对象时,需要传入OnContentTransmitter的匿名类,如下

1
2
3
4
5
TransmitExecutor (object : OnContentTransmitter {
override fun transmitContent(content: String) {
println(content)
}
}).executeTransmit()

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

1
2
3
4
val lambda = { content: String ->
println(content)
}
TransmitExecutor(lambda).executeTransmit()

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

1
2
3
TransmitExecutor({ content: String ->
println(content)
}).executeTransmit()

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

1
2
3
TransmitExecutor({ content ->
println(content)
}).executeTransmit()

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

1
2
3
TransmitExecutor { content ->
println(content)
}.executeTransmit()

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

1
2
3
TransmitExecutor {
println(it)
}.executeTransmit()

同样如果一个函数需要的参数是函数式接口时,和上面的步骤一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 执行传输操作
* 调用传入的 [OnContentTransmitter] 接口实例的 [OnContentTransmitter.transmitContent] 方法
*/
fun execute(onContentTransmitter: OnContentTransmitter) {
// 调用回调接口的方法,传入要传输的内容
onContentTransmitter.transmitContent("hello world")
}

// 执行传输操作,传入一个 Lambda 表达式实现的内容传输回调
execute {
// 打印传输的内容
println(it)
}

函数作为参数

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

1
2
3
4
fun execute(func: (content: String) -> Boolean) {
val result: Boolean = func.invoke("hello world")
println("执行结果 = $result")
}

正常调用时

1
2
3
4
5
6
execute (fun (content: String): Boolean {
if (TextUtils.isEmpty(content))
return false
println(content)
return true
})

通过Lambda表达式简化代码

1
2
3
4
5
6
execute {
if (TextUtils.isEmpty(it))
return@execute false
println(it)
return@execute true
}

函数作为返回值

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 函数的返回值是
* (str: String, like: String) -> Unit
* 这么一个函数类型的返回值
*/
fun getFunc(): (name: String, like: String) -> Unit {
return fun(name: String, like: String) {
println("全民制作人大家好,我是练习时长两年半的个人练习生$name,我喜欢$like")
}
}

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

调用时

1
2
3
getFunc().invoke("cxk", "唱、跳、rap、篮球")
// 或者(不好看)
getFunc()("cxk", "唱、跳、rap、篮球")