kotlin

kotlin基础

变量

变量使用var声明,变量会自动进行类型推断,也可使用冒号显示声明变量类型。常量使用val声明。

常见数据类型包括:

  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double
  • String
    1
    2
    3
    var intNum = 3;
    var byteNum:Byte = 3;
    val PI = 3.1415926;

val指的是varible+final,即引用不可变。但声明和初始化赋值可以分离。

1
2
val l = intArrayOf(1, 2, 3);
l[0] = 2;
1
2
val a: Int;
a = 1;

应当优先使用val、不可变对象及纯函数来设计程序

函数

函数基础

1
2
3
fun main(args: Array<String>) {
println("Hello World!")
}

fun声明函数,main()函数是程序的入口。

1
2
3
fun 函数名(参数名:参数类型):返回类型{
函数体
}

表达式作为函数体,返回类型自动推断(在没有递归的情形),当然也可以显式声明:

1
fun add(a: Int, b: Int) = a + b

无返回值使用Unit。没有声明返回类型的函数会被当做无返回值。

1
2
3
fun sayHello(name:String):Unit{
println("大家好我叫${name}");
}

函数参数化

函数类型由()->引导,括号内为参数类型,箭头后为返回值类型。不能省略Unit。

使用函数作为参数:

1
2
3
4
5
6
7
8
9
fun filterStudent(students: List<Student>, assertion:(Student)->Boolean):List<Student>{
val res = mutableListOf<Student>()
for (s in students){
if (assertion(s)) {
res.add(s)
}
}
return res
}

可以使用匿名函数:

1
filterStudent(students, fun(student: Student): Boolean { return student.age == 100 })

lambda表达式由{}包裹,由->引导返回值。在指定lambda表达式类型时可以忽略函数内变量的类型声明;在声明函数内变量类型后可以自动推导lambda表达式类型。

函数只有花括号时,是代码块函数体,如果有返回值必须带return

1
2
fun fun1(x: Int) { print(x) }
fun fun2(x:Int, y:Int): Int { return x + y }

当没有等号也没有花括号时,是单表达式函数体,可以省略return

1
fun fun3(x:Int, y:Int) = x + y

= { ... }形式是lambda表达式,参数在lambda表达式内部声明。所以如果前用fun声明,得到的就是lambda表达式函数体,必须用invoke()()来调用。

1
2
val fun4 = { x: Int, y: Int -> x + y } // fun4(1, 2)
fun fun5(x: Int) = { y: Int -> x + y } // fun5(1)(2)

因为fun5其实相当于返回lambda表达式:

1
2
3
fun fun5(x: Int): (Int) -> (Int) {
return { y: Int -> x + y }
}
1
2
3
4
fun foo(x: Int) = { print(x) }
fun bar(x: Int) = run { print(x) }
listOf(1, 2, 3).forEach { foo(it)() }
listOf(1, 2, 3).forEach { bar(it) }

当函数的最后一个参数是函数时,调用时可以将{}放在大括号外。如果仅有一个参数且为函数,调用时可以省略括号。

面向表达式编程

表达式语句

简略来说,表达式是有返回值的语句。在Kotlin中,if、try/catch/finally、函数体、lambda等都是表达式。

语句的作用就是服务于创建副作用(修改外部变量)的,而原则上的函数式编程,表达式不允许包含副作用。

Unit类型用以替代Java中的void,返回Unit类型表示没有返回值,让函数调用皆为表达式。

枚举类

1
2
3
4
5
6
7
8
9
enum class Day {
MON,
TUE,
WEN,
THU,
FRI,
SAT,
SUN
}

注意在枚举属性和枚举方法之间必须要有一个分号来区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum class DayOfWeek(val day: Int) {
MON(1),
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7)
;

fun getDayNumber(): Int {
return day
}
}

when

1
2
3
4
5
6
7
8
fun schedule(sunny: Boolean, day: Day): String = when (day) {
Day.SAT -> "play game"
Day.SUN -> "finshing"
else -> when {
sunny -> "outdoor"
else -> "study"
}
}

如果没有声明when所判断的对象,when会以->左侧的逻辑值来进行选择。

