Kotlin的语法入门

前言

Kotlin 是一种现代化的静态类型编程语言,最初由 JetBrains 开发并于 2011 年公开发布。它是一种通用编程语言,设计用于与 Java 100% 兼容,因此可以在 Java 平台上无缝运行。Kotlin 专注于简洁性、可读性和可维护性,并且旨在减少代码的样板和冗余性。

Kotlin 具有许多吸引人的特性,包括:

  1. 与 Java 互操作性: Kotlin 可以与 Java 代码完全互操作,这意味着现有的 Java 项目可以逐步采用 Kotlin,而不需要重新编写整个代码库。

  2. 空安全: Kotlin 在语言级别支持空安全,这意味着它能够防止常见的空指针异常,并提供更安全的代码。

  3. 扩展函数: Kotlin 允许开发者在不修改类定义的情况下向现有类添加新方法,这通过所谓的扩展函数实现。

  4. 函数式编程支持: Kotlin 提供了许多函数式编程的特性,如 Lambda 表达式、高阶函数等,使得编写函数式风格的代码更加容易。

  5. 协程: Kotlin 通过协程提供了一种轻量级的并发编程方式,能够简化异步代码的编写,提高代码的可读性和可维护性。

  6. 数据类: Kotlin 提供了数据类(data class)的概念,简化了用于数据传输和持久化的类的编写。

  7. 可扩展性: Kotlin 允许开发者通过编写自定义的 DSL(领域特定语言)来扩展语言的功能,从而使其适应特定领域的需求。

总的来说,Kotlin 是一种强大而现代的编程语言,适用于各种应用场景,从 Android 移动应用开发到企业级后端服务开发。

其简洁性、安全性和与 Java 的无缝集成使得它成为了许多开发者的首选之一。

那么性能呢?

Kotlin 和 Java 在性能方面没有明显的优劣之分,它们在 JVM 上都可以实现高性能的应用程序。

这是因为 Kotlin 与 Java 共享 JVM 运行环境,并且 Kotlin 编译器会将 Kotlin 代码编译成与 Java 字节码兼容的字节码。

数据类型

在 Kotlin 中,数据类型包括基本数据类型和引用数据类型。下面是 Kotlin 中常见的数据类型:

基本数据类型

  • 整数类型
    • Byte:8 位有符号整数,取值范围为 -128 到 127。
    • Short:16 位有符号整数,取值范围为 -32768 到 32767。
    • Int:32 位有符号整数,取值范围为 -2^31 到 2^31 - 1。
    • Long:64 位有符号整数,取值范围为 -2^63 到 2^63 - 1。
  • 浮点类型
    • Float:32 位单精度浮点数。
    • Double:64 位双精度浮点数。
  • 其他基本类型
    • Char:16 位 Unicode 字符。
    • Boolean:布尔类型,取值为 truefalse

引用数据类型

  • 字符串:使用 String 类表示,是不可变的。
  • 数组:Kotlin 中的数组有固定大小,使用 Array 类表示。
  • 集合:Kotlin 标准库提供了丰富的集合类,如 ListSetMap 等。
  • :开发者可以定义自己的类来表示复杂的数据结构。

Kotlin 还支持类型推断,这意味着在大多数情况下,编译器可以推断出变量的类型,而不需要显式地指定类型。

例如:

1
2
val number = 42 // Kotlin 编译器会推断出 number 的类型为 Int
val name = "Kotlin" // Kotlin 编译器会推断出 name 的类型为 String

字符串

下面的 Kotlin 代码示例,展示了如何使用字符串模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
val name = "Alice"
val age = 30
val city = "Wonderland"

// 使用字符串模板输出信息
println("姓名:$name,年龄:$age,城市:$city")

// 字符串模板中可以包含表达式
println("明年${age + 1}岁时,$name 将在 $city 庆祝生日。")

// 使用字符串模板作为函数参数
greet("$name", age)
}

fun greet(name: String, age: Int) {
println("你好,$name!你今年$age岁了。")
}

在上面的示例中,我们定义了变量 nameagecity,然后使用字符串模板将它们嵌入到字符串中。

在第二个 println 语句中,我们还展示了如何在字符串模板中包含表达式。

