fun main() { val a = 10 val text = "这是拼接的值" + a println(text) //打印出来就是与其他类型的拼接结果 // 但是我们需要注意字符串拼接的顺序,只能由字符串拼接其他类型,如果是其他类型拼接字符串,可能会出现问题:}
但是现在我们就是希望其他类型的数据拼在最前面,这里应该怎么做呢?我们可以使用字符串模版来完成:
fun main() { val a = 10 val text = "这是拼接的值$a" //这里的$为模版表达式,可以直接将后面跟着的变量或表达式以字符串形式替换到这个位置 println(text) val text = "$a 这是拼接的值" //注意这里$a之后必须空格,否则会把后面的整个字符串认为这个变量的名字 println(text)}
出现这种情况除了用空格去解决之外,我们也可以添加一个花括号:
val text = "${a}这是拼接的值" //添加花括号就可以消除歧义了val text = "${a > 0}这是拼接的值" //花括号中也可以写成表达式
由于美元符用于模版表达式了,所以说如果我们希望在字符串中仅仅表示$这个字符,那么我们需要用到转义:
val text = "\$这是美元符" //普通字符串直接使用\$表示//原始字符串要套个娃val str = """ ${'$'}这是美元符 """
流程控制
除了直接执行语句之外,我们也可以将if和else用作结果判断,比如:
fun main() { val score = 2 //这里判断socre是否大于60,是就得到Yes,否就得到No,并且可以直接赋值给变量 val res = if (score > 60) "Yes" else "No" println(res) // No}
对于多行代码块的情况,默认最后一行作为返回的结果:
fun main() { val score = 2 val res = if (score > 60) { println("不错啊期末没挂科") "Yes" //代码块默认最后一行作为返回结果 } else { println("不会有人Java期末还要挂科吧") "No" // println 不能放在最后 }}
fun main() { outer@ for (i in 1..3) { //在循环语句前,添加 标签@ 来进行标记 inner@ for (j in 1..3) { if (i == j) break@outer //break后紧跟要结束的循环标记,当i == j时终止外层循环 println("$i, $j") } }}
函数定义
fun 函数名称([函数参数…]): 返回值类型 {
//函数体
}
//这个函数用于打印一段文本fun hello(): Unit { //本质上应该是返回Unit类型,这个类型表示空,类似于Java中的void,默认情况下可以省略 println("PHP是世界上最好的语言.kt")}
fun main() { test() //调用函数时,如果对应参数有默认值,可以不填}fun test(text: String = "我是默认值"){ println(text)}
在调用函数时,我们可以手动指定传入的参数对应的是哪一个形式参数:
fun main() { test(b = 3) // 9, 这里如果只想填写第二个参数b,我们可以直接指定吧实参给到哪一个形参 test(3) // 13, 这种情况就是只填入第一个实参 } fun test(a: Int = 6, b: Int = 10) { print((a + b).toString()) }
对于一些内容比较简单的函数,比如上面仅仅是计算两个参数的和,我们可以直接省略掉花括号,像这样编写:
fun test(a: Int = 6, b: Int = 10): Int = a + b //函数的结果直接缩减为 = a + b 效果跟之前是一样的fun test(a: Int = 6, b: Int = 10) = a + b //返回类型可以自动推断,这里可以吧返回类型省掉
变量 getter and setter
修改和使用变量默认调用它的 getter and setter 方法,通过反编译可以看到。
我们也可以自定方法的逻辑
var a: Int = 5get() = field *10set(value){ println("我被赋值了") field = value}var str: String = "尊嘟假嘟" get() = field + field //使用filed代表当前这个变量(字段)的值,这里返回值拼接的结果fun main(){ println(a) // 50 println(str) // 尊嘟假嘟尊嘟假嘟 a = 90; // 我被赋值了 a的值变为90}
late init
lateinit in Kotlin is a modifier used to declare a non-nullable variable that will be initialized at a later point in the program, rather than at the time of its declaration. This is particularly useful in scenarios where the initialization of a variable depends on external factors, such as dependency injection frameworks, Android lifecycle methods, or when setting up test environments.
lateinit can only be used with non-nullablevar properties. It cannot be used with val (read-only) properties, nullable types, or primitive types (like Int, Boolean, Double) as these types have default initial values.
Deferred initialization:
The lateinit keyword informs the compiler that the variable will be initialized before it is accessed. This allows you to avoid providing an initial value at the declaration point, which would otherwise be required for non-nullable properties.
Runtime check:
If a lateinit property is accessed before it has been initialized, a UninitializedPropertyAccessException will be thrown at runtime. This ensures that you are aware of uninitialized properties and helps prevent potential NullPointerException issues.
class MyClass { lateinit var myString: String fun initializeString(value: String) { myString = value } fun printString() { println(myString) // Accessing myString after initialization }}fun main() { val obj = MyClass() obj.initializeString("Hello, lateinit!") obj.printString()}
fun main() { //这个变量表示的也是(String) -> Int这种类型的函数 var func: (String) -> Int = ::test //使用双冒号来引用一个现成的函数(包括我们后续会学习的成员函数、构造函数等)}//这个函数正好与上面的变量表示的函数类型一致fun test(str: String): Int { return 666}
匿名函数
除了引用现成的函数之外,我们也可以使用匿名函数,这是一种没有名称的函数:
fun main() { val func: (String) -> Int = fun(str: String): Int { //这里写了fun关键字后,并没有编写函数名称,这种函数就是匿名函数,因为在这里也不需要什么名字,只需要参数列表函数体 println("这是传入的内容$str") return 666 }}
fun main() { var func: (String) -> Int = { //一个Lambda表达式只需要直接在花括号中编写函数体即可 println("这是传入的参数$it") //默认情况下,如果函数只有一个参数,我们可以使用it代表传入的参数 666 //跟之前的if表达式一样,默认最后一行为返回值 } func("HelloWorld!") // result: 这是传入的参数HelloWorld!}
对于参数有多个的情况,我们也可以这样进行编写:
fun main() { val func: (String, String) -> Unit = { a, b -> //我们需要手动添加两个参数这里的形参名称,不然没法用他两 println("这是传入的参数$a, 第二个参数$b") //直接使用上面的形参即可 } val func2: (String, String) -> Unit = { _, b -> println("这是传入的第二个参数$b") //假如这里不使用第一个参数,也可以使用_下划线来表示不使用 } func("Hello", "World") // 这是传入的参数Hello,第二个参数World}
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}
在上述代码中,参数 combine 具有函数类型(R, T) -> R,因此 fold 接受一个函数作为参数, 该函数接受类型分别为 R 与 T 的两个参数并返回一个 R 类型的值。 在 for 循环内部调用该函数,然后将其返回值赋值给 accumulator。
fun main() { val items = listOf(1, 2, 3, 4, 5) // Lambdas 表达式是花括号括起来的代码块。 items.fold(0) { // 如果一个 lambda 表达式有参数,前面是参数,后跟“->” acc: Int, i: Int -> // variable 'i' is each element in the list print("acc = $acc, i = $i, ") val result = acc + i println("result = $result") // lambda 表达式中的最后一个表达式是返回值: result } /* acc = 0, i = 1, result = 1 acc = 1, i = 2, result = 3 acc = 3, i = 3, result = 6 acc = 6, i = 4, result = 10 acc = 10, i = 5, result = 15 */}
for 循环遍历集合 list 的语法
for(element: Int in items) { print("$element")}print("\n")// 12345
Accumulates value starting with initial value and applying operation from left to right to current accumulator value and each element.
Returns the specified initial value if the collection is empty.
Params: operation - function that takes current accumulator value and an element, and calculates the next accumulator value.
// 在新版 kt 标准库中简化了 fold 方法public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R { var accumulator = initial for (element in this) accumulator = operation(accumulator, element) return accumulator }
fun main() { val items = listOf(1, 2, 3, 4, 5) // lambda 表达式的参数类型是可选的,如果能够推断出来的话: val joinedToString = items.fold("Elements:") { acc, i -> // 不加类型参数 acc + " " + i } println(joinedToString) // Elements: 1 2 3 4 5 /* accumulator = Elements: Elements + " " + 1 Elements + " " + 2 Elements + " " + 3 Elements + " " + 4 Elements + " " + 5 return accumulator */ // now accumulator is Elements: 1 2 3 4 5}
it:单个参数的隐式名称
一个 lambda 表达式只有一个参数很常见。
If the compiler can parse the signature without any parameters, the parameter does not need to be declared and -> can be omitted. 该参数会隐式声明为 it:
val ints = listOf(-3, -2, -1, 0, 1, 2, 3)// 传统写法:显式声明参数val positives1 = ints.filter { n: Int -> n > 0 }println("positives1 = $positives1")// 简写写法:只有一个参数时,可以用 `it`val positives2 = ints.filter { it > 0 }println("positives2 = $positives2")
class Person constructor(firstName: String) { /*……*/ }
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(firstName: String) { /*……*/ }
The primary constructor initializes a class instance and its properties in the class header. The class header can’t contain any runnable code. If you want to run some code during object creation, use initializer blocks inside the class body. Initializer blocks are declared with the init keyword followed by curly braces. Write any code that you want to run within the curly braces.
在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起:
//sampleStartclass InitOrderDemo(name: String) { val firstProperty = "First property: $name".also(::println) //注意,多个初始化操作时,从上往下按顺序执行 init { println("First initializer block that prints $name") } val secondProperty = "Second property: ${name.length}".also(::println) init { println("Second initializer block that prints ${name.length}") }}//sampleEndfun main() { InitOrderDemo("hello")}
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5
Kotlin没有写任何构造函数,Kotlin 会默认生成一个无参的主构造函数:
class Person { val name = "Default"}
编译器生成的大致效果:
public class Person { private final String name = "Default"; public Person() { } // ← 编译器帮你生成的无参构造函数}
val tmp = x.inc() // 调用你写的 operator fun inc()x = tmp
同样的,++a的操作步骤如下:
将a.inc()的结果分配给a。
作为表达式的结果返回a的新值。
用 Student对象来举一个例子
/**@ Author: ferry@ Date 16/08/2025 2:27 am */class Student(var name:String, var age:Int) { operator fun inc(): Student { return Student(this.name, age + 1) }}fun main(){ var student1 = Student("John", 18) student1++ println(student1.name + ": " + student1.age) // John: 19}
class Student(var name:String, var age:Int) { operator fun inc(): Student { return Student(this.name, age ++) // 先返回新的Student对象,然后在之前的对象中自增1 }}fun main(){ var student1 = Student("John", 18) student1++ println(student1.name + ": " + student1.age) // John: 18}
class Student(var name:String, var age:Int) { operator fun inc(): Student { return Student(this.name, age ++) // 先在之前的对象中自增1,然后返回新的Student对象 }}fun main(){ var student1 = Student("John", 18) student1++ println(student1.name + ": " + student1.age) // John: 19}
中缀函数
实际上中缀函数在我们之前很多时候都有出现,比如位运算:
var num = 3println(num shl 1) // “左移” 运算符(Shift Left)
二进制: 00000011 = 3(真值)
左移 1 位: 00000110 = 6 (真值)
/*** Shifts this value left by the [bitCount] number of bits.** Note that only the five lowest-order bits of the [bitCount] are used as the shift distance.* The shift distance actually used is therefore always in the range `0..31`.*/@kotlin.internal.IntrinsicConstEvaluationpublic infix fun shl(bitCount: Int): Int
class Student(var name: String, var age: Int) { //这个中缀函数实现了将给定字符串与当前对象的名字拼接并返回 infix fun customAppend(string: String) : String{ return name + string }}
我们在使用时,也非常方便,真的就像在使用一个运算符一样:
fun main() { val student = Student("小明", 18) println(student customAppend "我爱你") // 等价于 println(student.customAppend("我爱你")) // 小明我爱你}
Warning
中缀函数调用的优先级低于算术运算符、类型转换和rangeTo运算符,例如以下表达式就是等效的:
1 shl 2 + 3相当于1 shl (2 + 3)
0 until n * 2相当于0 until (n * 2)
xs union ys as Set<*>相当于xs union (ys as Set<*>)(类型转换会在下一章多态进行介绍)
fun main() { val str: String? = null //Elvis运算符 ?: 左侧为空值检测目标,右侧为检测到null时返回的结果 val len: Int = str?.length ?: 0 // in this case: len = 0, because the String 'str' is null}
Destructuring declarations
解构声明
Sometimes it is convenient to destructure an object into a number of variables, for example:
val (name, age) = person
This syntax is called a destructuring declaration. A destructuring declaration creates multiple variables at once. You have declared two new variables: name and age, and can use them independently:
println(name)println(age)
A destructuring declaration is compiled down to the following code:
val name = person.component1()val age = person.component2()
class Student(var name: String, var age: Int) { operator fun component1() = name //使用component1表示解构出来的第一个参数 operator fun component2() = age //使用component2表示解构出来的第二个参数 operator fun component3... //以此类推}
val (a, b) = student //解构出来的参数按顺序就是componentN的结果了
如果我们只想要使用第二个参数,而第一个参数不需要,可以直接使用_来忽略掉:
val (_, b) = studentprintln("年龄: $b")
解构同样可以用在Lambda表达式中:
class Person(var name: String, var age: Int) { operator fun component1() = name operator fun component2() = age}fun main() { val person = Person("Smith", 18) val (name, age) = person // Destructuring declarations: 基本使用 println("$name, $age") // Smith, 18 // used in lambda val printStudentName: (Person) -> Unit = { (name, _ ) -> println("Student's name is: $name") } // invoke this method we just wrote printStudentName(person) // Student's name is: Smith // 两个参数 一个接受 Person类,一个是 Int val increaseAgeDestructured: (Person, Int) -> Unit = { ( _ ,age), increment -> println("increased person's age : ${increment}") val newAge = age + increment println(newAge) } increaseAgeDestructured(person,3) // increased person's age : 3 // 21}
包和导入
package com.test // in the package `com.test`var a = 20fun message() { println("我是测试方法")}class User
现在kt文件存放在了一个明确的包中,如果我们要在这个包以外的其他地方使用就需要导入
import com.test.User //使用import关键字进行导入,导入时需要输入 包名.类型/顶级函数名称 来完成import com.test.messageimport com.test.afun main() { val user = User() message()}
open class Student { //注意,跟类一样,函数必须添加open关键字才能被子类覆盖 open fun hello() = println("我会打招呼")}class ArtStudent : Student() { fun draw() = println("我会画画") //在子类中编写一个同名函数,并添加override关键字,我们就可以在子类中进行覆盖了,然后编写自己的实现 override fun hello() = println("おはよー")}
变量也是可以进行覆盖的:
open class Student { open val test: String = "测试" fun hello() = println("我会打招呼: $test") //这里拿到的test就会变成被覆盖掉的}class ArtStudent : Student() { //对父类的变量进行覆盖,类型必须一样 override val test: String = "干嘛" fun draw() = println("我会画画")}
有些时候为了方便,比如在父类中的属性,我们可以直接在子类的主构造函数中直接覆盖:
open class Student { open val name: String = "大明" fun hello() = println("我会打招呼,我叫: $name")}//在主构造函数中覆盖,也是可以的,这样会将构造时传入的值进行覆盖class ArtStudent(override val name: String) : Student() { fun draw() = println("我会画画")}fun main() { val student = ArtStudent("小红") student.hello() // 我会打招呼,我叫:小红}
不过,由于存在我们之前讲解的的初始化顺序,下面的这种情况需要特别注意:
open class Student { open val name: String = "小明" init { println("我的名字是: ${name.length}") } //这里拿到的name实际上是还未初始化的子类name}class ArtStudent : Student() { override val name = "大明"}fun main() { ArtStudent() // NullPointerException 初始化ArtStudent()先调用Student()发现为null // 子类的属性要等父类构造执行完才初始化}
创建 ArtStudent() 时,执行顺序是这样的:
先执行 父类构造函数(包括 init {} 块里的代码)。
然后才初始化子类的属性。
多态
跟java一样
open class Student { open fun hello() = println("大家好")}class ArtStudent : Student() { override fun hello() = println("我姓🐴我叫🐴牛逼")}fun main() { val student: ArtStudent = ArtStudent() // 一般情况 val student: Student = ArtStudent() // 做其父类来进行使用}
顶层Any类
/** * Kotlin类继承结构中的根类. 所有Kotlin中的类都会直接或间接将Any作为父类 */public open class Any { /** * 判断某个对象是否"等于"当前对象,这里同样是对运算符"=="的重载,而具体判断两个对象相等的操作需要由子类来定义 * 在一些特定情况下,子类在重写此函数时应该保证以下要求: * * 可反身: 对于任意非空值 `x`, 表达式 `x.equals(x)` 应该返回true * * 可交换: 对于任意非空值 `x` 和 `y`, `x.equals(y)` 当且仅当 `y.equals(x)` 返回true时返回true * * 可传递: 对于任意非空值 `x`, `y`, 和 `z`, 如果 `x.equals(y)` 和 `y.equals(z)` 都返回true, 那么 `x.equals(z)` 也应该返回真 * * 一致性: 对于任意非空值 `x` 和 `y`, 在多次调用 `x.equals(y)` 函数时,只要不修改在对象的“equals”比较中使用的信息,那么应当始终返回同样的结果 * * 永不等于空: 对于任意非空值 `x`, `x.equals(null)` 应该始终返回false */ public open operator fun equals(other: Any?): Boolean /** * 返回当前对象的哈希值,它具有以下约束: * * * 对同一对象多次调用该函数时,只要不修改对象上的equals比较中使用的信息,那么此函数就必须始终返回相同的整数 * * 如果两个对象通过`equals`函数判断为true,那么这两个对象的哈希值也应该相同 */ public open fun hashCode(): Int /** * 将此对象转换为一个字符串,具体转换为什么样子的字符串由子类自己决定 */ public open fun toString(): String}
//使用abstract表示这个是一个抽象类abstract class Student { abstract val type: String //抽象类中可以存在抽象成员属性 abstract fun hello() //抽象类中可以存在抽象函数 //注意抽象的属性不能为private,不然子类就没法重写了}
当一个子类继承自抽象类时,必须要重写抽象类中定义的抽象属性和抽象函数:
class ArtStudent: Student() { override val type: String = "美术生" override fun hello() = println("原神,启动!")}
当然,抽象类不仅可以具有抽象的属性,同时也具有普通类的性质,同样可以定义非抽象的属性或函数:
abstract class Student { abstract val type: String abstract fun hello() fun test() = println("不会有人玩到大三了才开始学Java吧") //定义非抽像属性或函数,在子类中不强制要求重写}
接口
interface A { val x: String //接口中所有属性默认都是abstract的(可省略关键字) fun sleep() //接口中所有函数默认都是abstract的(可省略关键字)}interface B { fun game()}class Student: A, B { //接口的实现与类的继承一样,直接写到后面,多个接口用逗号隔开 override val x: String = "测试" //跟抽象类一样,接口中的内容是必须要实现的 override fun sleep() = println("管他什么早八不早八的,睡舒服再说") override fun game() = println("读大学就该玩游戏玩到爽")}
class Student // 这样写感觉没什么用var Student.gender: String get() = "not specified" set(value) {}fun main() { val student = Student() println(student.gender) // not specified}
我们也可以在一个类中定义其他类的扩展,并且在定义时可以直接使用其他类提供的属性:
class A { fun hello() = "Hello World"}class B { fun A.test() { hello() //直接在类A的扩展函数中调用A中定义的函数 }}
像这种扩展,由于是在类中定义,因此也仅限于类内部使用,比如:
class A { fun hello() = "Hello World"}class B (private val a: A){ private fun A.test() = hello() + "!!!" fun world() = println(a.test()) //只能在类中通过A的实例使用扩展函数,只能在类B里面使用 扩展函数 test()}fun main() = B(A()).world() // Hello World!!!
在名称发生冲突时,需要特别处理:
class A { fun hello() = "Hello World"}class B (private val a: A){ private fun A.test() { hello() //直接使用优先匹配被扩展类中的方法 this.hello() //扩展函数中的this依然指的是被扩展的类对象 this@B.hello() //这里调用的才是下面的 } fun hello() = "Bye World"}
局部扩展也是可以的,我们可以在某个函数里面编写扩展,但作用域仅限于当前函数:
fun main() { fun String.test(): Unit { println("String test extension method!") } "".test() // String test extension method!}
如果我们将一个扩展函数作为参数给到一个函数类型变量,那么同样需要再具体操作之前增加类型名称才可以:
fun main() { //因为是对String类型的扩展函数,需要String.前缀 val customLength: String.() -> Int = { this.length //跟上面一样,扩展函数中的this依然指的是被扩展的类对象 } // 4 println("test".customLength()) //可以直接对符合类型的对象使用这个函数 // 5 println(customLength("Hello")) //如果是直接调用,那么必须要传入对应类型的对象作为首个参数,此时this就指向我们传入的参数}
总结一下,扩展属性更像是针对于原本类编写的外部工具函数,而绝不是对原有类的修改。
特殊类型介绍
数据类型
//在class前面添加data关键字表示为一个数据类data class User(val name: String, val age: Int)fun main() { val user1 = User("小明", 18) val user2 = User("小明", 18) println(user1) //打印出来就是格式化的字符串 User(name=小明, age=18) println(user1 == user2) //true,因为自动重写了equals函数 val (name, age) = user1 //自动添加componentN函数,因此支持解构操作 println("名称: $name, 年龄: $age")
open class A { open fun f() {}}open class B : A() { override fun f() {} // 默认就是 final,不能再 override // open override fun f() {} // 如果你想允许 C 再 override,需要写 open}class C : B() { override fun f() {} // ❌ 报错,因为 B 的 f() 已经是 final 了}
data class User(val name: String, val age: Int) { val id: Int = 888 final override fun equals(other: Any?): Boolean { // final 可以省略跟上面一样 return true } //如果已经存在toString的情况下,不会自动生成 override fun toString(): String = "我是自定义的toString"}fun main() { val user1 = User("小明", 18) println(user1) //结果: 我是自定义的toString val user2 = User("Jack", 19) println(user2 == user1) // true,自动义 equals()永远返回 true}
// 正常用法abstract class AbstractUser { //此函数必须是open的,否则无法被数据类继承 open operator fun component1() = "卢本伟牛逼"}class NormalUser : AbstractUser() // 定义子类实现 父类// 子类覆盖 component1class RealUser(val name: String) : AbstractUser() { override fun component1() = name}fun main() { val user = NormalUser() val (v) = user // 解构 println(v) // 输出 "父类默认值" val user = RealUser("Tom") val (v) = user println(v) // 输出 "Tom"}
abstract class AbstractUser { //此函数必须是open的,否则无法被数据类继承 open operator fun component1() = "卢本伟牛逼"}data class User(val name: String, val age: Int): AbstractUser() //自动覆盖掉父类的component1函数fun main() { val user = User("Tom", 20) val (n) = user println(n) // 输出 "Tom" ,因为数据类生成了自己的 component1 覆盖了父类的}
data class Person(val name: String){ var age: Int = 0 // age属性不会被处理}fun main() { val person1 = Person("John") val person2 = Person("John") person1.age = 10 person2.age = 20 println("person1 == person2: ${person1 == person2}") // person1 == person2: true println("person1 with age ${person1.age}: $person1") // person1 with age 10: Person(name=John) println("person2 with age ${person2.age}: $person2") // person2 with age 20: Person(name=John)}
data class Person(val name: String){ var age: Int = 0 // age属性不会被处理}fun main() { val person1 = Person("John") person1.age = 10 val person3 = person1.copy() println(person3 == person1) // true println(person3 === person1) // false // 在copy函数还可以在拷贝过程中手动指定属性的值: val personCopy = person1.copy(name = "I am a clone") println(personCopy) // Person(name=I am a clone)}
枚举
//在类前面添加enum表示这是一个枚举类型enum class LightState { GREEN, YELLOW, RED //直接在枚举类内部写上所有枚举的名称,一般全部用大写字母命名}fun main() { val state: LightState = LightState.RED //直接得到红灯 println(state.name) //自带name属性,也就是我们编写的枚举名称,这里是 RED}
同样的,枚举类也可以具有成员:
//同样可以定义成员变量,但是不能命名为 name,因为name拿来返回枚举名称了enum class LightState(val color: String) { GREEN("绿灯"), YELLOW("黄灯"), RED("红灯"); //枚举在定义时也必须填写参数,如果后面还要编写成员函数之类的其他内容,还需在末尾添加分号结束 fun isGreen() = this == LightState.GREEN //定义一个函数也是没问题的}fun main() { val state: LightState = LightState.RED println("信号灯的颜色是: ${state.color}") // 红灯 println("信号灯是否可以通行: ${state.isGreen()}") // false}
enum class LightState(val color: String) { GREEN("绿灯"){ override fun test() = println("我是绿灯,表示可以通过") }, YELLOW("黄灯") { override fun test() = println("我是黄灯,是让你减速,不是让你踩油门加速过去") }, RED("红灯") { override fun test() = println("我是红灯,禁止通行") }; abstract fun test() //抽象函数}fun main() { val state: LightState = LightState.RED state.test() //调用函数: 我是红灯,禁止通行}
public enum LightState { GREEN("绿灯") { @Override public void test() { System.out.println("我是绿灯,表示可以通过"); } }, YELLOW("黄灯") { @Override public void test() { System.out.println("我是黄灯,是让你减速,不是让你踩油门加速过去"); } }, RED("红灯") { @Override public void test() { System.out.println("我是红灯,禁止通行"); } }; private final String color; // 枚举构造函数必须是 private private LightState(String color) { this.color = color; } public String getColor() { return color; } // 抽象方法,要求每个枚举常量都实现 public abstract void test(); public static void main(String[] args) { LightState state = LightState.RED; state.test(); // 调用方法: 我是红灯,禁止通行 }}
如果枚举类实现了某个接口,同样可以像这样去实现:
interface Message { fun sendMessage()}enum class LightState(val color: String): Message{ GREEN("绿灯"){ override fun sendMessage() = println("我是绿灯,表示可以通过") }, YELLOW("黄灯") { override fun sendMessage() = println("我是黄灯,是让你减速,不是让你踩油门加速过去") }, RED("红灯") { override fun sendMessage() = println("我是红灯,禁止通行") };}fun main() { val state = LightState.GREEN state.sendMessage() // 我是绿灯,表示可以通过}
interface Message { fun sendMessage()}enum class Season(private val type: String): Message{ SPRING("spring"),SUMMER("summer"),WINTER("winter"), AUTUMN("autumn"); override fun sendMessage() = println(type) }fun main() { val season = Season.SPRING season.sendMessage() // spring}
枚举类提供的函数
interface Message { fun sendMessage() }enum class Season(private val type: String): Message{ SPRING("spring"),SUMMER("summer"),WINTER("winter"), AUTUMN("autumn"); override fun sendMessage() = println(type) }fun main() { // 通过 字符串获取枚举类对象 val winter: Season = Season.valueOf("WINTER") winter.sendMessage() // winter // 下面这个方法跟 valueOf()差不多 enumValueOf<Season>("AUTUMN").sendMessage() // autumn}
匿名类和伴生对象
val obj: Any = object{ //使用object关键字声明一个匿名类并创建其对象,可以直接使用变量接收得到的对象 val name: String = "I am an anonymous class" // 属性 override fun toString(): String { // 匿名类默认继承于Any,可以直接重写其toString return "custom toString function!" }}fun main() { println(obj) // custom toString function!}
interface Person { fun chat()}fun main() { val obj: Person = object : Person { //直接实现Person接口 override fun chat() = println("牛逼啊") } // 牛逼啊 obj.chat() //当做Person的实现类使用}
interface Personopen class Human(val name: String)fun main() { val obj: Human = object : Human("小明"), Person { //继承类时,同样需要调用其构造函数 override fun toString() = "我叫$name" //因为是子类,直接使用父类的属性也是没问题的 } println(obj)}
The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code.
A class Derived can implement an interface Base by delegating all of its public members to a specified object:
interface Base { fun print()}class BaseImpl(val num: Int) : Base { override fun print() = println(num)}class Derived(val base: Base): Base { //将一个Base的实现类作为属性保存到类中,同样实现Base接口 override fun print() = base.print() //真正去实现这个接口的,实际上并不是当前类,而是被拉进来的那个替身}fun main(){ Derived(BaseImpl(3)).print() // 3}
interface Base { fun print()}class BaseImpl(val x: Int) : Base { override fun print() = println(x)}class Derived(val b: Base): Base by b //使用by关键字将所有接口待实现操作委托给指定成员fun main() { val derived = Derived(BaseImpl(1)) derived.print() // number is: 1}
除了类可以委托给其他对象之外,类的成员属性也可以委托给其他对象:
import kotlin.reflect.KProperty// 定义包含属性委托的类class Example { var p: String by Delegate()}// 委托的类class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, 这里委托了 ${property.name} 属性" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef 的 ${property.name} 属性赋值为 $value") }}fun main() { val e = Example() println(e.p) // 访问该属性,调用 getValue() 函数 // com.ferry.delegation.Example@6433a2, 这里委托了 p 属性 e.p = "Runoob" // 调用 setValue() 函数 // com.ferry.delegation.Example@6433a2 的 p 属性赋值为 Runoob println(e.p) /* println(e.p) 这行代码 又一次调用 getValue() 函数 “e.p” */}}
泛型
class Score<T>(var name: String)// 这里的T就是一个待定的类型,同样是这个类具有的,我们称为泛型参数// 可以看到,它相比普通的类型,仅仅多了一个`<T>`表示类型参数,那么如何使用呢?fun main() { //在创建对象时,再来明确使用的是什么类型,同样使用尖括号填写 val score = Score<Int>("数据结构与算法")}
既然可以做到使用时明确,那现在我们应该怎么去设计这个类呢?
class Score<T>(var name: String, var id: String, var value: T)//我们在定义类型参数后,T就是一个待定类型,我们可以直接将value属性的类型定义为Tfun main() { val score = Score<String>("数据结构与算法基础", "EP074512", "优秀") //在使用时,使用<String>来明确Score的值类型,此时value的类型也会变成String val value: String = score.value //得到的直接就是String类型的结果}
class Test<T : Any?>(val data: T) // 默认 T 的类型为 null,或 Any
在使用out和in之后,类型的使用就可以更加灵活,但是这样会存在一定的安全隐患,比如下面的代码:
// 了解class Test<T>(val data: T)open class Aclass B: A()class C: A()fun main() { val test1: Test<B> = Test(B()) //这里存放的都是B类型的数据 // 没问题 val test2: Test<out A> = test1 //此时test2与test1是同一个对象,但是test2是out A // 本质上 对象test1的data类型是 B,在上面写好了 //报错: test2.data = C() //由于C是A的子类,按照正常情况来说可以直接用(但实际上这句会报错) val data: B = test1.data //这下搞笑了,拿到的类型应该是C,结果接收的类型是B}
为了解决这种情况,Kotlin对于out或in的类型进行了限制,比如设置了out的情况下:
Setter for ‘data’ is removed by type projection
属性的setter操作被限制,无法通过编译,因为这可能会导致不安全的操作发生,而in也是同理的:
class Test<T>(val data: T)open class Aclass B: A()class C: A()fun main() { val test1: Test<A> = Test(B()) //这里存的是B类型的对象 val test2: Test<in C> = test1 //直接使用in C接收得到 // val data: C = test2.data //此时得到的结果应该也可以是C才对,那肯定是错的 // 得进行强转 val data: C = test2.data as C}
class Test<T>(val data: T){ fun test(t: T){}}open class Aclass B: A()class C: A()fun main() { val test1: Test<B> = Test(B()) val test2: Test<out A> = test1 // test2.test(C()) //报错,因为这里存在消费行为}
// Set up a window size callback to adjust the viewportglfwSetWindowSizeCallback(window, new GLFWWindowSizeCallbackI() { @Override public void invoke(long window, int width, int height) { // Make sure the viewport matches the new window dimensions glViewport(0, 0, width, height); }});
fun repeatAction(times: Int, action: () -> Unit) { repeat(times) { action() } }fun main() { repeatAction(5){ println("I am the actual action") } // 'I am the actual action' was executed 5x}
// repeat// 相当于 for 循环的写法for (i in 1..times) { action() }
@FunctionalInterfacepublic interface Runnable { public abstract void run();}
fun main() { //使用Java中的Runnable接口(省略后) val runnable: Runnable = Runnable { println("Hello World") } // java 方式 val runnable2: Runnable = Runnable({ println("Hello World") }) Thread(runnable).start()}
作用域函数
// 传统方式data class Person(var name: String, var age: Int, var city: String) { fun moveTo(newCity: String) { city = newCity } fun incrementAge() { age++ }}fun main() {//sampleStart val alice = Person("Alice", 20, "Amsterdam") println(alice) alice.moveTo("London") alice.incrementAge() println(alice)//sampleEnd}
返回 lambda 表达式结果
let
data class Person(var name: String, var age: Int, var city: String) { fun moveTo(newCity: String) { city = newCity } fun incrementAge(): Unit{ age++ }}fun main() { Person("Alice", 23, "Amsterdam").let { println(it) // Person(name=Alice, age=23, city=Amsterdam) it.incrementAge() it.moveTo("London") println(it) // Person(name=Alice, age=24, city=London) } // 使你的代码更加简洁易读}
val x = "Hello"val len = x.let { str -> // 一个 lambda str.length // str 就是 "Hello"}println(len) // 5
run
fun main() {//sampleStart val numbers = mutableListOf("one", "two", "three") val countEndsWithE = numbers.run { add("four") add("five") // count,返回满足条件的元素个数:(需要传入lambda) count { it.endsWith("e") } // lambda的返回值为 int } println("There are $countEndsWithE elements that end with e.") // There are 3 elements that end with e. // one // three // five//sampleEnd}
//对当前对象进行操作,并返回当前对象本身public inline fun <T> T.also(block: (T) -> Unit): T { ... block(this) //调用我们传入的函数 return this //返回当前T类型对象本身}
fun writeToLog(message: String) { println("INFO: $message") // INFO: getRandomInt() generated value 67}fun main() { //sampleStart fun getRandomInt(): Int { return Random.nextInt(100).also { writeToLog("getRandomInt() generated value $it") } } val i = getRandomInt() println(i) // 67//sampleEnd}
fun writeToLogIntTest(value: Int): Unit { println("INFO: $value") // INFO: 3}fun main() { val number = 3 number.also(::writeToLogIntTest)}
apply
class Student(var name: String, var age: Int) { fun hello() = println("Hello, I am ${this.name}, I am ${this.age} years old")}// 接受: 可空类型 Student, 返回: 可空类型 Studentfun createStudent(student: Student?): Student? { student?.name = "Alice" // 不为空的情况 赋值 student?.age = 18 student?.hello() return student}fun main() { val newStudent = createStudent(Student("", 0)) // Hello, I am Alice, I am 18 years old println(newStudent) // com.ferry.generic.Student@4d405ef7}
// 优化class Student(var name: String, var age: Int) { fun hello() = println("Hello, I am ${this.name}, I am ${this.age} years old") }fun createStudentApply(student: Student?): Student?{ student?.apply { this.name = "John" this.age = 17 this.hello() } return student }// 接受: 可空类型 Student, 返回: 可空类型 Studentfun fun createStudent(student: Student?): Student? { student?.name = "Alice" // 不为空的情况 赋值 student?.age = 18 student?.hello() return student } fun main() { val newStudent = createStudent(Student("", 10)) println(newStudent) createStudentApply(newStudent) }
data class Person(var name: String, var age: Int = 0, var city: String = "")fun main() {//sampleStart val adam = Person("Adam").apply { age = 20 // 同 this.age = 20 city = "London" } println(adam)//sampleEnd}
public inline fun <T> T.apply(block: T.() -> Unit): T { // ... block() // 调用我们传入的函数 return this // 返回当前T类型对象本身}// 泛型 T,使用时代表任意类型// T.apply()对任意类型的扩展函数// 参数:接受一个任意类型的扩展函数// 返回值:调用者本身
apply 及 also 的返回值是上下文对象本身。因此,它们可以作为辅助步骤包含在调用链中:可以继续在同一个对象上一个接一个地进行链式函数调用。
data class User(var name: String = "", var age: Int = 0)fun main() { val user = User().apply { name = "Alice" // 直接操作 User 对象 age = 25 } println(user) // 输出:User(name=Alice, age=25)}
difference between also and apply
data class User(var name: String = "", var age: Int = 0)fun main() { val wonder = User("default", 0) println(wonder) // User(name=default, age=0) val appliedWonder = wonder.apply { name = "Alice" // 直接操作 User 对象 age = 25 } println(appliedWonder) // User(name=Alice, age=25)}
class User(var name: String = "", var age: Int = 0)fun main() { val wonder = User("default", 0) println(wonder) // com.ferry.scopefun.User@72ea2f77 val appliedWonder = wonder.apply { name = "Alice" // 直接操作 User 对象 age = 25 } println(appliedWonder) // com.ferry.scopefun.User@72ea2f77}
val map: MutableMap<Int, Student> = mutableMapOf( 10001 to Student("小明", 18), 10002 to Student("小红", 17), 10003 to Student("小刚", 19))//使用mutableMapOf可以放入多个键值对,并生成一个Map对象
val student: Student? = map[10001] //使用[]运算符通过Key查找Value
遍历一个Map
// 第一种方法fun main() { val map: MutableMap<Int,Person> = mutableMapOf( 1 to Person("Jack",10), 2 to Person("Smith",14), 3 to Person("John",16) ) map.forEach { (k, v) -> println("键: $k, 值 $v") }//键: 1, 值 com.ferry.collection.Person@66a29884//键: 2, 值 com.ferry.collection.Person@4769b07b//键: 3, 值 com.ferry.collection.Person@cc34f4d}class Person(val name: String, val age: Int)
Grouping
The Kotlin standard library provides extension functions for grouping collection elements. The basic function groupBy() takes a lambda function and returns a Map. In this map, each key is the lambda result, and the corresponding value is the List of elements on which this result is returned. This function can be used, for example, to group a list of Strings by their first letter.
You can also call groupBy() with a second lambda argument – a value transformation function. In the result map of groupBy() with two lambdas, the keys produced by keySelector function are mapped to the results of the value transformation function instead of the original elements.
This example illustrates using the groupBy() function to group the strings by their first letter, iterating through the groups on the resulting Map with the for operator, and then transforming the values to uppercase using the valueTransform function:
val numbers = listOf("one", "two", "three", "four", "five")// Groups the strings by their first letter using groupBy()val groupedByFirstLetter = numbers.groupBy { it.first().uppercase() }println(groupedByFirstLetter)// {O=[one], T=[two, three], F=[four, five]}// Iterates through each group and prints the key and its associated valuesfor ((key, value) in groupedByFirstLetter) { println("Key: $key, Values: $value")}// Key: O, Values: [one]// Key: T, Values: [two, three]// Key: F, Values: [four, five]// Groups the strings by their first letter and transforms the values to uppercaseval groupedAndTransformed = numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.uppercase() })println(groupedAndTransformed)// {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}
Reflection
在这里提供一个测试用例,后续反射的使用讲解会根据用例操作:
fun main() { println(Person().javaClass) // Class println(Person()::class.java) // Class println(Person::class.java) // Class println(Person().javaClass.kotlin) // KClass println(Person::class) // KClass}class Person { var instanceField: String = "instanceFieldValue" constructor() constructor(instanceField: String) { this.instanceField = instanceField } fun invokeMethod() { println("invokeMethod") } fun invokeMethodWithParam(param: String) { println("invokeMethodWithParam, param = $param") } companion object { var companionField: String = "companionFieldValue" fun invokeCompanionMethod() { println("invokeCompanionMethod") } fun invokeCompanionMethodWithParam(param: String) { println("invokeCompanionMethodWithParam, param = $param") } }}