kotlin基础 变量 变量使用var
声明,变量会自动进行类型推断,也可使用冒号显示声明变量类型。常量使用val
声明。
常见数据类型包括:
Byte
Short
Int
Long
Float
Double
String1 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 ;
应当优先使用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 } fun fun5 (x: Int ) = { y: Int -> x + y }
因为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" )
构造方法参数名前的val
和var
其实表示在类内创建同名属性。
构造方法的参数只能在初始化类内属性成员或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} " ) } }
延迟初始化 类内属性必须立即初始化,除非显式延迟初始化。
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.PUBLICATION
,LazyThreadSafetyMode.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); } 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) = pairval (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)
除此之外还有!is
、as?
等运算符。
Kotlin实现可空参数的方式是@Nullable
加if..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