最后,我们调用了 greet 函数,并将字符串模板作为参数传递给函数。

数组操作

在Kotlin中,数组的操作可以通过标准库中的Array类来完成。

以下是一些常见的 Kotlin 数组操作:

创建数组

使用构造函数创建数组

1
val array = Array(5) { i -> i * 2 } // 创建一个包含 5 个元素的数组,每个元素的值是索引的两倍

使用arrayOf函数创建数组

1
val array = arrayOf(1, 2, 3, 4, 5) // 创建一个包含指定元素的数组

访问数组元素

1
val element = array[index] // 获取数组中指定索引处的元素值

修改数组元素

1
array[index] = value // 修改数组中指定索引处的元素值

数组切片

1
val subArray = array.sliceArray(1..3) // 返回数组中指定范围的子数组

数组合并

1
val combinedArray = array1 + array2 // 将两个数组合并为一个新数组

遍历数组

使用for循环

1
2
3
for (element in array) {
// 对每个元素执行操作
}

使用forEach高阶函数

1
2
3
array.forEach { element ->
// 对每个元素执行操作
}

数组转换

1
val newArray = array.map { it * 2 } // 将数组中的每个元素乘以 2 并返回一个新的数组

过滤数组

1
val filteredArray = array.filter { it % 2 == 0 } // 过滤数组中的偶数元素

查找数组元素

1
val index = array.indexOf(element) // 返回指定元素在数组中的索引,如果不存在则返回 -1

数组排序

1
2
3
val sortedArray = array.sorted() // 返回一个排序后的新数组

array.sort() // 原地对数组进行排序

集合类

Set/List/Map

首先要说的是没有new

集合的分类:

  • Set(集)
  • List(列表)
  • Map(映射)

Kotlin中,明确的区分了只读可变的集合
代码如下 前三个是只读 后三个是可变的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var list = listOf<String>("aa", "bb", "cc")
list.forEach { item ->
Log.i("Kotlin", item)
}

var set = setOf<String>("aa", "bb", "cc")
set.forEach { item ->
Log.i("Kotlin", item)
}


val map = mapOf("a" to 1, "b" to 2, "c" to 3)
var map = mapOf<String, String>(Pair("aa", "AA"), Pair("bb", "BB"), Pair("cc", "CC"));
for ((key,value) in map){
Log.i("Kotlin", "key:$key value:$value")
}

可变长度

1
2
3
4
5
6
7
8
9
10
11
var mList = mutableListOf<String>();
mList.add("aa")
Log.i("Kotlin", mList[0])

var mSet = mutableSetOf<String>();
mSet.add("aa")
Log.i("Kotlin", mSet.elementAt(0))

var mMap = mutableMapOf<String, String>()
mMap["aa"] = "AA"
Log.i("Kotlin", "$mMap")

List处理

遍历

只遍历项

1
2
3
4
var list = listOf<String>("aa", "bb", "cc")
list.forEach { item ->
Log.i("Kotlin", item)
}

遍历索引和项

有几种方法可以做到这一点,以下是其中的一些:

使用 forEachIndexed 函数:

1
2
3
4
val list = listOf("a", "b", "c")
list.forEachIndexed { index, item ->
println("索引 $index 对应的项为 $item")
}

使用 indicesget 属性:

1
2
3
4
val list = listOf("a", "b", "c")
for (i in list.indices) {
println("索引 $i 对应的项为 ${list[i]}")
}

使用 withIndex() 函数:

1
2
3
4
val list = listOf("a", "b", "c")
for ((index, item) in list.withIndex()) {
println("索引 $index 对应的项为 $item")
}

以上方法都可以用来遍历 List,并在每次迭代中获取索引和项。

选择哪种方法取决于个人偏好和代码风格。

map

1
2
3
val ulist = mutableListOf<String>("A", "B", "C")
val ulistNew = ulist.map { c -> c.toLowerCase(Locale.ROOT) }
L.e("ulistNew:${ulistNew}")

结果

ulistNew:[a, b, c]

filter

1
2
3
4
5
val ulist = mutableListOf<Int>(1, 2, 3)
val ulistNew = ulist.filter {
it >= 2
}
L.e("ulistNew:${ulistNew}")

