开发环境配置
本文采用win11系统进行开发环境配置,具体如下:
IntelliJ IDEA Ultimate Edition 2024.1.4
Azul Zulu Community™ 17.0.12
Kotlin 2.0.20
安装
IntelliJ IDEA
前往官网下载安装即可
JDK
安装完IntelliJ IDEA后新建空项目,之后打开右上角齿轮设置的项目结构
选择项目设置下的项目,找到SDK,点击选择下载JDK
选择17版本,Azul Zulu Community™,位置可以自定义,然后点击下载,再点击右下角的确定
界面右下角就会显示下载的进度条
安装完成可以回到设置查看是否有爆红,如下则为正常安装
也可以直接从官网安装:
新建项目
IDEA新建项目,选择左侧的Kotlin
,选择位置,构建系统选择Maven
,JDK版本选择之前下载的azul-17
,添加示例代码,高级设置里再设置下组ID和工作ID
点击创建,之后就会自动开始下载了(可能比较慢,需要霍格沃茨的网络环境)
代码结构
package com.xlyotut // 软件包名
/**
* 主函数
*/
fun main() {
// 变量声明和初始化
val name = "Kotlin"
// 打印输出
println("Hello, " + name + "!")
// 循环语句
for (i in 1..5) {
// 循环打印输出
println("i = $i")
}
}
输出:
Hello, Kotlin!
i = 1
i = 2
i = 3
i = 4
i = 5
区分大小写,大小写敏感
一般省略结尾的
;
号,多个语句在同一行则需要;
进行分隔单行注释
//
,多行注释/**/
变量与基本类型
变量声明
变量语法:
var 变量名称 : 数据类型
不可变变量语法(赋值一次之后不能再更改):
val 不可变变量名称 : 数据类型 = 初始值
变量命名规范
使用有意义且易于理解的名字:变量名应该能够清楚地描述其代表的内容,避免使用缩写或含糊不清的名称。
使用驼峰式命名法:
对于局部变量和成员变量,使用小驼峰式命名法(lowerCamelCase),即第一个单词的首字母小写,后续单词的首字母大写。
对于常量,使用大驼峰式命名法(UpperCamelCase),即所有单词的首字母都大写。
变量名应以字母开头:变量名可以包含字母、数字和下划线(_),但不能以数字开头。
尽量使用名词:因为变量代表的是数据,通常使用名词来命名。
避免使用 Kotlin 保留字:不要使用 Kotlin 语言中的保留字(如
val
,var
,fun
等)作为变量名。
以下是一些遵循 Kotlin 命名规范的例子:
// 成员变量
var companyName: String = "Example Inc."
// 常量
const val MAX_COUNT = 100
// 局部变量
val numberOfEmployees = 50
// 私有变量,通常在后面添加一个下划线
private var _isInitialized: Boolean = false
以下是一些非法的命名方式:
// 非法的命名:使用了缩写,不易理解
var cpyNm: String = "Example Inc."
// 非法的命名:以数字开头
var 123name: String = "John"
// 非法的命名:使用了 Kotlin 保留字
var val: Int = 10
数字类型
数字可使用
_
进行分隔,如:114_5140无符号数在赋值时必须后边跟后缀
u
或U
数字运算
运算就不用多说了,都是跟其它语言差不多,有2点需要注意
1.kotlin的位运算不是用符号,而是用函数来进行
如,异或运算
var num = 1 xor 2
函数对照表如下:
2.新增特有的区间运算符..
1到5(包括1和5)则如下所示
for (i in 1..5) {
// 循环打印输出
println("i = $i")
}
1到5(包括1,不包括5)则如下所示
for (i in 1..<5) {
// 循环打印输出
println("i = $i")
}
布尔类型
var isFirst : Boolean = true
值只能是true
和false
in判断
使用in
判断一个数是否在一个区间里面
判断一个数是否在[1,5]里面
var num = 3
println(num in 1..5)
判断一个数是否在[1,5)里面
var num = 5
println(num in 1..<5)
判断一个数是否不在[1,5]里面
var num = 10
println(num !in 1..5)
判断一个数是否不在[1,5)里面
var num = 10
println(num !in 1..<5)
字符类型
字符使用单引号赋值
var ch : Char = 'a'
无符号
16位
范围
0~65535
Unicode存储
字符串使用双引号赋值
var str : String = "abcd"
三引号为多行字符串
var str : String = """
abc
ddd
ooo
"""
输出如下
abc
ddd
ooo cc
使用trimIndent
可以去除每行开头的制表符
var str : String = """
abc
ddd
ooo cc
""".trimIndent()
输出如下
abc
ddd
ooo cc
模板字符串
语法
"$变量名称"
或
"${变量名称}"
如下示例
var idx = 114514
println("索引为$idx 个") // 输出:索引为114514 个
或
var idx = 114514
println("索引为${idx}个") // 输出:索引为114514个
等于判断
kotlin
有2种比较运算符==
和===
,这俩区别在于
==
(结构相等)==
用于比较两个对象的内容是否相等。它调用了equals()
方法(或者为基本数据类型比较它们的值)。对于基本数据类型(如 Int、Long、Float 等),
==
比较的是值是否相等。对于引用数据类型(如类实例),
==
比较的是两个对象是否代表相同的值(即它们的equals()
方法返回 true)。
===
(引用相等)===
用于比较两个对象的引用是否相同,即它们是否指向内存中的同一个对象。对于基本数据类型,
===
的行为与==
相同,因为基本数据类型是按值传递的。对于引用数据类型,
===
检查的是两个引用是否指向同一个对象实例。
val a: Int = 1000
val b: Int = 1000
println(a == b) // 输出 true,因为值相等
println(a === b) // 输出 true,因为基本数据类型按值传递,这里比较的是同一个值
val str1 = "Hello"
val str2 = "Hello"
println(str1 == str2) // 输出 true,因为字符串内容相等
println(str1 === str2) // 在大多数情况下,对于字符串字面量,这也会输出 true,因为 Kotlin 有字符串字面量优化,但不是绝对的
val obj1 = StringBuilder("Hello")
val obj2 = StringBuilder("Hello")
println(obj1 == obj2) // 输出 false,因为它们的引用不同
println(obj1 === obj2) // 输出 false,因为它们不是同一个对象实例
val obj3 = obj1
println(obj1 === obj3) // 输出 true,因为 obj3 是 obj1 的引用
流程控制
其它流程控制语句都和java差不多,接下来说几点不一样的
when
类似于java的switch
语句
when (idx) {
214 -> println("214")
666 -> println("666")
else -> println("none")
}
这里的else
相当于switch
的default
还可以直接做为表达式
var reslut = when (idx) {
214 -> "214"
666 -> "666"
else -> "none"
}
做表达式就必须要有默认分支的else
了
for
与java的增强for循环类似,不过可以使用Range来表示范围
for (i in 1..5) {
println(i)
} // 1 2 3 4 5
步长
for (i in 1..5 step 2) {
println(i)
} // 1 3 5
倒序
for (i in 5 downTo 1) {
println(i)
} // 5 4 3 2 1
函数
创建和使用
语法如下
fun 函数名称([函数参数列表..]): 返回值类型 {
// 函数体
}
示例如下
fun helloWorld(msg: String, time: Int = 1) : String {
return "[${msg}]: Hello World * $time"
}
像上面只有一行的简洁函数,可以使用表达式主体
fun helloWorld(msg: String, time: Int = 1) : String = "[${msg}]: Hello World * $time"
无返回值的函数默认返回值类型为Unit
fun funcTest(num: Int) : Unit {
println(num)
}
变量属性函数
类似于C#语言,变量具有get
和set
函数,默认生成如下
var num = 32
get() = field
set(value) {
field = value
}
其中field
就是代表这个变量本身,不要在get
和set
内部直接使用变量本身如num,使用field
代替
其实就是名称为get
和set
的函数,可以使用表达式主体=
,也可以使用花括号{}
来定义
get
函数会在获取值时被调用,从而拿到该变量的值set
函数会在设置该值时被调用,从而设置变量的具体值,value
就是目标值
变量函数
函数也可以被变量存储,类似于javascript
var helloWorld: (String, Int) -> String
就相当于
fun helloWorld(msg: String, time: Int): String
可以使用typealias
给函数类型起别名
typealias Hello = (String, Int) -> String
给变量函数赋值有以下几种方法:
方法引用
fun main() { var helloWorld : Hello = ::helloWorldFunc // 方法引用 var ctx = helloWorld("OK", 114) println(ctx) } fun helloWorldFunc(msg: String, time: Int): String { return "[${msg}]: Hello World * $time" }
匿名函数
fun main() { var helloWorld: Hello = fun(msg, time) = "[${msg}]: Hello World * $time" var ctx = helloWorld("OK", 114) println(ctx) }
lambda表达式
fun main() { var helloWorld: Hello = { msg, time -> "[${msg}]: Hello World * $time" } var ctx = helloWorld("OK", 114) println(ctx) }
lambda表达式
其在只有一个传入参数的情况下,可不用为其变量命名,默认为it
var lambdaTest: (Int) -> Unit = { println(it) }
无显示return
则最后一行默认为返回值
var lambdaTest: (Int) -> Double = {
println(it)
1.113
}
使用_
可以简化不使用的变量,类似于C#
var helloWorld: (String, Int) -> String = { _, time -> "Hello World * $time" }
在lambda
中使用return
需要打标签(定义了哪个函数需要提前返回)
var lambdaTest: (Int) -> Double = test@{ // test@定义标签
println(it)
return@test 1.113 // 使用标签返回
}
还可以
myTest { str, num ->
println(str)
return@myTest num.toFloat() // 定义了myTest函数需要提前返回
}
高阶函数
可以将变量函数做为参数传入和返回值(进行一个套的娃)
各位可以不看最后的输出,猜猜看会输出什么
fun main() {
// 114为传入myTest的第一个参数
var test = myTest(114) { str, num -> // 这里是定义func: (String, Int) -> Float函数
println(str)
num.toFloat() // 函数返回值
}(514) // 这里是调用myTest返回的函数(Short) -> Double
println(test) // 输出调用myTest返回的函数(Short) -> Double返回的值Double
}
fun myTest(time: Int, func: (String, Int) -> Float): (Short) -> Double {
var floatVar = func("Call func", time) // 调用传入函数
var result: (Short) -> Double = { (floatVar + it).toDouble() } // 生成函数做为返回值
return result // 返回生成的函数
}
输出如下
Call func
628.0
内联函数
使用关键词inline
声明内联函数,类似于C++的内联函数
直接将函数照搬照抄到目标位置,并不会进行调用函数
fun main() {
myTest { println("test") }
}
inline fun myTest(func: () -> Unit) {
func()
}
使用该网站https://www.decompiler.com/在线将生成的.class
进行反编译
编译后(java)就相当于
public static final void main() {
int $i$f$myTest = false;
int var1 = false;
System.out.println("test");
}
1.默认形参不能被当做变量
inline fun myTest(func: () -> Unit) {
// var a = func // 这是不行的
func()
}
2.使用noinline
关键字可以指定非内联形参
inline fun myTest(noinline func: () -> Unit) {
var a = func
func()
}
3.高阶内联函数调用时return
不用打标签,可以直接return
,但因为是照搬照抄,可能会导致调用方提前返回
fun main() {
myTest { return } // 提取返回
println("hello kotlin") // 不会执行
}
inline fun myTest(func: () -> Unit) {
func()
}
要解决的话打个标签就可以了
提前结束myTest
函数
fun main() {
myTest { return@myTest }
println("hello kotlin")
}
inline fun myTest(func: () -> Unit) {
func()
}
提取结束main
函数
fun main() {
myTest { return@main }
println("hello kotlin")
}
inline fun myTest(func: () -> Unit) {
func()
}
内联函数最好用在有变量函数形参的函数(高阶函数)上
中缀函数
例如位运算的shl
,在俩操作数中间
println(1 shl 2)
这个shl
就是中缀函数
其本质就是一个函数,但是加上了infix
关键字,要成为中缀函数有以下要求
必须是成员函数
只能有一个参数
参数不能有默认值
定义
class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
infix fun test(score: Float): String {
return "${name}的成绩是${score}分"
}
}
调用
fun main() {
val student = Student("符玄", "仙舟·太卜司")
student.id = "St19198"
println(student test 114.5f)
}
输出
符玄的成绩是114.5分
需要注意的是
中缀函数优先级低于算数运算符、类型转换和
rangeTo
运算符中缀函数优先级高于布尔运算符、
is
和in
以及其它一些运算符
类与对象
类定义
class Student public constructor(public val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
}
public constructor
:直接在类名后面定义构造函数,有val
或者var
的才是这个类的成员变量var age: Int
:在类内部定义类的成员变量,必须进行初始化lateinit var id: String
:如果不想初始化,可以使用lateinit
,在之后进行初始化,但在使用之前必须给其赋值var major: String = inMajor
:使用形参inMajor
初始化成员变量major
上面这个类定义了一个Student
类
在实例化时必须首先传入
name
,且name
之后不可改变定义成员变量
age
,初始化为0定义成员变量
id
,在之后自行初始化定义成员变量
major
,通过实例化时传入的形参inMajor
进行初始化
public constructor
通常可以省略,如下所示
class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
}
辅助构造函数
相当于构造函数重载
class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
constructor(name: String): this(name, "未知专业") // 这里的this调用主构造函数
constructor(): this("未知姓名") // 辅助构造函数还可以调用其它辅助构造函数
}
辅助构造函数不能定义成员变量(即不能使用
val
和var
)辅助构造函数可以有函数体,能执行一些语句
执行辅助构造函数前会先调用主构造函数(辅助构造函数必须间接或直接调用主构造函数)
初始化代码块
在主构造函数执行时会自动调用该代码块,init
代码块顺序按照从上往下执行,如果age
在init
后面,则会报错变量未初始化
class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
constructor(name: String): this(name, "未知专业")
constructor(): this("未知姓名")
init {
println("${this.name}初始化")
if (age > 18) age = 18
}
}
成员函数
class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
fun doWork() {
println("${this.name}开始学习")
}
}
类实例化
fun main() {
val student = Student("符玄", "仙舟·太卜司")
println(student.name)
println(student.age)
student.id = "St19198"
println(student.id) // 因为lateinit关键字,使用该值前需要初始化
println(student.major)
}
从c语言来讲,实例化类之后,变量存储的是该类对象的指针,指向具体的类对象
所以单纯的表示该类可以不使用var
,使用val
即可,类似于c语言的指针常量
运算符重载
operator fun plus(another: Student): Student {
val result = Student(name, major)
result.id = id + another.id
result.age = age + another.age
return result // 返回一个新的Student对象
}
能重载的操作符表格
+=
、-=
、*=
、/=
、%=
这几个运算符要定义其xxxAssign
运算符函数或运算符函数,2者选其一如,
+=
定义了函数plusAssign
,则函数plus
无需定义
空类型
可以参考C#的空类型?
var str: String? = null
表示这个变量可以为null
使用
!!
明确该对象不为空
val student: Student? = null
println(student!!.name)
使用
?.
进行null
条件判断,安全调用
val student: Student? = null
println(student?.name)
如果对象为空,则安全调用返回null
,且不会报错
使用
?:
自定义为null
时的返回
val student: Student? = null
println(student?.name ?: "未知")
解构声明
类似于python,可以将一个类对象的一些属性直接解析出来
定义
class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
operator fun component1(): String {
return name
}
operator fun component2(): String {
return major
}
}
使用
fun main() {
val stu = Student("符玄", "Type-C")
var (name, major) = stu
println("${name}的专业是$major")
}
相当于
fun main() {
val stu = Student("符玄", "Type-C")
println("${stu.name}的专业是${stu.major}")
}
想使用该解构功能只需按照顺序重载运算符componentxxx
函数即可,如下
operator fun component1(): 返回值类型 {
// 函数体
}
operator fun component2(): 返回值类型 {
// 函数体
}
operator fun component3...
operator fun component4...
还可以在lambda
中使用
fun main() {
val stu = Student("符玄", "Type-C")
val func: (Student) -> Unit = { (name, major) ->
println("${name}的专业是$major")
}
func(stu)
}
权限修饰符
OOP
继承
在kotlin
中所有的类默认都是“终态”的,不能被任何类进行继承,加上open
关键字后就可以被继承了(成为父类)
open class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
constructor(name: String) : this(name, "未知专业")
constructor() : this("未知姓名")
}
使用符号:
即可进行继承,继承时需要调用父类的构造函数
open class Student(val name: String, inMajor: String) {
var age: Int = 0
lateinit var id: String
var major: String = inMajor
constructor(name: String) : this(name, "未知专业")
constructor() : this("未知姓名")
}
open class ComputerStudent(name: String) : Student(name) {
constructor() : this("计算机学生")
}
class SuperComputerStudent : ComputerStudent() {
}
需要注意的是:
单一继承,不支持多重继承,只能继承一个父类
可以继承多个接口,多个用符号
,
进行分隔子类辅助构造函数不能直接调用(
super
)父类构造函数
优先级关系:父类初始化 > 子类主构造函数 > 子类辅助构造函数
属性覆盖
子类可以使用关键词override
重写父类成员
父类
open class Student(val name: String, inMajor: String) {
open var age: Int = 0
lateinit var id: String
var major: String = inMajor
open fun code() {
println("打代码")
}
constructor(name: String) : this(name, "未知专业")
constructor() : this("未知姓名")
}
子类
class ComputerStudent(name: String) : Student(name) {
override var age: Int = 16
get() = super.age
set(value) {
field = if (value > 18) 18 else value
}
constructor() : this("计算机学生")
override fun code() {
println("计算机学生[${name}]在打代码")
super.code() // 调用父类的方法
}
}
需要注意的是:
成员也是默认“终态”的,不能被子类重写,需要添加关键字
open
可以重写的成员可以是成员变量和成员函数
对与
open
属性,在父类的初始化代码块init
中使用会默认用子类的,而子类那时还未初始化,这样就会出现空指针open class Student { open val name: String = "ABC" init { println("我的名称是:${this.name.length}") // 未初始化,会出现空指针 } } class ComputerStudent : Student() { override val name: String = "123" }
类型判断和转换
使用关键字is
进行类型判断,使用as
进行类型转换
fun main() {
val student = ComputerStudent("银狼")
studentDoWork(student)
}
fun studentDoWork(student: Student) {
if (student is ComputerStudent) {
student.code() // idea提示智能转换,将student智能转化为ComputerStudent
}
else if (student is SportStudent) {
(student as SportStudent).sport()
}
}
处理可空类型时,使用as?
可以进行安全转换,如果转换失败则返回null
val stu : Student? = SportStudent()
val su = stu as? ComputerStudent
println(su)
顶层Any类
类似于java的Object
超类
public open class Any public constructor() {
public open operator fun equals(other: kotlin.Any?): kotlin.Boolean
public open fun hashCode(): kotlin.Int
public open fun toString(): kotlin.String
}
运算符
==
就是调用函数equals
可空对象进行
==
运算时实际是这样的a == b a?.equals(b) ?: (b === null)
抽象类
使用abstract
关键字定义抽象类,不需要open
关键词即可被其它类继承
abstract class Student(val name: String, inMajor: String) {
abstract var age: Int
lateinit var id: String
var major: String = inMajor
abstract fun doWork()
constructor(name: String) : this(name, "未知专业")
constructor() : this("未知姓名")
}
class SportStudent : Student() {
override var age: Int = 0
get() = field
set(value) { field = value }
override fun doWork() {
println("唱跳rap篮球")
}
}
open class ComputerStudent(name: String) : Student(name) {
constructor() : this("计算机学生")
override var age: Int = 16
get() = field
set(value) { field = value }
override fun doWork() {
println("计算机学生打代码")
}
}
需要注意的是:
抽象类可以继承非抽象类
抽象类的抽象成员子类必须重写,除非子类也是抽象类
接口
使用interface
关键字定义接口
interface IPlayGame {
fun playGame(name: String)
var x: String // 接口不能存储某个具体的值,所以只能重写getter,不能重写setter
fun playGame2(name: String) { // 接口方法的默认方法
x = name // 具体setter还是要看具体实现类
println("玩$name")
}
}
abstract class Student(val name: String, inMajor: String) {
abstract var age: Int
lateinit var id: String
var major: String = inMajor
abstract fun doWork()
constructor(name: String) : this(name, "未知专业")
constructor() : this("未知姓名")
}
open class ComputerStudent(name: String) : Student(name), IPlayGame {
constructor() : this("计算机学生")
override var age: Int = 16
get() = field
set(value) { field = value }
override fun doWork() {
println("计算机学生打代码")
}
override fun playGame(name: String) {
println("计算机学生${this.name}在玩$name")
}
override var x: String = "" // 实现类就可以正常存储和重写
get() = field
set(value) { field = value }
}
实现的接口同时有同名的方法,使用super<接口类型>.方法名称
即可区分开来
interface A {
fun sleep() = println("A sleep")
}
interface B {
fun sleep() = println("B sleep")
}
class Sub : A, B {
override fun sleep() {
super<B>.sleep() // 调用接口B的sleep函数
}
}
需要注意的是:
实现类相当于继承了接口,所以可以使用
is
和as
关键字进行类型判断和转换接口不能存储某个具体的值,所以只能重写
getter
,不能重写setter
,实现类就可以正常存储和重写接口的方法可以有默认方法,对接口属性进行的操作还是要看具体实现类的
getter
和setter
类的扩展
类似与C#对类的扩展,一种无侵入式的对类进行功能扩展
fun main() {
val str = "克拉拉"
println(str.exTest())
}
fun String.exTest() : String {
return "Hello $this"
}
还可以扩展属性,只不过不能直接扩展,因为直接扩展不进行任何的存储,需要扩展属性的getter
和setter
函数才能看起来跟真的属性一样
var field: Int = 114514 // 让该变量替代A.myNum去存储
var A.myNum: Int
get() = field
set(value) { field }
名称冲突时:
class A {
fun hello() = "A Hello"
}
class B(private val a: A){
private fun A.test() {
hello() // 优先匹配被扩展函数,A里的hello
this.hello() // 扩展函数里的this还是A的对象,还是A里的hello
[email protected]() // 打上具体标签指定具体的同名函数,B里的hello
}
fun hello() = "B Hello"
}
扩展方法还可以做为变量函数使用
fun main() {
val func: String.(Int) -> String = { this + it.toString() }
println("abc".func(96)) // 输出:abc96
println(func("abc", 96)) // 输出:abc96
}
同名扩展函数优先使用对应类型的扩展函数
fun main() {
val obj : A = B()
obj.exTest() // 输出:A
}
open class A
class B : A()
fun A.exTest() = println("A")
fun B.exTest() = println("B")
扩展方法内部调用被扩展类的成员受到权限修饰符控制
open class A
class B : A() {
private fun foo(){}
}
fun B.exTest() {
// foo() // 非法,私有函数
}
需要注意的是:
this
表示当前操作的类对象语法就是
要扩展的类名.额外方法名称
扩展类本身就有的函数需要进行重载,如果一模一样则扩展时就会失效
同样受到权限修饰符的控制
扩展可以被子类重写
泛型
语法
对于泛型类型只能当做Any
类型来使用
fun main() {
Score<Float>(1.14f).showScore()
}
class Score<T>(var score: T) {
fun showScore() = println("分数为${score}")
}
使用函数类型的形参时,我们可以使用泛型来代表不确定的类型
// 接受Int类型形参,返回值类型为T的func函数
// 整个test函数返回类型T
fun <T> test(func: (Int) -> T): T {
// ...
}
// 无形参无返回值的func函数为T类型的扩展函数
// 整个test函数返回类型T
fun <T> test(func: T.() -> Unit): T {
// ...
}
上述的函数类型 T.() -> Unit
意思是传递给这个函数的lambda
表达式或函数字面量将会在 T
类型的实例上执行(T类型的扩展函数)
需要注意的是:
不能使用泛型来创建对象
子类在继承时,可以选择将父类的泛型参数给明确为某一类型,或是使用子类定义的泛型参数作为父类泛型参数的实参使用
内置高阶扩展函数
作用是用来优化繁琐的代码
不止如上这些,还有通过非对象调用的高阶扩展函数
apply
如下代码,给可空类型进行赋值,看起来十分的不优雅
class Person(var name: String, var age: Int) {
fun hello() = println("Hello, $name, $age")
constructor(): this("", 0)
}
fun makePeople(people:Person?) : Person? {
// 不优雅的赋值
people?.age = 500
people?.name = "纳西妲"
people?.hello()
return people
}
可使用内置的高阶函数apply
进行赋值
fun makePeople(people: Person?): Person? = people?.apply {
age = 500
this.name = "纳西妲"
hello()
}
这个apply
函数可使用this
操作调用者people?
,且代码块结束自动返回调用者people?
其实就相当于给people?
类进行了扩展,增加了个扩展函数,如下
{
age = 500
this.name = "纳西妲"
hello()
}
然后apply
函数里自动调用这个扩展函数
来看看剩下的
let
用于执行一个lambda
表达式并将得到的结果作为返回值返回
fun letMy(people: Person): Person = people.let {
it.name = "纳西妲"
it.age = 500
it
}
源码如下
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
also
用于执行一个lambda
表达式并返回对象本身,跟apply
功能一样,但采用的是it
参数形式传递给lambda
当前对象
fun letMy(people: Person): Person = people.also {
it.name = "纳西妲"
it.age = 500
}
源码如下
/**
* Calls the specified function [block] with `this` value as its argument and returns `this` value.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
run
用于执行一个lambda
表达式并将得到的结果作为返回值返回,它跟apply
一样,使用this
传递当前对象,可以看到接受的参数是一个扩展函数
val obj = Any()
var result = obj.run {
println(this)
"Hello world!"
} // result存储的结果为:Hello world!
源码如下
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
takeIf
满足某些条件才进行处理,传入一个用于判断的函数,根据结果返回对象本身或是null
val obj = Any()
var result = obj.takeIf { false } ?: "Noop"
println(result) // Noop
源码如下
/**
* Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#takeif-and-takeunless).
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
takeUnless
跟takeIf
相反,传入一个用于判断的函数,根据取反结果返回对象本身或是null
val obj = Any()
var result = obj.takeUnless { true } ?: "Noop"
println(result) // Noop
源码如下
/**
* Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#takeif-and-takeunless).
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (!predicate(this)) this else null
}
with
手动传入一个现有变量,然后通过这个变量去调用传入的lambda
var len = with("Hello Kotlin") { this.length }
println(len) // 12
源码如下
/**
* Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
逆变和协变
类似于C#的逆变和协变:https://www.cnblogs.com/VVStudy/p/11404300.html
众所周知,父类可以存储子类对象,如下
val n1: Int = 114
val n2: Number = n1
但是使用泛型如此会报错
class Test<T>(var data: T)
fun myTest() {
val o1: Test<Int> = Test(114)
val o2: Test<Number> = o1
}
如此这般,泛型类型Test<T>
存在以下几种形变:
协变:因为
Int
是Number
的子类,所以Test<Int>
也是Test<number>
的子类,可以直接转换逆变:跟协变相反,
Test<number>
可以直接转化为Test<Int>
,前者是后者的子类抗变:
Test<Int>
跟Test<Number>
没半毛钱关系,无法互相转换
诶!那么现在我们遇到的情况就是抗变,在kotlin
中,泛型默认都是抗变的
使用俩关键字可以进行转换
out
:标记一个类型参数为协变,可以实现子类转父类in
:标记一个类型参数为逆变,可以实现父类转子类
val o1: Test<Int> = Test(114)
val o2: Test<out Number> = o1
现在,Test<Number>
类型可以接收Test<Int>
类型了
还有通配符*
代表所有的,且不受限制,但只能当做Any?
类型使用
var test: Test<*> = Test(114)
test = Test("Hello")
需要注意的是:
out
只允许读出,是生产者,不能用作函数的参数,setter
被限制int
只允许写入,是消费者,不能用作函数的返回值,getter
被限制
泛型界限
可以对泛型指定界限,以达到限制某一类型及其子类的目的
class Test<T: Number>(var data: T)
现在该类的data
类型只能是Number
及其子类
多个约束使用where
关键字
class Test<T>(var data: T) where T: Number, T: Comparable<T>
写C#的小伙伴肯定很熟悉了
类型擦除
在编译后的代码是不会有任何的泛型类型的,都会被替换为Any?
,如果定义了上界,则会替换为上界类型
基于此,很多操作是不允许的,比如类型判断
class Test<T>(private var data: T) {
fun isType(obj: Any): Boolean {
// return obj is T // 报错
}
}
包括使用
val test: Test<Int> = Test(10)
println(test is Test<Double>) // 报错,由于类型擦除,无法判断具体的类型
println(test is Test)
使用reified
关键字可以具体化类型,具化类型参数允许在函数体内部检测泛型类型,该关键字只适用于内联函数
inline fun <reified T> isType(value: Any): Boolean {
return value is T
}
数组
在kotlin
中数组的类型是Array
创建
可使用内置的函数arrayOf()
,arrayOfNulls()
以及emptyArray()
val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)
使用Array
构造函数创建
val arr: Array<Int> = Array(5) { 0 } // 创建容量为5的数组,并全部初始化为0
创建完成后,数组容量和元素类型是固定不变的
操作
跟java的Type[]
一样,都是差不多的
val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)
arr[4] = 114
println(arr[4]) // 114
循环
for (i in arr) println(i)
indices
获取数组的索引
for (index in arr.indices) {
println(arr[index])
}
withIndex
方法能同时获取元素和索引
for ((index, value) in arr.withIndex()) {
println("$index : $value")
}
forEach
方法使用lambda
来遍历
arr.forEach { println(it) }
forEachIndexed
方法带索引的lambda
来遍历
arr.forEachIndexed { index, i -> println("$index -> $i") }
joinToString
将数组元素拼接为字符串
println(arr.joinToString("-", "(", ")", 4, "...more"){
(it * it).toString()
}) // (1-4-9-16-...more)
还有其它操作
val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val arr2: Array<Int> = arr.copyOf()
// 相等
println(arr === arr2) // false
println(arr.contentEquals(arr2)) // true
// 分隔数组
println((arr + arr2).sliceArray(0..<8).joinToString()) // 1, 2, 3, 4, 5, 1, 2, 3
// 数组反转排序
arr.reverse()
// 数组打乱随机排序
arr.shuffle()
// 数组排序
arr.sort()
arr.sortDescending()
// 自定义类需重写compareTo接口方法
// 包含
println(8 in arr2.reversedArray()) // false
println(arr.contains(3)) // true
可变长参数
使用关键字vararg
指定可变长参数,0~多个
fun main() {
myTest(1, 2, 3, 4, 5, 6, 7, 8)
}
fun myTest(vararg nums: Int) {
println(nums.contentToString()) // [1, 2, 3, 4, 5, 6, 7, 8]
}
可当做数组使用
但是不能直接传入数组,需要使用扩展运算符*
来将数组里的每个元素一个个传入
fun main() {
val arr = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)
myTest(*arr.toIntArray())
}
fun myTest(vararg nums: Int) {
println(nums.contentToString()) // [1, 2, 3, 4, 5, 6, 7, 8]
}
或者引用元素类型
fun main() {
val arr = arrayOf("abc", "123", "IIVI")
myTest(*arr)
}
fun myTest(vararg values: String) {
println(values.contentToString()) // [abc, 123, IIVI]
}
需要注意的是:
只允许一个可变长参数
原生类型数组
直接创建原生数组
intArrayOf(1, 2, 3, 45)
floatArrayOf(1f, 2f, 3f, 4f, 5f)
... // 依此类推
多维数组
val arr: Array<IntArray> = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5), intArrayOf(6, 7, 8, 9))
如下
{
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}
}
访问
println(arr[1][1]) // 5
访问非定义索引会报错
println(arr[1][2]) // 第二行第3个,不是默认0,而是未定义直接报错
进行对比
val arr: Array<IntArray> = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5), intArrayOf(6, 7, 8, 9))
val arr2 = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5), intArrayOf(6, 7, 8, 9))
println(arr.contentEquals(arr2)) // false
println(arr.contentDeepEquals(arr2)) // true
contentEquals
只会对比第一层元素,而contentDeepEquals
可以对比深层的元素
集合
List
:有序集合,可包含重复颜色Set
:不包含重复元素的集合,一般不维护顺序Map
:一组键值对
创建
使用函数mutableXxxOf
来创建不同类型的集合
val list = mutableListOf(1, 2, 3, 4, 5)
操作
集合
val set1 = mutableSetOf("AAA", "BBB", "CCC")
val set2 = mutableSetOf("DDD", "CCC", "FFF")
// 并集∪(+)
println(set1 union set2) // [AAA, BBB, CCC, DDD, FFF]
// 交集∩
println(set1 intersect set2) // [CCC]
// 差集-(-)
println(set1 subtract set2) // [AAA, BBB]
// 并集减去交集
println((set1 - set2) union (set2 - set1)) // [AAA, BBB, DDD, FFF]
对于自定义类对象,需要重写equals
和hashCode
函数才能在集合中不重复存放
class MyClass(var name: String, var age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MyClass
if (name != other.name) return false
if (age != other.age) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
return result
}
override fun toString(): String {
return "MyClass(name='$name', age=$age)"
}
}
val set = mutableSetOf(
MyClass("符玄", 18), MyClass("纳西妲", 500),
MyClass("符玄", 18), MyClass("符玄", 300)
)
println(set)
// 输出: [MyClass(name='符玄', age=18), MyClass(name='纳西妲', age=500), MyClass(name='符玄', age=300)]
键值对
使用to
关键字可以创建一个键值对
val map = mutableMapOf<Int, MyClass>(
10001 to MyClass("符玄", 18),
10002 to MyClass("纳西妲", 500),
19198 to MyClass("符玄", 18),
99999999.to(MyClass("符玄", 300))
)
println(map)
// 输出: {10001=MyClass(name='符玄', age=18), 10002=MyClass(name='纳西妲', age=500), 19198=MyClass(name='符玄', age=18), 99999999=MyClass(name='符玄', age=300)}
val my: MyClass? = map[19198]
println(my) // MyClass(name='符玄', age=18)
遍历
map.forEach { (k, v) ->
println("${k} = ${v}")
}
进行覆盖时会返回被移除的值
val old: MyClass? = map.put(10002, MyClass("藿藿", 50))
花式移除
map -= 10002 // 移除单个
map -= listOf(10001, 19198) // 批量移除
获取的键值对不存在时,返回默认值
map.getOrDefault(114514, MyClass("1919810", 114514))
获取键值对,如果不存在则存入默认值并返回
map.getOrPut(114514) { MyClass("1919810", 114514) }
需要注意的是:
值被移除时,那个键值对就会整个被移除,不存在值不在但键存在的情况
重写了
+=
和-=
运算符用于put
和remove
迭代器
集合和数组都有迭代器
val iterator = map.values.iterator()
Iterable
迭代器接口
/**
* Classes that inherit from this interface can be represented as a sequence of elements that can
* be iterated over.
* @param T the type of element being iterated over. The iterator is covariant in its element type.
*/
public interface Iterable<out T> {
/**
* Returns an iterator over the elements of this object.
*/
public operator fun iterator(): Iterator<T>
}
可以看到是operator
运算符重载,重载的运算符就是for in
遍历
不能在for in
遍历时修改集合,会报异常ConcurrentModificationException
val list = mutableListOf(1, 2, 3, 4, 5)
for (i in list) {
println(i)
list.remove(i) // 不能在for in遍历时修改集合
}
可以使用迭代器来修改,但是还是不能插入
val list = mutableListOf(1, 2, 3, 4, 5)
val iterator: MutableIterator<Int> = list.iterator()
while (iterator.hasNext()) {
println(iterator.next())
iterator.remove()
}
println(list.count()) // 0
需要注意的是:
迭代器的使用是一次性的,要从头来过则需要重新调用
iterator()
来重新生成迭代器
序列
类似于java的Stream
流,是惰性的,只有需要时才被调用
val list = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "FF", "123")
val result = list.asSequence().filter {
println("正在过滤:$it")
it.length > 2
}.map {
println("正在转换小写操作")
it.lowercase()
}.take(4)
result.forEach { println(it) }
其只包含一个生成迭代器的函数
/**
* Given an [iterator] function constructs a [Sequence] that returns values through the [Iterator]
* provided by that function.
* The values are evaluated lazily, and the sequence is potentially infinite.
*
* @sample samples.collections.Sequences.Building.sequenceFromIterator
*/
@kotlin.internal.InlineOnly
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
override fun iterator(): Iterator<T> = iterator()
}
需要注意的是:
对于大量数据使用序列可优化性能,而对于特别少的数据来说可能会增加开销
特殊类型
数据类型
使用关键字data
创建一个数据类型,代表一个数据类,类似于java的lambok
的@Data
注解
data class Milk(val name: String, var price: Double) {
var brand: String = "" // 该成员变量不会被data处理
}
声明该数据类后,会自动生成以下函数:
equals()
/hashCode()
toString()
生成的字符串格式类似于
Milk(name=纯牛奶, price=13.6)
componentN()
按成员声明顺序进行解构函数声明
copy()
用于对对象进行拷贝
val milk = Milk("纯牛奶", 13.6)
val milk2 = Milk("纯牛奶", 13.6)
println(milk == milk2) // true
val milkCopy = milk.copy()
milkCopy.price += .2233
println(milkCopy) // Milk(name=纯牛奶, price=13.8233)
编译后如下(Java):
public final class Milk {
@NotNull
private final String name;
private double price;
public Milk(@NotNull String name, double price) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.price = price;
}
@NotNull
public final String getName() {
return this.name;
}
public final double getPrice() {
return this.price;
}
public final void setPrice(double var1) {
this.price = var1;
}
@NotNull
public final String component1() {
return this.name;
}
public final double component2() {
return this.price;
}
@NotNull
public final Milk copy(@NotNull String name, double price) {
Intrinsics.checkNotNullParameter(name, "name");
return new Milk(name, price);
}
// $FF: synthetic method
public static Milk copy$default(Milk var0, String var1, double var2, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.name;
}
if ((var4 & 2) != 0) {
var2 = var0.price;
}
return var0.copy(var1, var2);
}
@NotNull
public String toString() {
return "Milk(name=" + this.name + ", price=" + this.price + ')';
}
public int hashCode() {
int result = this.name.hashCode();
result = result * 31 + Double.hashCode(this.price);
return result;
}
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
} else if (!(other instanceof Milk)) {
return false;
} else {
Milk var2 = (Milk)other;
if (!Intrinsics.areEqual(this.name, var2.name)) {
return false;
} else {
return Double.compare(this.price, var2.price) == 0;
}
}
}
}
需要注意的是:
该数据类型必须定义至少一个成员变量
主构造函数必须标记为
val
或var
如果以及实现了上述自动实现的函数,就不会自动生成,除了
componentN
和copy
函数不能显示实现该数据类型不能被继承、不能是抽象、不能是密封的、不能是内部的
枚举类型
在kotlin
中,每个枚举值可以看做一个个类,它们都继承与定义的枚举类,如下
enum class EHttpStatus {
OK,
MovePermanently,
NotFound,
InternalServerError,
}
像OK
这个枚举值就是一个类,其继承与EHttpStatus
这个类
可以看到上述的枚举是个Http
状态码类,只有状态没有码,枚举也是可以有成员的,我们给它加个码
enum class EHttpStatus(val code: Int) {
OK(200),
MovePermanently(301),
NotFound(404),
InternalServerError(500),
}
还能定义函数和重写函数
enum class EHttpStatus(val code: Int) {
OK(200),
MovePermanently(301),
NotFound(404),
InternalServerError(500);
override fun toString(): String {
return code.toString()
}
}
甚至每个枚举值还能实现接口
interface IShowInfo {
fun showInfo(): String
}
enum class EHttpStatus(val code: Int): IShowInfo {
OK(200) {
override fun showInfo(): String {
return "OK"
}
},
MovePermanently(301) {
override fun showInfo(): String {
return "Move Permanently"
}
},
NotFound(404) {
override fun showInfo(): String {
return "Not Found"
}
},
InternalServerError(500) {
override fun showInfo(): String {
return "Internal Server Error"
}
};
override fun toString(): String {
return code.toString()
}
}
使用when
表达式进行判断
var status = EHttpStatus.MovePermanently
val msg = when (status) {
EHttpStatus.MovePermanently -> "1"
EHttpStatus.OK -> "2"
EHttpStatus.NotFound -> "3"
EHttpStatus.InternalServerError -> "4"
}
println(msg)
如下图就可以看到枚举类和枚举值都分别生成了一个类
匿名类
适用于以匿名的形式创建一个临时的使用对象,在使用完之后就不需要了的情况,比如函数的接口形参
fun main() {
makeAShow(object : IShowInfo {
override fun showInfo(): String {
return "Hello Kotlin"
}
}) // Hello Kotlin
}
interface IShowInfo {
fun showInfo(): String
}
fun makeAShow(show: IShowInfo) {
println(show.showInfo())
}
匿名类不能创建构造函数,所以也称对象表达式,无法定义成员变量
fun main() {
println(test().name)
// println(test().age) // 报错,没有age这个成员变量
}
open class Human(val name: String)
fun test() = object : Human("青衣") {
val age: Int = 18
override fun toString(): String = "Human($name)"
}
对于只有一个函数的单一接口,也称函数式接口,可以直接使用lambda
写函数体
makeAShow { "Hello World" } // Hello World
它还是个饿汉式单例
fun main() {
val sing = Singleton
println(sing.name) // 你干嘛~?
println(sing) // 你干嘛~?诶哟~
}
// 单例类
object Singleton {
var name = "你干嘛~?"
override fun toString(): String = "${name}诶哟~"
}
编译如下(Java):
public final class Singleton {
@NotNull
public static final Singleton INSTANCE = new Singleton();
@NotNull
private static String name = "你干嘛~?";
private Singleton() {
}
@NotNull
public final String getName() {
return name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
name = var1;
}
@NotNull
public String toString() {
return name + "诶哟~";
}
}
伴生对象
希望一个类即支持单例类那样直接调用,又支持像一个普通类一样使用,那就使用伴生对象
实际就是将单例类写到某个类的内部
fun main() {
println(MyClass.MOCK)
MyClass.isDone = true
println(MyClass.isDone)
}
class MyClass(var name: String){
companion object {
const val MOCK = "MOCK_STR"
var isDone = false
}
}
可以在伴生对象里定义静态的成员函数和成员变量,就是相当于java的静态关键字static
使用类名来进行访问
委托模式
是一个更好的继承替代方案
委派设计模式代码如下
fun main() {
val derived = Derived(BaseImpl(114514))
derived.print() // 114514
}
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() = print(x)
}
class Derived(val b: Base) : Base {
override fun print() = b.print()
}
可以看到Derived
对于print
方法无能为力,只能委托其它已经实现的类BaseImpl
来进行print
在kotlin
中,对于该设计模式给予了原生支持
class Derived(b: Base) : Base by b
除了类,类的成员属性也可以委托给其它对象
class MyClass {
val name: String by lazy { "延迟名称" }
}
上述代码表示在获取name
时才会触发后面函数体的内容
还可以对变量进行监控
import kotlin.properties.Delegates
fun main() {
val my = MyClass()
println(my.name) // 我是初始值
my.name = "新的值" // name changed from 我是初始值 to 新的值
}
class MyClass {
var name: String by Delegates.observable("我是初始值") {
property, oldValue, newValue ->
println("${property.name} changed from $oldValue to $newValue")
}
}
使用
::
来将属性委托给其它属性
class MyClass(var name: String = "") {
var nick: String by ::name
}
委托给map集合,实现属性名称一一对应属性值
fun main() {
val map = mutableMapOf<String, Any>(
"name" to "青衣",
"age" to 18
)
val user = User(map)
println(user) // User(name='青衣', age=18)
map["age"] = 9999 // 属性名称一一对应属性值
println(user) // User(name='青衣', age=9999)
}
class User(map: MutableMap<String, Any>) {
var name: String by map
var age: Int by map
override fun toString(): String {
return "User(name='$name', age=$age)"
}
}
密封类
密封类只能同一模块进行继承和使用,使用关键字sealed
定义密封类
sealed class MyClass
密封类本身就是抽象类,所以不能实例化
异常
自定义异常
class TestException(override val message: String): Exception(message)
异常处理
fun main() {
try {
test(1, 0)
} catch (e: TestException) {
println("程序发生错误: ${e.message}") // 程序发生错误: 你干嘛~?诶哟除以0
} finally {
println("程序结束") // 程序结束
}
}
class TestException(override val message: String): Exception(message)
fun test(a: Int, b: Int): Int {
if (b == 0) throw TestException("你干嘛~?诶哟除以0")
return a / b
}
还可以在变量赋值时进行简写
val input = readln()
val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }