kotlin
liduoan.efls Engineer

Kotlin基础

一种在Java虚拟机上运行的静态类型编程语言

可以和java代码相互与运作

容易在Android项目中替代Java或者同Java一起使用

image

*kt会被Kotlin编译器编程编译成.class的字节码文件,然后被归档成.jar,最后呢由各平台打包工具输出最终的原因程序

上图不难理解*kt最终会被编译成Java的字节码文件,那为什么在最后一步还需要一个Kotlin运行时呢?

这是因为,我们用Java来写的程序所有的实现都会有标准的Java类库来做支撑,比如:java.lang.*, java.util.*,但Kotlin中的类库是不在标准的Java类库中的,所以,Kotlin应用程序需要在最后一步借助Kotlin运行时来支撑这些Java标准类库没有的实现。

数据类型

Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是,字符不属于数值类型,是一个独立的数据类型。

1
2
3
4
5
fun baseType() {
val num1 = -1.5 //默认的double类型
val num2 = 1f //这里是float类型
var num3 = 3 //Int类型
}

数组

数组的创建方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun arrayType() {
// arrayOf 创建数组
var arrayOf = arrayOf(1, 2, 3)
// arrayOfNulls创建指定大小 所有元素为空的数组
var arrayOfNulls = arrayOfNulls<String>(5)
// 动态创建数组
var asc = Array(5) {i -> (i*i).toString()}
asc.forEach { println(it) }
//[] 运算符代表调用成员方法 get() 与 set()

// 原生类型数组
var intArrayOf = intArrayOf(3, 4, 5)
// 大小为5 数值为0的数组
var intArray = IntArray(5)
// 大小为5 数值为32的数组
var intInit = IntArray(5) { 32 }
}

数组的遍历方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun arrayFor() {
var array = intArrayOf(3, 4, 5)
// 数组遍历
for (item in array) {
println(item)
}
// 索引遍历
for (index in array.indices) {
println(index.toString() + "-> ${array[index]}" )
}
// 遍历元素带索引
for ((index, item) in array.withIndex()) {
println("$index -> $item")
}
// foreach 遍历
array.forEach { print(it) }
// 高级foreach
array.forEachIndexed { index, item ->
println("$index -> $item")
}
}

集合

Kotlin 标准库提供了一整套用于管理集合的工具,集合是可变数量(可能为零)的一组条目,各种集合对于解决问题都具有重要意义,并且经常用到。

  • List 是一个有序集合,可通过索引(反映元素位置的整数)访问元素。元素可以在 list 中出现多次。列表的一个示例是一句话:有一组字、这些字的顺序很重要并且字可以重复。

  • Set 是唯一元素的集合。它反映了集合(set)的数学抽象:一组无重复的对象。一般来说 set 中元素的顺序并不重要。例如,字母表是字母的集合(set)。

  • Map(或者字典)是一组键值对。键是唯一的,每个键都刚好映射到一个值,值可以重复。

我们需要注意到,集合分为可变集合和不可变集合两种方式

而数组则是可变的数组,我们可以看到Array

1
2
3
4
5
6
7
8
9
10
11
public class Array<T> {
public inline constructor(size: Int, init: (Int) -> T)

public operator fun get(index: Int): T

public operator fun set(index: Int, value: T): Unit

public val size: Int

public operator fun iterator(): Iterator<T>
}

这里我们看下集合的排序Api

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
fun listMap() {
val numbers = mutableListOf(1, 2, 3, 4)
//随机排列元素
numbers.shuffle()
println(numbers)
numbers.sort()
//排序,从小打到
numbers.sortDescending()
//从大到小
println(numbers)
//定义一个Person类,有name 和 age 两属性
data class Language(var name: String, var score: Int)
val languageList: MutableList<Language> = mutableListOf()
languageList.add(Language("Java", 80))
languageList.add(Language("Kotlin", 90))
languageList.add(Language("Dart", 99))
languageList.add(Language("C", 80))
//使用sortBy进行排序,适合单条件排序
languageList.sortBy { it.score }
println(languageList)
//使用sortWith进行排序,适合多条件排序
languageList.sortWith(compareBy(
//it变量是lambda中的隐式参数
{ it.score }, { it.name }) )
println(languageList)
}

方法

image

在Java中对象是一等公民,而在Kotlin中方法是一等公民

所有的方法是可以直接定义在文件里面的,而java中方法必须定义类中

这也说明了Kotlin是以方法为一等的

方法声明

主要看下有哪些方法

成员方法

1
2
3
4
5
6
7
8
9
fun main() {
Person().getName()
}

class Person {
fun getName() {
println(this.javaClass.simpleName)
}
}

类方法

也就是我们在java中常说的静态方法

在kotlin中可以有几种方式实现

  • companion object 实现的类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
Person.fun2()
}
class Person {
// 伙伴对象的类方法
companion object {
fun fun2() {
println("伙伴对象的类方法")
}
}
fun getName() {
println(this.javaClass.simpleName)
}
}
  • 静态类
1
2
3
4
5
6
// 工具类的类方法
object TimeUtil {
fun timeGet(): Date {
return Date()
}
}
  • 全局静态

    也就是Kotlin文件中定义的一些方法,它们可以在任何地方被调用

单表达式方法

也就是方法返回的是单个的表达式,可以省略花括号并且在=号后指定代码体

1
2
3
fun double(x: Int): Int = x * 2
// 返回值可以由编译器进行推断
fun double(x: Int) = x * 2

方法参数

主要有具体参数,默认参数,可变数量的参数

具体参数就是平常的那种

默认参数指的是参数可以有默认值

1
2
// 可以看到off的数值为0 len的数值为数组大小
fun read(b: Array, off: Int = 0, len: Int = b.size) { /*……*/ }

补充知识,在方法中最后一个参数是Lambda表达式的话,表达式可以在括号外传入。

可变数量的参数(Varargs)

方法的参数(通常是最后一个)可以用 vararg 修饰符标记:

1
2
3
fun append(vararg str: Char): String {
//.....
}

可变参数的要求:

只有一个参数可以标注为 vararg

如果 vararg 参数不是列表中的最后一个参数, 可以使用具名参数语法传递其后的参数的值,或者,如果参数具有方法类型,则通过在括号外部传一个 Lambda。

当我们调用 vararg 方法时,我们可以一个接一个地传参,例如 append('h', 'e', 'l', 'l', 'o'),或者,如果我们已经有一个数组并希望将其内容传给该方法,我们使用伸展(spread)操作符(在数组前面加 *):

1
2
val world = charArrayOf('w', 'o', 'r', 'l', 'd') 
val result = append('h', 'e', 'l', 'l', 'o',' ', *world)

方法作用域

方法作用域为文件顶层声明,局部方法

我们主要看下没见过的局部方法

1
2
3
4
5
6
7
8
9
10
fun magic(): Int{
// 局部方法
fun foo(v:Int): Int {
return v*v
}

var random = (0..100).random()

return foo(random)
}

方法进阶

高阶方法

高阶函数就是将函数作为参数或返回值的函数。Kotlin支持高阶函数,这是Kotlin函数式编程的一大特性。

一般有函数作为参数和函数作为返回值两种方式

作为参数比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun List<Int>.sum(callback: (Int) -> Unit): Int {
var result = 0
for(v in this) {
result += v
callback(v)
}
return result
}

//调用方式
fun main() {
var listOf = listOf<Int>(1, 2, 3)
var sum = listOf.sum { println(it) }
}

作为返回值比较不好明白

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 需求:实现一个能够对集合元素进行求和的高阶函数,并且返回一个 声明为(scale: Int) -> Float的函数

fun List<String>.toIntSum() : (scale: Int) -> Float{
println("第一层函数")
return fun(scale) : Float{
var result = 0f
for (v in this) {
result += v.toInt() * scale
}
return result
}
}
// 调用方式
fun main() {
var listOf = listOf<String>("1", "2", "3")
var toIntSum = listOf.toIntSum()
println(toIntSum(2))
//简易写法可以为
// listOf.toIntSum()(2)
}

闭包

方法与闭包的特性可以说是Kotlin语言最大的特性了

闭包可以简单理解为能够读取其他方法内部变量的方法。例如在JavaScript中,只有方法内部的子方法才能读取局部变量,所以闭包可以理解成“定义在一个方法内部的方法“。在本质上,闭包是将方法内部和方法外部连接起来的桥梁。

闭包的特性:

  • 方法可以作为另一个方法的返回值或者参数,还可以作为一个变量的值
  • 方法可以嵌套定义,即在一个方法内部可以定义另一个方法

闭包的好处:

  • 加强模块化:闭包有益于模块化编程,它能以简单的方式开发较小的模块,提高开发速度和程序的可复用性
  • 抽象:闭包是数据和行为的组合,这可以使得闭包具有较好的抽象能力
  • 灵活:闭包的应用使得编程更加灵活
  • 简化代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//需求:实现一个接受一个testClosure方法,该方法要接受一个Int类型的v1参数,
// 同时能够返回一个声明为(v2: Int, (Int) -> Unit)的函数,并且这个函数能够计算v1与v2的和
fun testClosure(v1: Int): (v2: Int, (Int) -> Unit) -> Unit {
println("第一层函数")
return fun(v2: Int, printer: (Int) -> Unit) {
printer(v1 + v2)
}
}

fun main() {
var testClosure = testClosure(2)
testClosure(3){
println(it)
}
}

解构声明

指的是把对象解构成很多的变量

1
2
3
4
5
var result = Result("success", 0) 
// 解构声明的语法
val (msg, code) = result
println("msg:${msg}")
println("code:${code}")

很清晰的语法方式

匿名方法

就是没有方法名的方法

1
2
3
4
5
6
7
//这种就是匿名方法
fun(x: Int, y: Int): Int = x + y

fun(x: Int, y: Int): Int {
return x + y
}

我们在闭包的例子中返回的方法就是匿名方法

这就是一个简易的语法罢了

方法的字面值

我认为这个就是变量可以是一个方法的官方解释

1
2
3
4
5
6
7
8
//定义了一个变量 tmp,而该变量的类型就是 (Int) -> Boolean 
fun literal() {
var tmp: ((Int) -> Boolean)? = null
// { num -> (num > 10) }即是一个方法字面值
tmp = { num -> (num > 10) }
println(tmp(10))
}

看到tmp变量,他的变量类型是什么?是方法!

类与接口

构造方法

在Kotlin中一个类可以有一个主构造方法和多个次构造方法

我们先看主构造方法

主构造方法是类头的一部分,跟在类名后面。

1
2
class KotlinClass constructor(name: String) {
}

如果主构造方法没有任何注解或者可见性修饰符可以省去constructor

1
2
class KotlinClass (name: String) {
}

主构造方法不能包含任何代码,初始化代码可以放到init关键字的初始化块中。

初始化块的顺序按照在类体中的顺序执行,和属于初始化器交织在一起

声明属性的构造方法

1
2
3
//构造方法的参数作为类的属性并赋值,
//KotlinClass2在初始化时它的name与score属性会被赋值
class KotlinClass2(val name: String, var score: Int) { /*……*/ }

次构造方法

我们可以在类体内通过constructor声明类的次构造方法

1
2
3
4
5
6
7
class KotlinClass constructor(name: String) {

var views: MutableList<View> = mutableListOf()
constructor(view: View, name: String) : this(name) {
views.add(view)
}
}

类有主构方法的时候,每个次构造方法都要委托给主构造方法处理

初始化代码块中的代码是主构造方法的一部分,所以初始化代码会在次构造方法执行前执行【init代码块】

继承与覆盖

和Java不同,Kotlin中所有类都默认为final,如果他需要被继承,我们需要使用open声明

1
2
3
4
5
6
// 打开继承
open class Animal(age:Int) {
init {
println(age)
}
}

在Kotlin中所有类都有共同的超类AnyAny有三个方法equals(),hashCode(),toString()

在Kotlin中继承用:如需继承一个类,请在类头中把超类放到冒号之后:

1
2
//派生类有柱构造方法的情况 
class Dog(age: Int) : Animal(age)

如果派生类有一个主构造方法,其基类必须用派生类主构造方法的参数初始化。

如果派生类没有主构造方法,那么每个次构造方法必须使用 super 关键字初始化其基类型。

1
2
3
4
//派生类无柱构造方法的情况 
class Cat : Animal {
constructor(age: Int) : super(age)
}