结果

ulistNew:[2, 3]

foreach

1
2
3
4
val ulist = mutableListOf<Int>(1, 2, 3)
ulist.forEach{
L.e("ulistNew:${it}")
}

结果

1
2
3

可变长参数函数

函数的变长参数可以用 vararg 关键字进行标识

1
2
3
4
5
6
7
8
9
10
fun varargFun(vararg v:String){
for(vt in v){
print(vt)
}
}
//普通传递:
varargFun(1,"aaa","bbb","ccc","ddd","fff")
//数组传递:
val strArray = arrayOf("aaa","bbb","ccc","ddd","fff")
varargFun(1,*strArray)

数据类(pojo)

1
data class Customer(val name: String, val email: String)

自动添加的方法:

  • 为所有属性添加 getters ,如果为 var 类型同时添加 setters
  • equals()
  • haseCode()
  • toString()
  • copy()

数据类以及解构声明

组件函数允许数据类在解构声明中使用:

1
2
3
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

枚举

1
2
3
4
5
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}

可见修饰符

  • private:同一类或文件(针对包级别定义)中可见
  • protected:同private 加子类可见
  • internal:在同一个模块中可见(如果声明范围的所有者是可见的)
  • public:公共,所有都可见

单例

1
2
3
4
5
object MyClass{
fun aa(){

}
}

那么MyClass.aa()其实就是调用单例的aa方法

继承

open 关键字用于声明一个可以被继承或者被子类重写的类、方法或者属性。

Kotlin 中的类、方法、属性默认是不可继承或重写的,如果你希望某个类可以被其他类继承,或者某个方法或属性可以在子类中被重新实现,就需要使用 open 关键字来进行声明。

自定义组件

1
2
3
4
5
6
7
8
9
10
open class SpliterLinearLayout : LinearLayoutCompat {

constructor(context: Context) : super(context) {

}

constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {

}
}

类扩展

1
2
3
4
5
fun <T> MutableList<T>.swap(x: Int, y: Int) {
val tmp = this[x] // 'this' corresponds to the list
this[x] = this[y]
this[y] = tmp
}

调用方式

1
2
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)// 在 `swap()` 函数中 `this` 持有的值是 `list`

扩展是被静态解析

延迟初始化

在 Kotlin 中,lateinit 关键字用于延迟初始化属性,即在声明属性时不需要立即赋初始值,而是在后续的某个时间点再进行赋值。

lateinit 只能用于非空类型的属性,并且必须是类属性(不能是局部变量)。

下面是一个简单的示例,演示了如何在 Kotlin 中使用 lateinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Example {
lateinit var name: String

fun init() {
name = "WeTab"
}

fun printName() {
// 在确保 name 已经被初始化之后才能访问它
if (::name.isInitialized) {
println(name)
} else {
println("name 未被初始化")
}
}
}

fun main() {
val example = Example()
example.init()
example.printName()
}

在这个示例中,name 属性被标记为 lateinit,在 initializeName() 方法中对其进行了初始化,然后在 printName() 方法中检查是否已经被初始化并打印其值。

需要注意的是,如果在访问一个未被初始化的 lateinit 属性时,会抛出 UninitializedPropertyAccessException 异常,因此在访问之前必顋确保已经对其进行了初始化。

类对比

Java

1
2
3
Intent t = new Intent();
t.setClass(this,LoginActivity.class);
startActivity(t);

Kotlin

1
2
3
val t = Intent()
t.setClass(this, LoginActivity::class.java)
startActivity(t)

接口写法对比

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
OkGo.<TUserBean>post("")
.params("","")
.execute(new JsonCallback<TUserBean>() {
@Override
public void onSuccess(Response<TUserBean> response) {

}

@Override
public void onFinish() {
super.onFinish();
}
});

Kotlin

1
2
3
4
5
6
7
8
9
10
11
OkGo.post<TUserBean>("")
.params("", "")
.execute(object : JsonCallback<TUserBean>() {
override fun onSuccess(response: Response<TUserBean>) {

}

override fun onFinish() {
super.onFinish()
}
})

静态属性和方法

Kotlin中定义