1
2
3
4
5
6
fun schedule(sunny: Boolean, day: Day): String = when {
day == Day.SAT -> "play game"
day == Day.SUN -> "finshing"
sunny -> "outdoor"
else -> "study"
}

区间表达式

区间表达式的用法:

1
2
3
for (i in 1..10) {
println(i)
}

infix表达式

infix表达式是类似于运算符的函数调用,也可以像普通函数一样调用:

1
2
3
4
5
6
7
8
9
10
class Person {
infix fun called(name: String) {
println("My name is ${name}.")
}
}

fun main(args: Array<String>) {
val p = Person()
p called "Steve"
}

map常用如下定义:

1
2
3
4
5
mapOf(
1 to "one",
2 to "two",
3 to "three"
)
1
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

字符串

字符串是不可变对象。生字符串使用三引号来引导。

字符串模板用$引导。

1
2
3
fun sayHello(name:String):String{
return "大家好我叫${name}";
}

面向对象

类和对象

1
2
3
4
5
class Student {
val name: String = "Steve"
val age: Int = 13
fun run() {}
}

Kotlin中的类,除非显式声明延迟初始化,否则必须指定默认值。

1
2
3
4
5
6
interface flyable {
val speed: Int
fun fly() {
println("I can fly.")
}
}

Kotlin的接口通过方法来实现属性,所以不支持直接赋值常量,需要用get()

1
2
3
4
interface Flyable {
val height
get() = 1000
}

构造方法

构造方法

支持构造方法默认参数,来减少Java中的构造函数重载带来的麻烦。

1
class Bird(val age: Int = 1, val color: String = "blue")
1
val bird = Bird(color = "black")

构造方法参数名前的valvar其实表示在类内创建同名属性。

构造方法的参数只能在初始化类内属性成员或init语句块中调用。可以有多个init语句块,它们将会由上而下执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Bird(maybeWeight: Int, val color: String) {
val weight: Int = if (maybeWeight > 0) maybeWeight else 0

init {
println("do some other thing")
println("the weight is ${maybeWeight}")
}

// Unresolved reference: maybeWeight
// fun printWeight() {
// println(maybeWeight)
// }
}

延迟初始化

类内属性必须立即初始化,除非显式延迟初始化。

1
2
3
class Person(val name: String, val age: Int) {
val isAdult: Boolean by lazy(LazyThreadSafetyMode.NONE) { age >= 18 }
}

lazy接受其后的lambda,返回一个Lazy<T>。第一次访问属性时执行lambda并记录结果,以后只是返回记录的结果。因此,by lazy只用于常量,赋值后不再更改。