覆盖规则

主要是两种,覆盖方法和覆盖属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 打开继承
open class Animal(age:Int) {
// 属性允许被覆盖
open val foot = 0
init {
println(age)
}

open fun eat() {

}
}
// 继承
class Dog(age: Int) : Animal(age) {
//覆盖属性
override val foot = 4
// 覆盖方法
override fun eat() {

}
}

也就是说,无论是属性还是方法都是在类中不允许被覆盖的

必须显式的声明这些可以被覆盖,子类中也必须说明我们覆盖了这些

属性

属性的声明

Kotlin类的属性可以用关键字var声明可变,也可以用关键字val声明为可读的

GettersSetters

声明一个属性的完整语法是

1
2
3
var <propertyName>[: <PropertyType>] [= <property_initializer>] 
[<getter>]
[<setter>]

其中初始器,gettersetter都是可选的。

如果属性类型可以从初始器(或者getter)中推断出来,可以省略属性类型

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
// 继承
class Dog(age: Int) : Animal(age) {
var simple: Int?
get() {
println("get")
return 1
}
set(value) {}

//覆盖属性
override val foot = 4
// 覆盖方法
override fun eat() {
println("simple:${simple}")
}
}
//运行
fun main() {
var dog = Dog(2)
dog.eat()
println("==============")
var simple = dog.simple
}
//----------运行结果------------
/*
2
get
simple:1
==============
get
*/

我们需要知道gettersetter的特点

定义了getter,每次访问属性的时候都会调用它

定义了setter,每次赋值都会调用它

延迟初始化属性

通常属性声明为非空类型必须在构造方法中初始化。

然后这不利于依赖注入来初始化或者单元测试的setup方法初始化

为了处理这种情况,可以使用lateinit来延迟初始化

1
2
3
4
5
6
7
8
9
10
11
12
data class Shop(val name: String, val location: String)
class Test {
lateinit var shop: Shop
fun setup() {
shop = Shop("杂货铺", "深圳")
}
fun eat() {
if(::shop.isInitialized) {
println(shop.location)
}
}
}

在这种延迟初始化方式中,我们在未初始化的情况下去访问属性的时候会抛出异常

我们可以通过属性的if(::shop.isInitialized)来检测

抽象类与接口

抽象类

1
2
3
4
5
6
7
8
9
abstract class Printer {
abstract fun print()
}
class FilePrinter : Printer() {
override fun print() {
TODO("Not yet implemented")
}

}

接口

Kotlin中的接口即可包含抽象方法的声明,也可包含实现。

与抽象类不同的是,接口无法保存状态,它可以由属性,但是必须把声明为抽象或提供访问器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Study {
var time :Int
fun address()
fun earningCourses() {
println("架构师")
}
}

class StudyAs(override var time: Int) : Study{

override fun address() {
TODO("Not yet implemented")
}

}

数据类

数据类指的是我们只保存数据的一些类

1
2
3
4
5
6
data class Address(val name: String, val number: Int) { 
var city: String = ""
fun print() {
println(city)
}
}

数据类的要求:

主构造方法需要至少一个参数

主构造方法都需要标记为val var

对象表达式与对象声明

Kotlin提供对象表达式来方面我们需要对一个类轻微改动并创建它的对象,而不是为之显式声明新的子类。

对象表达式

要创建一个继承自某个类型的匿名类的对象,我们会这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open class Address2(name: String) {
open fun print() {

}
}
class Shop2 {
var address: Address2? = null
fun addAddress(address: Address2) {
this.address = address
}
}
fun test3() {
Shop2().addAddress(object :Address2("Android"){
override fun print() {
super.print()
}
})
}

匿名对象可以用作只在本地和私有作用域中声明的类型

对象声明是指我们把class变为object,变成了对象声明

1
2
3
4
5
6
7
8
object DataUtil { 
fun isEmpty(list: ArrayList?): Boolean {
return list?.isEmpty() ?: false
}
}
fun testDataUtil() {
val list = arrayListOf("1") println(DataUtil.isEmpty(list))
}

注意看,这里是我们使用object来使得这个变为静态的

我们调用方法不用实例化