1
2
3
4
5
6
7
8
9
class ApiModel {
companion object {
val baseUrl = "http://www.psvmc.cn"

fun getName(): String {
return "小明"
}
}
}

Kotlin中调用

1
2
ApiModel.baseUrl
ApiModel.getName()

Java中调用

1
2
ApiModel.Companion.getBaseUrl();
ApiModel.Companion.getName();

所以建议静态属性和方法建议还用Java来写

类转换

1
activity as ClassActivity

表达式

三目运算

1
if(a>b) a else b

When 表达式

when 取代了 C 风格语言的 switch

也能替换if/else

1
2
3
4
5
6
7
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}

循环

1
2
3
4
5
for (i in 1..4) print(i) //打印1234
for (i in 1 until 4)print(i) //打印123
for (i in 4 downTo 1 step 1) print(i) //打印4321
for (i in (1..4).reversed()) print(i) // prints "4321"
for (i in (1..4).reversed() step 2) print(i) // prints "42"

对象内置函数

大括号括着代表这个函数结合了前面函数的功能。

image-20211220171249959

let函数

不为空后执行

1
2
3
4
5
6
7
8
9
10
11
12
val date = ...
data?.let{
...//如果不为空执行该语句块
}

mVar?.function1()

// 使用kotlin(使用let函数)
// 方便了统一判空的处理 & 确定了mVar变量的作用域
mVar?.let {
it.function1()
}

also函数

作用 & 应用场景

类似let函数,但区别在于返回值:

  • let函数:返回值 = 最后一行 / return的表达式
  • also函数:返回值 = 传入的对象的本身

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// let函数
var result = mVar.let {
it.function1()
it.function2()
999
}
// 最终结果 = 返回999给变量result

// also函数
var result = mVar.also {
it.function1()
it.function2()
999
}
// 最终结果 = 返回一个mVar对象给变量result

with函数

作用

调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可

应用场景

需要调用同一个对象的多个方法 / 属性

使用方法

1
2
3
4
5
 with(object){
// ...
}

// 返回值 = 函数块的最后一行 / return表达式

使用示例

1
2
3
4
5
6
7
8
9
10
11
// 此处要调用people的name 和 age属性
// kotlin
val people = People("carson", 25)
with(people) {
println("my name is $name, I am $age years old")
}

// Java
User peole = new People("carson", 25);
String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
System.out.println(var1);

run函数

作用

结合了let、with两个函数的作用,即:

  1. 调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
  2. 定义一个变量在特定作用域内
  3. 统一做判空处理

使用方法

1
2
3
4
object.run{
// ...
}
// 返回值 = 函数块的最后一行 / return表达式

使用示例

1
2
3
4
5
6
7
8
9
10
11
// 此处要调用people的name 和 age属性,且要判空
// kotlin
val people = People("carson", 25)
people?.run{
println("my name is $name, I am $age years old")
}

// Java
User peole = new People("carson", 25);
String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
System.out.println(var1);

apply函数

作用 & 应用场景

与run函数类似,但区别在于返回值:

  • run函数返回最后一行的值 / 表达式
  • apply函数返回传入的对象的本身

应用场景

对象实例初始化时需要对对象中的属性进行赋值 & 返回该对象

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// run函数
val people = People("carson", 25)
val result = people?.run{
println("my name is $name, I am $age years old")
999
}
// 最终结果 = 返回999给变量result

// Java
val people = People("carson", 25)
val result = people?.run{
println("my name is $name, I am $age years old")
999
}
// 最终结果 = 返回一个people对象给变量result

函数

传参写在大括号中

在 Kotlin 中,如果函数的最后一个参数是一个函数,那么可以将这个函数参数写在大括号 {} 中,这样在调用该函数时可以更清晰地表达函数的意图。

这种写法通常用于函数式编程的场景,例如定义高阶函数或使用函数字面值(lambda 表达式)。

下面是一个简单的示例,展示了如何在 Kotlin 中使用大括号 {} 来传递函数参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun process(text: String, block: (String) -> Unit) {
println("开始处理文本:$text")
block(text)
println("处理完毕!")
}

fun main() {
val text = "Hello, Kotlin!"

// 使用大括号 {} 传递函数参数
process(text) {
println("打印文本:$it")
}
}

