Kotlin语法基础
Kotlin 概述
下面是一些 Kotlin 的学习资源:
-
Kotlin官方文档中文翻译版:https://book.kotlincn.net/
-
Jetbrains关于Kotlin的在线课程:https://hyperskill.org/courses?category=4&utm_source=jbkotlin_hs&utm_medium=referral&utm_campaign=kotlinlang-docs&utm_content=button_1&utm_term=22.03.23
基础语法
kotlin 1.8.20
包定义与导入
包的声明应处于源文件顶部,目录与包的结构无需匹配:源代码可以在文件系统的任意位置。真这么做的话,Idea里面会有提示让你修改成文件目录对应的包名:Package directive does not match the file location 。说明目录结构与包名匹配是推荐的方式,代码组织更清晰
import 关键字和 Java 里差不多。Kotlin 里支持如果出现名字冲突,可以使用 as 关键字在本地重命名冲突项来消歧义:
import org.example.Message // Message 可访问
import org.test.Message as TestMessage // TestMessage 代表“org.test.Message”
关键字 import 并不仅限于导入类;也可用它来导入其他声明:
访问修饰符
在 Kotlin 中有四个可见性修饰符: private 、 protected 、 internal 和 public 。 默认可见性是 public
变量命名冲突
如果变量命名和保留字冲突,可使用`(键盘上数字1左边的字符,需在英文模式下输入)进行转义例如
val `object` = Object()
fun toString(`object`: Object): String
面向对象
类
Kotlin 中使用关键字 class 声明类。类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的; 如果一个类没有类体,可以省略花括号。
class Empty
构造函数
在 Kotlin 中的一个类有一个主构造函数并可能有一个或多个次构造函数。主构造函数在类头中声明,它跟在类名与可选的类型参数后。
class Person constructor(firstName: String) { /*……*/ }
// 如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(firstName: String) { /*……*/ }
// 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:
class Customer public @Inject constructor(name: String) { /*……*/ }
// 构造函数参数支持默认值
class Person(firstName: String = "Foo") { /*……*/ }
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类有一个公有构造函数,那么声明一个带有非默认可见性的空的主构造函数:单例就要这么写构造函数
class DontCreateMe private constructor() { /*……*/ }
在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值。这使得 Kotlin 更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。
属性
声明类属性时,可以使用尾部逗号:
class Person(
val firstName: String,
val lastName: String,
var age: Int, // 尾部逗号
) { /*……*/ }
创建类的实例
创建对象没有 Java 中的 new 关键字,直接调用类名对应的构造函数即可
val invoice = Invoice()
val customer = Customer("Joe Smith")
伴生对象
如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。
更具体地讲,如果在你的类内声明了一个伴生对象, 你就可以访问其成员,只是以类名作为限定符。
class User(val name: String) {
// 定义伴生对象
companion object Factory {
fun create(name: String): User = User(name)
}
}
fun main(){
// 直接通过类名调用伴生对象的方法,相当于 static 方法调用了
val userInstance = User.create("John Doe")
println(userInstance.name) // John Doe
}
初始化代码块
初始化代码块可以有多个,按顺序执行
class A {
init {
println("初始化代码块")
}
}
open关键字
open 关键字和 java 中的 final 用在类上是相反的,final 是禁止有子类,而 open 是允许有子类。默认情况下 Kotlin 中所有类都是 final 的
open class Base {
// 可以被重写
open fun v() {}
// 不可被重写
fun nv() {}
}
internal 关键字 - 内部类
internal 修饰类的方法,表示这个类方法只适合当前module使用,如果其他module使用的话,会找不到这个internal方法或者报错。
数据类 data class
https://kotlinlang.org/docs/data-classes.html
基本等效于DTO、POJO等类
data class Customer(val name: String, val email: String)
自动具有下面的方法
- getters (and setters in case of
vars) for all properties equals()hashCode()toString()copy()component1(),component2(),......,componentN()
copy 方法
函数
Kotlin 函数定义使用 fun 关键字
函数定义参数列表支持尾部逗号
fun powerOf(
number: Int,
exponent: Int, // trailing comma
) { /*...*/ }
函数参数默认值
Kotlin 支持函数参数默认值,这个特性也叫做可选参数
fun read(
b: ByteArray,
off: Int = 0,
len: Int = b.size,
) { /*...*/ }
有下面几点注意事项
- 重载父类的函数时,子类函数会使用父类的函数的默认值,所以在子类函数中重新声明默认值是不允许的
open class A {
open fun foo(i: Int = 10) { /*...*/ }
}
class B : A() {
override fun foo(i: Int) { /*...*/ } // No default value is allowed.
}
class C : A() {
// error: An overriding function is not allowed to specify default values for its parameters
override fun foo(i: Int = 20) { /*...*/ }
}
- 如果一个具有默认值的参数位于一个没有默认值的变量之前,则只能通过调用具有命名参数的函数来使用默认值
fun foo(
bar: Int = 0,
baz: Int,
) { /*...*/ }
foo(baz = 1) // The default value bar = 0 is used
- 如果所有具有默认值的参数之后的最后一个参数是函数类型,则可以将相应的lambda参数作为命名参数传递,也可以在括号外传递
fun foo(
bar: Int = 0,
baz: Int = 1,
qux: () -> Unit,
) { /*...*/ }
foo(1) { println("hello") } // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") } // Uses both default values bar = 0 and baz = 1
命名参数
调用函数时可以命名一个或多个函数的参数。当函数有很多参数并且很难将值与参数相关联时,这会很有用,特别是当它是布尔值或空值时。
在函数调用中使用命名参数时,你可以自由地更改它们的列出顺序。如果想使用它们的默认值,你可以完全省略这些参数。
fun reformat(
str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ',
) { /*...*/ }
调用函数时,无需为所有参数指定名称
reformat(
"String!",
false,
upperCaseFirstLetter = false,
divideByCamelHumps = true,
'_'
)
void 函数
如果函数没有返回有用的值,则其返回类型为Unit。Unit是一种只有一个值的类型-Unit。此值不必显式返回:
fun printHello(name: String?): Unit {
if (name != null)
println("Hello $name")
else
println("Hi there!")
// `return Unit` or `return` is optional
}
Unit 可以不用写,例如:
// `return Unit` or `return` is optional
fun printHello(name: String?) {}
表达式函数
当函数体由单个表达式组成时,可以省略花括号,并在=符号后指定函数体
fun double(x: Int): Int = x * 2
// 返回类型可省略,编译器可以类型推断出来
fun double(x: Int) = x * 2
非表达式函数必须始终明确指定返回类型,除非返回Unit。有些语言类型推断很强,可以不用写返回类型。但是 Kotlin 不会推断具有块体的函数的返回类型,这个是设计上如此,因为这些函数在函数体体中可能有复杂的控制流,并且返回类型对读者来说并不明显,有时甚至对编译器来说也这样。
可变参数
使用 vararg 修饰符标记函数的最后一个参数为可变参数,就是不知道会有多少个参数
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
函数类型
https://kotlinlang.org/docs/lambdas.html#function-types
Kotlin 中具有函数类型,支持函数类型的变量定义
fun main() {
// 定义匿名函数
val func: () -> Unit = fun() {
println("匿名函数")
}
// func 变量的类型是:
func() // 调用函数
}
函数类型命名
为函数设置类型别名
typealias FuncType1 = (Int, Int) -> Unit
在定义函数类型时,支持对参数进行命名,这些名称可用于记录参数的含义,起到添加注释文档的作用
typealias FuncType2 = (a: Int, b: Int) -> Unit
创建函数类型的对象
- 通过函数字面量进行创建
// 通过lambda 创建
val func1 = { a: Int, b: Int -> a + b }
// 通过匿名函数创建
val func2 = fun(s: String): Int { return s.toIntOrNull() ?: 0 }
- 使用函数引用进行创建:支持各种各样的函数定义
class N
fun double(x: Int): Int = x * 2
fun Int.isOdd(x: Int): Boolean = x == 2
fun main() {
fun localFun() {}
// 本地函数引用
val func2 = ::localFun
// 顶层函数引用
val func3 = ::double
// 成员函数引用,不支持函数重载
val func4 = List<Int>::size
val func5 = String::intern
val func6 = String::replace // 编译错误:Overload resolution ambiguity. All these functions match.
// 构造函数引用
val func7 = ::N
// 扩展属性函数引用
val func8 = Int::isOdd;
}
- 通过类实现某个函数类型,创建类对象从而创建函数对象
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
调用函数对象
和 js 一样,Kotlin 调用函数对象的方式有以下方式:
-
通过函数名称调用
-
函数对象有 invoke 方法,和 js 函数的 call 方法一样
fun main() {
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // extension-like call
}
扩展函数
https://www.kotlinprimer.com/extension-functions/basics/functions-with-receiver/
这个特性简单来说就是:向别人定义好的类添加方法。比如为 JDK 中的 String 类添加一个方法:
fun String.lastChar(): Char = this.get(this.length - 1)
// 或者
fun String.lastChar(): Char = this[this.length - 1]
然后就可以在任意String类型上调用此方法
fun main() {
println("hello world".lastChar()) // d
}
其中 String 类型被称为 receiver type,而调用扩展函数的对象实例被称为 receiver object:

此外 this 可省略:
fun String.lastChar(): Char = get(length - 1)
局部函数
简单来说:函数内定义函数。局部函数的作用这里不再赘述
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
// 定义局部函数
fun validate(
user: User,
value: String,
fieldName: String
) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Cannot save user ${user.id}: $fieldName is empty"
)
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}
Java中需要借助局部类来实现类似的功能
public class Main {
public static void main(String[] args) {
// 局部类
class A {
// 类似局部函数功能
public void method() {
}
}
}
}
Lambda 函数
https://kotlinlang.org/docs/lambdas.html
lambda 定义和 Java 一样,使用单箭头 ->
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
比如这个函数可以具体为下面的函数
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
匿名函数
匿名函数就是没有函数名称的函数
max(strings, { a, b -> a.length < b.length })
这个函数定义可以长下面这样:
fun compare(a: String, b: String): Boolean = a.length < b.length
fun compareString(a: String, b: String): Boolean = a.length < b.length
闭包 closure
闭包是可以在函数体中访问的变量的作用域。
lambda 表达式、匿名函数、局部函数和对象表达式可以访问其闭包,其中包括在外部作用域中声明的变量。闭包中捕获的变量可以在lambda中修改:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
内联函数
https://kotlinlang.org/docs/inline-functions.html
Function Receiver
https://discuss.kotlinlang.org/t/what-is-a-receiver/18588
函数类型可以定义一个 receiver 类型,例如函数定义:A.(B) -> C,其中 A 就是 receiver 类型,这么写表示这个函数可以在 A 这个对象上调用,调用时需要传递一个 B 类型的参数,并且会返回 C 类型。
https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver
Function literals with receiver
https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver
函数式接口/SAM
https://book.kotlincn.net/text/fun-interfaces.html
摘自:https://blog.csdn.net/vitaviva/article/details/104055623
只有一个抽象成员函数的接口称为函数式接口或 单一抽象方法(SAM,Single Abstract Method)接口。函数式接口可以有多个非抽象成员函数,但只能有一个抽象成员函数。
interface ToString {
String toString(Object object);
}
class A {
public String delegateToString(ToString strategy) {
return strategy.toString(this);
}
}
需要实现一个ToString的匿名内部类对象,即使只有一个方法,也要对方法进行声明。在Java8之后,我们可以使用Lambda对调用进行简化:
public static void main(String[] args) {
A a = new A();
// java8之前
a.delegateToString(new ToString() {
@Override
public String toString(Object object) {
return object.toString();
}
});
// java8 之后可以这么写
a.delegateToString(object -> object.toString());
a.delegateToString(Object::toString);
}
这种借助Lambda对Parser的调用称为SAM转换(Single Abstract Method Conversions),在Kotlin中使用在Java中定义的delegateToString方法时,SAM转换同样有效:
fun main() {
val a = A()
a.delegateToString { it.toString() }
}
Kotlin将Java的接口翻译成了lambda,因此在kotlin的同名方法实际变成了一个高阶函数
String delegateToString(ToString strategy) => fun delegateToString(f: (obj) -> String)
当在Kotlin中定义相同的delegateToString方法时(仍使用Java定义的ToString接口)
class B {
fun delegateToString(strategy: ToString): String? {
return strategy.toString(this)
}
}
fun main() {
// Argument type mismatch: actual type is 'Function0<String>', but 'ToString' was expected.
// 注:1.8.20似乎已经没有此限制了,下面这行不会报错
B().delegateToString { it.toString() }
}
此时delegateToString在Kotlin中有明确定义,并不是一个高阶函数,所以此时只能用以下两种方式调用:
// 匿名类对象
B().delegateToString(object : ToString {
override fun toString(obj: Any?): String {
return obj.toString()
}
})
// 使用SAM转换方式
B().delegateToString(ToString {
it.toString()
})
如果将ToString接口改成Kotlin版本的,如下所示:
interface ToStringKt {
fun toString(obj: Any?): String?
}
class B {
fun delegateToString(strategy: ToStringKt): String? {
return strategy.toString(this)
}
}
fun main() {
// Error: Argument type mismatch: actual type is 'Function0<String>', but 'ToStringKt' was expected.
B().delegateToString { it.toString() }
// No Error
B().delegateToString(object : ToStringKt {
override fun toString(obj: Any?): String {
return obj.toString()
}
})
// Error: Interface 'interface ToStringKt : Any' does not have constructors.
B().delegateToString(ToStringKt {
it.toString()
})
}
此时解决办法是将ToStringKt改为函数式接口
fun interface ToStringKt {
fun toString(obj: Any?): String?
}
class B {
fun delegateToString(strategy: ToStringKt): String? {
return strategy.toString(this)
}
}
fun main() {
// Error: Argument type mismatch: actual type is 'Function0<String>', but 'ToStringKt' was expected.
B().delegateToString { it.toString() }
// Hint: Convert to lambda
B().delegateToString(object : ToStringKt { // 写法1
override fun toString(obj: Any?): String {
return obj.toString()
}
})
// 写法1可简化为写法2
B().delegateToString({ it.toString() }) // 写法2
// 写法2还可简化为
B().delegateToString { it.toString() } // 写法3
}
运算符重载
协程
参见:https://www.cnblogs.com/vonlinee/p/19223443
JVM平台注解
@JvmStatic
对于标注的元素,如果它是函数,则需要从此元素生成额外的静态方法。如果此元素是属性,则应生成额外的静态 getter / setter 方法。
class A {
companion object {
@JvmStatic
val VAL = 1
val nonStaticValue = 2
@JvmStatic
fun callStatic() {
println("callStatic")
}
fun callNonStatic() {
println("callNonStatic")
}
}
}
fun main() {
A.callStatic()
A.Companion.callNonStatic()
A.VAL
A.Companion.nonStaticValue
}
@JvmField
使用@JvmField,在 Java 中调用的时候,可以直接使用属性名,而不是对应的getter方法。
class A(@JvmField val a: String, val b: String)

浙公网安备 33010602011771号