lazy默认有同步锁(LazyThreadSafetyMode.SYNCHRONIZED),并行模式(LazyThreadSafetyMode.PUBLICATIONLazyThreadSafetyMode.NONE将不会有任何线程开销。

var应该使用lateinit

1
2
3
4
5
6
7
8
class Person(val name: String, val age: Int) {
lateinit var sex: String

fun printSex() {
this.sex = if (this.name == "Steve") "male" else "female"
println(this.sex)
}
}
1
2
3
4
5
6
7
8
class Person(val name: String, val age: Int) {
lateinit var sex: String

fun printSex() {
this.sex = if (this.name == "Steve") "male" else "female"
println(this.sex)
}
}

主从构造方法

从构造方法通过constructor定义,用于接收特殊的数据来获取构造类的参数值。每个类最多有一个主构造方法和多个从构造方法,从构造方法直接或间接地委托主构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Bird(age: Int) {
val age: Int

init {
this.age = age
}

constructor(birth: DateTime) : this(getAgeByBirth(birth))
constructor(timestamp: Long) : this(DateTime(timestamp))

}

fun getAgeByBirth(birth: DateTime): Int {
return Years.yearsBetween(birth, DateTime.now()).years
}

访问控制

Kotlin默认类不允许继承,方法不允许重写,即final,需要用open允许继承。

密封类只允许在本文件继承,不能初始化,因为内部实现是一个抽象类。

1
2
3
4
sealed class Bird {
open fun fly() = "I can fly"
class Eagle : Bird()
}

Kotlin默认类是public的。protected表示类及子类内可见;private在类内表示本类可见,在类外表示本文件可见;internal表示模块内可见。一个模块表示一起编译的Kotlin文件。

继承

Kotlin使用单冒号来继承。Kotlin只支持单继承。

多继承接口时,必须使用override实现接口属性和方法,可以用super<T>调用接口的默认方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Flyable{
fun fly()
fun kind() = "flyable"
}

interface Runable{
fun run()
fun kind() = "runable"
}

class Bird(val name:String):Flyable , Runable{
override fun fly() {
println("bird can fly")
}

override fun run() {
println("bird can run")
}

override fun kind(): String {
return super<Flyable>.kind()
}
}

使用inner声明内部类,使用内部类来解决多继承问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
open class Horse {
fun runFast() {
println("I can run fast")
}
}

open class Donkey {
fun doLongTimeThing() {
println("I can do some thing long time")
}
}

class Mule {
private inner class HorseC : Horse()
private inner class DonkeyC : Donkey()

fun runFast() {
HorseC().runFast()
}

fun doLongTimeThing() {
DonkeyC().doLongTimeThing()
}
}

by用于委托,可以替代多继承。

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
interface Flyable {
fun fly()
}

interface Runable {
fun run()
}

class Flyer : Flyable {
override fun fly() {
println("I can fly")
}
}

class Runer : Runable {
override fun run() {
println("I can run")
}
}

class Bird(flyer: Flyer, runer: Runer) : Flyable by flyer, Runable by runer {}

fun main(args: Array<String>) {
val flyer = Flyer()
val runer = Runer()
val bird = Bird(flyer, runer)
bird.fly()
bird.run()
}

数据类型

data class声明数据类,Kotlin会自动生成getter/setter,equals、hashCode、构造方法等函数。

1
data class Bird(var weight: Double, var age: Int, var color: String)

反编译如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public final class Bird {
private double weight;
private int age;
@NotNull
private String color;

public final double getWeight() {
return this.weight;
}

public final void setWeight(double var1) {
this.weight = var1;
}

public final int getAge() {
return this.age;
}

public final void setAge(int var1) {
this.age = var1;
}

@NotNull
public final String getColor() {
return this.color;
}

public final void setColor(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.color = var1;
}

public Bird(double weight, int age, @NotNull String color) {
Intrinsics.checkNotNullParameter(color, "color");
super();
this.weight = weight;
this.age = age;
this.color = color;
}

public final double component1() {
return this.weight;
}

public final int component2() {
return this.age;
}

@NotNull
public final String component3() {
return this.color;
}

@NotNull
public final Bird copy(double weight, int age, @NotNull String color) {
Intrinsics.checkNotNullParameter(color, "color");
return new Bird(weight, age, color);
}

// $FF: synthetic method
public static Bird copy$default(Bird var0, double var1, int var3, String var4, int var5, Object var6) {
if ((var5 & 1) != 0) {
var1 = var0.weight;
}

if ((var5 & 2) != 0) {
var3 = var0.age;
}

if ((var5 & 4) != 0) {
var4 = var0.color;
}

return var0.copy(var1, var3, var4);
}

@NotNull
public String toString() {
return "Bird(weight=" + this.weight + ", age=" + this.age + ", color=" + this.color + ")";
}

public int hashCode() {
long var10000 = Double.doubleToLongBits(this.weight);
int var1 = ((int)(var10000 ^ var10000 >>> 32) * 31 + this.age) * 31;
String var10001 = this.color;
return var1 + (var10001 != null ? var10001.hashCode() : 0);
}

public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Bird) {
Bird var2 = (Bird)var1;
if (Double.compare(this.weight, var2.weight) == 0 && this.age == var2.age && Intrinsics.areEqual(this.color, var2.color)) {
return true;
}
}

return false;
} else {
return true;
}
}
}

copy()用于浅拷贝。如果数据类对象属性不可变,可以在拷贝时创建新对象。如果数据类对象属性可变,拷贝后要注意引用修改问题。

componentN()用于结构,所以你可以写出这样的代码:

1
2
val bird = Bird(100.0, 10, "blue")
val (weight, age, color) = bird

数组也可以解构:

1
2
 val birdInfo = "20.0,1,bule"
val (weight, age, color) = birdInfo.split(",")

对组和三元组的解构:

1
2
3
4
val pair = Pair(100.0, 1)
val triple = Triple(100.0, 1, "blue")
val (weightP, ageP) = pair
val (weightT, ageT, colorT) = triple

object

伴生对象

伴生对象是从属于类的单例,用以替代Java中的静态成员。

1
2
3
4
5
6
7
8
9
class Prize(val name: String, count: Int, val type: Int) {
companion object {
val TYPE_REDPACK = 0
val TYPE_COUPON = 1
fun isRedpack(prize: Prize): Boolean {
return prize.type == TYPE_REDPACK
}
}
}

object单例

object是天生的单例

1
2
3
4
5
6
object DatabaseConfig {
var host: String = "127.0.0.1"
var port: Int = 3306
var username: String = "root"
var password: String = ""
}

object表达式

object表达式用于替代匿名内部类。

1
2
3
4
5
6
7
8
9
val absComparator = object : Comparator<Int> {
override fun compare(o1: Int?, o2: Int?): Int {
if (o1 == null)
return -1
if (o2 == null)
return 1
return abs(o1) - abs(o2)
}
}
1
Collections.sort(l, absComparator)

也可以用lambda表达式,在内部类方法较多时用object表达式比较合适。

1
2
3
4
5
6
7
val absComparator = Comparator<Int> { o1, o2 ->
if (o1 == null)
return@Comparator -1
if (o2 == null)
return@Comparator 1
abs(o1).compareTo(abs(o2))
}

代数数据类型和模式匹配

积类型和和类型

代数数据类型是由其他数据类型组合得来的类型,常见的包括积类型和和类型。

1
class BooleanProductUnit(a: Boolean, b: Unit) {}

除了枚举外,可以用密封类来创建和类型。

1
2
3
4
5
6
7
8
9
sealed class Day {
class SUN : Day()
class MON : Day()
class TUE : Day()
class WEN : Day()
class THU : Day()
class FRI : Day()
class SAT : Day()
}

和类型 好处在于类型安全,when可以直接省略else

1
2
3
4
5
6
7
8
9
fun schedule(day: Day): Unit = when (day) {
is Day.SUN -> study()
is Day.MON -> study()
is Day.TUE -> study()
is Day.WEN -> study()
is Day.THU -> study()
is Day.FRI -> study()
is Day.SAT -> study()
}

模式匹配

模式匹配本质上是匹配表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 常量匹配
fun constantPattern(a: Int) = when (a) {
1 -> "It is 1"
2 -> "It is 2"
else -> "It is other number"
}

// 类型匹配
sealed class Shape {
class Circle(val radius: Double) : Shape()
class Rectangle(val width: Double, val height: Double) : Shape()
}

fun getArea(shape: Shape) = when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rectangle -> shape.height * shape.width
}

// 逻辑表达式匹配
fun isAdult(age: Int) = when {
age in 0..18 -> (age.toString() + "岁尚未成年")
else -> "应该成年了"
}

对于一个用于记录整数四则运算的表达式的嵌套类:

1
2
3
4
sealed class Expr {
data class Num(val value: Int) : Expr()
data class Operate(val opName: String, val left: Expr, val right: Expr) : Expr()
}

现在的业务需求是化简任何expr+0为expr:

1
2
3
4
5
fun simplifyExpr(expr: Expr): Expr = when {
expr is Expr.Operate && expr.opName == "+" && expr.left is Expr.Num && expr.left.value == 0 -> expr.right
expr is Expr.Operate && expr.opName == "+" && expr.right is Expr.Num && expr.right.value == 0 -> expr.left
else -> expr
}

可以利用模式匹配化简如下:

1
2
3
4
5
6
7
8
fun simplifyExpr(expr: Expr) = when (expr) {
is Expr.Num -> expr
is Expr.Operate -> when (expr) {
Expr.Operate("+", expr.left, Expr.Num(0)) -> expr.left
Expr.Operate("+", Expr.Num(0), expr.right) -> expr.right
else -> expr
}
}

Kotlin还没有完全支持模式匹配,有些功能还不是非常强大。

增强Kotlin的模式匹配

类型测试/类型转换

在对类型进行测试后,可以直接当做该类型使用,因为Kotlin支持Smart Casts。

1
expr.left is Expr.Num && expr.left.value == 0

面向对象的分解

上述复杂的判断可以定义为函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sealed class Expr {
abstract fun isZero(): Boolean
abstract fun isAddZero(): Boolean
abstract fun left(): Expr
abstract fun right(): Expr

data class Num(val value: Int) : Expr() {
override fun isZero(): Boolean = this.value == 0
override fun isAddZero(): Boolean = false
override fun left(): Expr = throw Throwable("no element")
override fun right(): Expr = throw Throwable("no element")

}

data class Operate(val opName: String, val left: Expr, val right: Expr) : Expr() {
override fun isZero(): Boolean = false
override fun isAddZero(): Boolean = this.opName == "+" && (this.left.isZero() || this.right.isZero())
override fun left(): Expr = this.left
override fun right(): Expr = this.right
}
}

代码虽然得到简化,但是类结构变得臃肿。如果业务比较简单,后期数据结构也不会发生大的变化可以使用这种方式。

访问者设计模式

为类赋予访问者,在不改变类的条件下,定义作用于对象的新操作。

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
sealed class Expr {
abstract fun isZero(v: Visitor): Boolean
abstract fun isAddZero(v: Visitor): Boolean
abstract fun simplifyExpr(v: Visitor): Expr

data class Num(val value: Int) : Expr() {
override fun isZero(v: Visitor): Boolean = v.matchZero(this)
override fun isAddZero(v: Visitor): Boolean = v.matchAddZero(this)
override fun simplifyExpr(v: Visitor): Expr = v.doSimplifyExpr(this)
}

data class Operate(val opName: String, val left: Expr, val right: Expr) : Expr() {
override fun isZero(v: Visitor): Boolean = v.matchZero(this)
override fun isAddZero(v: Visitor): Boolean = v.matchAddZero(this)
override fun simplifyExpr(v: Visitor): Expr = v.doSimplifyExpr(this, v)
}
}

class Visitor {
fun matchAddZero(expr: Expr.Num): Boolean = false
fun matchZero(expr: Expr.Num): Boolean = expr.value == 0
fun doSimplifyExpr(expr: Expr.Num): Expr = expr
fun matchAddZero(expr: Expr.Operate): Boolean = when (expr) {
Expr.Operate("+", expr.left, Expr.Num(0)) -> true
Expr.Operate("+", Expr.Num(0), expr.right) -> true
else -> false
}
fun matchZero(expr: Expr.Operate): Boolean = false
fun doSimplifyExpr(expr: Expr.Operate, v: Visitor): Expr = when {
expr.right is Expr.Num && expr.right.isZero(v) -> expr.left
expr.left is Expr.Num && expr.left.isZero(v) -> expr.right
else -> expr
}
}

访问者设计模式将类的方法放到类的外部,可以减少许多类型判断的代码,只在指定的子类进行操作使逻辑变得轻巧。

访问者设计模式不便于后期维护,只有在数据结构不会有太大的改变,以及业务逻辑相对比较复杂时使用。

类型系统

可空类型

?表示允许空值的类型:

1
2
data class Seat(val student: Student?)
data class Student(val name: String, val age: Int)

?.表示安全调用:

1
println("座位上坐的学生是${s.student?.name}")

?:被称为Elvis操作符,指定空变量的返回值,类似三目运算符。

1
println("座位上的学生年龄是${s.student?.age ?: -1}")

!!.为非空断言,如果变量为空则会抛出NPE异常。

1
println(s.student!!.name)

除此之外还有!isas?等运算符。

Kotlin实现可空参数的方式是@Nullableif..else判断。这么做应该是为了在最好性能的前提下兼容Java。

可以用Elvis运算符抛出异常:

1
println("座位上的学生年龄是${s.student?.age?: throw NullPointerException()}")

如果实现一个Either类,可以做出:

1
2
3
4
5
6
7
8
sealed class Either<A, B>() {
class Left<A, B>(val Value: A) : Either<A, B>()
class Right<A, B>(val Value: B) : Either<A, B>()
}

fun getName(seat: Seat?): Either<Error, String> {
return seat?.student?.let { Either.Right<Error, String>(it.name) } ?: Either.Left<Error, String>(Error())
}

其中let将自己(it)作为其后的表达式参数,进行运算。


article_txt
目录