在上面的例子中,process 函数的最后一个参数是一个函数 (String) -> Unit,通过将这个函数写在大括号 {} 中,可以更清晰地看到这个函数是作为一个代码块传递给了 process 函数。

省略小括号

在 Kotlin 中,函数调用可以省略括号的情况有以下两种:

无参函数调用

当函数没有参数时,可以省略函数调用时的括号。

这通常用于无副作用的函数,例如 Kotlin 标准库中的一些扩展函数,比如 isEmpty()

示例如下:

1
2
3
4
val list = listOf(1, 2, 3)
if (list.isEmpty) {
println("List is empty")
}

Lambda 表达式是函数的最后一个参数

当函数的最后一个参数是一个 Lambda 表达式时,可以将 Lambda 表达式移到函数调用的外部,且省略函数调用时的括号。

这种语法被称为尾随闭包(Trailing Closures)。

示例如下:

1
2
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }

在上述示例中,map 函数的最后一个参数是一个 Lambda 表达式,因此可以省略函数调用时的括号,并将 Lambda 表达式移到函数调用的外部。

异常处理

在 Kotlin 中,异常处理与 Java 类似,但有一些语法上的差异和特点。

下面是 Kotlin 中异常处理的一般方法:

try-catch 块:

Kotlin 使用 trycatch 关键字来捕获异常。

与 Java 不同的是,在 Kotlin 中,catch 子句被看作是一个表达式,因此它可以返回一个值。这使得异常处理更加灵活。

示例如下:

1
2
3
4
5
6
7
try {
// 可能会抛出异常的代码
} catch (e: SomeException) {
// 处理异常的代码
} finally {
// 可选的 finally 块
}

try 表达式:

Kotlin 还提供了 try 表达式,它类似于 Java 中的 try-catch-finally 结构,但它可以作为表达式来使用,因此可以直接返回一个值。

示例如下:

1
2
3
4
5
6
val result = try {
// 可能会抛出异常的代码
} catch (e: SomeException) {
// 处理异常的代码
defaultValue // 指定一个默认值
}

Throw 表达式:

在 Kotlin 中,可以使用 throw 关键字抛出异常。与 Java 类似,你可以抛出任何类型的异常。示例如下:

1
2
3
4
5
6
fun divide(a: Int, b: Int): Int {
if (b == 0) {
throw IllegalArgumentException("Divider cannot be zero")
}
return a / b
}

自定义异常类:

与 Java 一样,你可以在 Kotlin 中创建自定义异常类。示例如下:

1
class CustomException(message: String) : Exception(message)

不检查异常(Unchecked Exceptions):

Kotlin 不区分受检查异常和不受检查异常,因此不需要在函数声明中声明可能抛出的异常,但你仍然可以选择捕获或抛出异常。

总的来说,Kotlin 的异常处理与 Java 相似,但具有一些语法上的差异和特点,使得异常处理更加灵活和方便。

日期处理

以下是一个使用Kotlin处理日期的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.time.LocalDate
import java.time.format.DateTimeFormatter

fun main() {
// 获取当前日期
val currentDate = LocalDate.now()
println("当前日期: $currentDate")

// 格式化日期
val formattedDate = currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
println("格式化后的日期: $formattedDate")

// 解析字符串为日期
val dateStr = "2024-03-17"
val parsedDate = LocalDate.parse(dateStr)
println("解析后的日期: $parsedDate")

// 比较日期
val futureDate = LocalDate.of(2025, 3, 17)
if (futureDate.isAfter(currentDate)) {
println("$futureDate 在未来")
} else if (futureDate.isBefore(currentDate)) {
println("$futureDate 在过去")
} else {
println("$futureDate 是今天")
}

// 添加或减去天数
val modifiedDate = currentDate.plusDays(5)
println("添加5天后的日期: $modifiedDate")

val modifiedDate2 = currentDate.minusMonths(2)
println("减去2个月后的日期: $modifiedDate2")
}

这个示例演示了如何获取当前日期,格式化日期,解析字符串为日期,比较日期,以及对日期进行简单的算术操作,如添加或减去天数和月份。