Kotlin知识点总结与初写时的一些建议
本文是在学习和使用kotlin时的一些总结与体会,一些代码示例来自于网络或Kotlin官方文档,持续更新...
对象相关
-
对象表达式:相当于Java匿名类部类,在使用的地方被立即执行:
val a = 10 val listener = object : Info("submit"),IClickListener { override fun doClick() { println("a:$a") } } listener.doClick() // 打印 a:10 //有时候我们只是需要一个没有父类的对象,我们可以这样写: val adHoc = object { var x: Int = 0 var y: Int = 0 } print(adHoc.x + adHoc.y) //像 java 的匿名内部类一样,对象表达式可以访问闭合范围内的变量 (和 java 不一样的是,这些变量不用是 final 修饰的) fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent){ enterCount++ } }) }
-
对象申明:Kotlin 中我们可以方便的通过对象声明来获得一个单例,对象声明是延迟加载的, 在第一次使用的时候被初始化,对象声明不是一个表达式,不能用在赋值语句的右边,对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中,
object MyInfo: Info("submit"),IClickListener { override fun doClick() { println("MyInfo do click, $text") // Log: MyInfo do click, , submit } } fun main(args: Array<String>) { MyInfo.doClick() } //当对象声明在另一个类的内部时,这个类的实例并不能直接访问对象申明内部,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量 class Site { var name = "菜鸟教程" object DeskTop{ var url = "www.runoob.com" fun showName(){ print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量 } } } fun main(args: Array<String>) { var site = Site() site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象 Site.DeskTop.url // 正确 }
-
伴随(生)对象:相当于静态内部类+该类的静态属性,所在的类被加载,伴生对象被初始化(和 Java 的静态初始是对应):
class Books(var name: String, val page: Int) { companion object ComBooks{ val a : Int = 10 fun doNote() { println("do note") } } } fun main(args: Array<String>) { Books.ComBooks.doNote() println("Book.a = ${Books.ComBooks.a}") println("-------------") Books.doNote() } // Log do note Book.a = 10 ------------- do note //伴随对象的成员可以通过类名做限定词直接使用: class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } val instance = MyClass.create() //在使用了 companion 关键字时,伴随对象的名字可以省略: class MyClass { companion object { } } //尽管伴随对象的成员很像其它语言中的静态成员,但在运行时它们任然是真正类的成员实例,比如可以实现接口: interface Factory<T> { fun create(): T } class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() } } //如果你在 JVM 上使用 @JvmStatic 注解,你可以有多个伴随对象生成为真实的静态方法和属性
属性字段相关
-
备用字段:Kotlin中不能有field,但在自定义getter/setter的时候需要直接访问属性而不是又通过getter/settter来取值赋值(循环调用)。Kotlin自动提供一个备用字段(field),通过它可以直接访问属性,没有使用备用字段时不生成备用字段(使用了setter就会生成),:
//使用field关键字 public var fieldProp = "" get() = field set(value) { field = value; } //不生成: val isEmpty: Boolean get() = this.size == 0 //生成: val Foo.bar = 1
-
备用属性:功能与备用字段类似。:
private var _table: Map<String, Int>? = null public val table: Map<String, Int> get() { if (_table == null) { _table = HashMap() // 参数类型是自动推导 } return _table ?: throw AssertionError("Set to null by another thread") }
Kotlin可以像python(@property)一样把方法变成属性调用,Kotlin是定义一个属性复写get()方法返回某个对象中其他的计算出来的值。
编译时常量
-
相当于java static finial xxx,而val 只是fInal ,一个编译时常量,一个运行时常量。使用const必须:
- 在kt文件中(类之外,Top-level)或在object{}中
- 必须是基本类型或String
- 必须没有自定义getter
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprected(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延迟初始化属性
-
当定义属性时没有使用 ? ,那么说明是一个非空属性,这时必须要初始化,如果想在后面的方法中再去赋值要加上lateinit。:
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } } //这个修饰符只能够被用在类的 var 类型的可变属性定义中,不能用在构造方法中.并且属性不能有自定义的 getter 和 setter访问器.这个属性的类型必须是非空的,同样也不能为一个基本类型.在一个lateinit的属性初始化前访问他,会导致一个特定异常,告诉你访问的时候值还没有初始化
复写属性
- 属性可复写,在主构造函数中就可使用override关键字作为属性声明。
代理(委托)模式
-
类代理:
Kotlin 在语法上支持代理 ,Derived 类可以继承 Base 接口并且指定一个对象代理它全部的公共方法:interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { printz(x) } } class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() } //在 Derived 的父类列表中的 by 从句会将 b 存储在 Derived 内部对象,并且编译器会生成 Base 的所有方法并转给 b
-
代理属性:
所谓的委托属性,就是对其属性值的操作不再依赖于其自身的getter()/setter()方法,是将其托付给一个代理类,从而每个使用类中的该属性可以通过代理类统一管理,再也不用在每个类中,对其声明重复的操作方法。语法:val/var <property name>: <Type> by <expression> //var/val:属性类型(可变/只读) //name:属性名称 //Type:属性的数据类型 //expression:代理类
使用场景:
- 延迟加载属性(lazy property): 属性值只在初次访问时才会计算
- 可观察属性(observable property): 属性发生变化时, 可以向监听器发送通知
- 将多个属性保存在一个 map 内, 而不是保存在多个独立的域内
Kotlin标准库中已实现的代理:
-
延迟加载(Lazy):lazy()是一个函数, 接受一个Lambda表达式作为参数, 返回一个Lazy类型的实例,这个实例可以作为一个委托, 实现延迟加载属性(lazy property): 第一次调用 get() 时, 将会执行 lazy() 函数受到的Lambda 表达式,然后会记住这次执行的结果, 以后所有对 get() 的调用都只会简单地返回以前记住的结果:
val no: Int by lazy { 200 } val c = 200 fun main(args: Array<String>) { val b = 200 println(no) // Log : 200 println(no) // Log : 200 }
注意:
- var类型属性不能设置为延迟加载属性,因为在lazy中并没有setValue(…)方法
- lazy操作符是线程安全的。如果在不考虑多线程问题或者想提高更多的性能,也可以使用 lazy(LazyThreadSafeMode.NONE){ … },lazy的三个参数为:
- SYNCHRONIZED:锁定,用于确保只有一个线程可以初始化[Lazy]实例。
- PUBLICATION:初始化函数可以在并发访问未初始化的[Lazy]实例值时调用几次,,但只有第一个返回的值将被用作[Lazy]实例的值。
- NONE:没有锁用于同步对[Lazy]实例值的访问; 如果从多个线程访问实例,是线程不安全的。此模式应仅在高性能至关重要,并且[Lazy]实例被保证永远不会从多个线程初始化时使用。
-
可观察属性(Observable):Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler).这种形式的委托,采用了观察者模式,其会检测可观察属性的变化,当被观察属性的setter()方法被调用的时候,响应器(handler)都会被调用(在属性赋值处理完成之后)并自动执行执行的lambda表达式,同时响应器会收到三个参数:被赋值的属性, 赋值前的旧属性值, 以及赋值后的新属性值。:
var name: String by Delegates.observable("wang", { kProperty, oldName, newName -> println("kProperty:${kProperty.name} | oldName:$oldName | newName:$newName") }) fun main(args: Array<String>) { println("name: $name") // Log:nam:wang name = "zhang" // Log:kProperty:name | oldName:wang | newName:zhang name = "li" // Log:kProperty:name | oldName:zhang | newName:li } //Delegates.observable(wang, hanler),完成了两项工作,一是,将name初始化(name=wang);二是检测name属性值的变化,每次变化时,都会打印其赋值前的旧属性值, 以及赋值后的新属性值。 ```
-
Vetoable:Delegates.vetoable()函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler),是可观察属性(Observable)的一个特例,不同的是在响应器指定的自动执行执行的lambda表达式中在保存新值之前做一些条件判断,来决定是否将新值保存。:
var name: String by Delegates.vetoable("wang", { kProperty, oldValue, newValue -> println("oldValue:$oldValue | newValue:$newValue") newValue.contains("wang") }) fun main(args: Array<String>) { println("name: $name") println("------------------") name = "zhangLing" println("name: $name") println("------------------") name = "wangBing" println("name: $name") } //Log name: wang ------------------ oldValue:wang | newValue:zhangLing name: wang ------------------ oldValue:wang | newValue:wangBing name: wangBing ```
-
Not Null:在实际开发时,我们可能会设置可为null的var类型属性,在我们使用它时,肯定是对其赋值,假如不赋值,必然要报NullPointException.一种解决方案是,我们可以在使用它时,在每个地方不管是不是null,都做null检查,这样我们就保证了在使用它时,保证它不是null。这样无形当中添加了很多重复的代码。在Kotlin中,用委托可以不用去写这些重复的代码,Not Null委托会含有一个可null的变量并会在我们设置这个属性的时候分配一个真实的值。如果这个值在被获取之前没有被分配,它就会抛出一个异常。
class App : Application() { companion object { var instance: App by Delegates.notNull() } override fun onCreate() { super.onCreate() instance = this } }
-
将多个属性保存在一个map内:使用Gson解析Json时,可以获取到相应的实体类的实例,当然该实体类的属性名称与Json中的key是一一对应的。在Kotlin中,存在这么一种委托方式,类的构造器接受一个map实例作为参数,将map实例本身作为属性的委托,属性的名称与map中的key是一致的,也就是意味着我们可以很简单的从一个动态地map中创建一个对象实例:
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } fun main(args: Array<String>) { val user = User(mapOf( "name" to "John Doe", "age" to 25 )) println(user.name) // 打印结果为: "John Doe" println(user.age) // 打印结果为: 25 } //委托属性将从这个 map中读取属性值(使用属性名称字符串作为 key 值)。 //如果不用只读的 Map , 而改用值可变的 MutableMap , 那么也可以用作 var 属性的委托。: class User(val map: MutableMap<String, Any?>) { val name: String by map val age: Int by map } fun main(args: Array<String>) { var map:MutableMap<String, Any?> = mutableMapOf( "name" to "John Doe", "age" to 25) val user = User(map) println(user.name) // 打印结果为: "John Doe" println(user.age) // 打印结果为: 25 println("--------------") map.put("name", "Green Dao") map.put("age", 30) println(user.name) // 打印结果为: Green Dao println(user.age) // 打印结果为: 30 }
-
属性委托的前提条件:getValue(),setValue()。自定义委托必须要实现:ReadOnlyProperty和ReadWriteProperty。取决于我们被委托的对象是val还是var,如:
public interface ReadWriteProperty<in R, T> { public operator fun getValue(thisRef: R, property: KProperty<*>): T public operator fun setValue(thisRef: R, property: KProperty<*>, value: T) } //定义一个NotNullVar private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> { private var value: T? = null public override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.") } public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } } //第一个thisRef表示持有该对象的对象, //第二个参数 property 是该值的类型, //第三个参数 value 就是属性的值了
密封类
-
密封类:类的扩展,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例,虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明,间接的子类不受限制。密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。密封类不允许有非-private 构造函数(其构造函数默认为 private)。使用密封类的关键好处在于使用 when 表达式 的时候,能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了:
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr() fun eval(expr: Expr): Double = when(expr) { is Const -> expr.number is Sum -> eval(expr.e1) + eval(expr.e2) NotANumber -> Double.NaN // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况 }
接口
-
与java8类似可有抽象方法与实现方法,不可保存状态,属性必须是抽象的或唯一值的(无备用属性)。
扩展
-
不需要在类中去添加方法,在外部就可以给任何地方的类添加我们想要的方法,替换掉java中的FileUtil,xxxUtil等如:
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list)) //变成 list.swap(list.binarySearch(otherList.max()), list.max())
扩展是被静态解析的:扩展实际上并没有修改它所扩展的类,只是让这个类的实例对象能够通过"."调用新的函数。需要强调的是扩展函数是静态分发的,举个例子,它们并不是接受者类型的虚拟方法。这意味着扩展函数的调用是由发起函数调用的表达式的(对象)类型决定的,而不是在运行时动态获得的表达式的(对象)类型决定。比如:
open class C class D: C() fun C.foo() = "c" fun D.foo() = "d" fun printFoo(c: C) { println(c.foo()) } printFoo(D()) //输出 c,因为这里扩展函数的调用决定于声明的参数 c 的类型,也就是 C。
如果有同名同参数的成员函数和扩展函数,调用的时候会使用成员函数,比如:
class C { fun foo() { println("member") } } fun C.foo() { println("extension") } C().foo() //输出"member",而不是"extension" //可以通过不同的函数签名的方式重载函数的成员函数: fun C.foo(i:Int) { println("extention") } C().foo(1) //输出 “extentions”。
扩展的域:大多数时候我们在 top level 定义扩展(就在包下面直接定义):
package foo.bar fun Baz.goo() { ... } //为了在除声明的包外使用这个扩展,我们需要在别的文件中使用时导入: //-------------------------------------------------// package com.example.usage import foo.bar.goo // 导入所有名字叫 "goo" 的扩展 // 或者 import foo.bar.* // 导入foo.bar包下得所有数据 fun usage(baz: Baz) { baz.goo() }
-
函数扩展:
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 } //this 关键字对应接收者对象(MutableList<T>) //使用: val l = mutableListOf(1, 2, 3) l.swap(0, 2)// 在 `swap()` 函数中 `this` 持有的值是 `l`
-
可空的接收者:
使用空接收者类型进行定义。这样的扩展使得,即使是一个空对象仍然可以调用该扩展,然后在扩展的内部进行 this == null 的判断。这样你就可以在 Kotlin 中任意调用 toString() 方法而不进行空指针检查:空指针检查延后到扩展函数中完成:fun Any?.toString(): String { if (this == null) return "null" // 在空检查之后,`this` 被自动转为非空类型,因此 toString() 可以被解析到任何类的成员函数中 return toString() }
T.所以扩展是用.来使用的,如Kotlin的to函数就是A to B 以空格使用。
-
属性扩展
注意,由于扩展并不会真正给类添加了成员属性,因此也没有办法让扩展属性拥有一个备份字段.这也是为什么初始化函数不允许有扩展属性。扩展属性只能够通过明确提供 getter 和 setter方法来进行定义:
//正确: val <T> List<T>.lastIndex: Int get() = size-1 //错误: val Foo.bar = 1 //error: initializers are not allowed for extension properties
-
伴随对象扩展
class MyClass { companion object {} } fun MyClass.Companion.foo(){ } //调用 MyClass.foo()
数据类
我们经常创建一个只保存数据的类。在这样的类中一些函数只是机械的对它们持有的数据进行,如从服务端返回的Json字符串对象映射成Java类。data类使用:
data class 类名(var param1 :数据类型,...){}
data class 类名 可见性修饰符 constructor(var param1 : 数据类型 = 默认值,...)
//data为声明数据类的关键字,必须书写在class关键字之前。
//在没有结构体的时候,大括号{}可省略。
//构造函数中必须存在至少一个参数,并且必须使用val或var修饰。这一点在下面数据类特性中会详细讲解。
//参数的默认值可有可无。(若要实例一个无参数的数据类,则就要用到默认值)
// 定义一个名为Person的数据类:
data class Preson(var name : String,val sex : Int, var age : Int)
data类必须满足的条件:
- 主构造函数需要至少有一个参数
- 主构造函数的所有参数需要标记为 val 或 var;
- 数据类不能是抽象、开放、密封或者内部的;
- 数据类是可以实现接口的,如(序列化接口),同时也是可以继承其他类的,如继承自一个密封类。
约定俗成的规定:当构造函数中的参过多时,为了代码的阅读性,一个参数的定义占据一行。
编辑器为我们做的事情:
- 生成equals()函数与hasCode()函数
- 生成toString()函数,由类名(参数1 = 值1,参数2 = 值2,....)构成
- 由所定义的属性自动生成component1()、component2()、...、componentN()函数,其对应于属性的声明顺序。
- copy()函数。修改部分属性,但是保持其他不变。
copy函数的使用:
data class User(val name : String, val pwd : String)
val mUser = User("kotlin","123456")
println(mUser)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)
标准库提供的data类: Pair 和 Triple,源码如下:
@file:kotlin.jvm.JvmName("TuplesKt")
package kotlin
// 这里去掉了源码中的注释
public data class Pair<out A, out B>(
public val first: A,
public val second: B) : Serializable {
// toString()方法
public override fun toString(): String = "($first, $second)"
}
// 转换
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
// 转换成List集合
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)
// 这里去掉了源码中的注释
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C ) : Serializable {
// toString()方法
public override fun toString(): String = "($first, $second, $third)"
}
// 转换成List集合
public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)
泛型
-
两种型变:
-
协变:当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变;
-
逆变:当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变;
其余为不变。
协变,逆变,不变来原于子类可以安全的向上转型为父类。
-
-
不变(java的泛型<T>是不变的):
ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch
-
协变:
List<? extends Number> list001 = new ArrayList<Integer>(); List<? extends Number> list002 = new ArrayList<Float>(); Number n1=list001.get(0); Number n2=list002.get(0);
“? extends Number”则表示通配符”?”的上界为Number,换句话说就是,“? extends Number”可以代表Number或其子类,但代表不了Number的父类(如Object),因为通配符的上界是Number。于是有“? extends Number” ≦ Number,则List<? extends Number> ≦ List< Number >。但是这里不能向list001、list002添加除null以外的任意对象。可以这样理解一下,List<Integer>可以添加Interger及其子类,List<Float>可以添加Float及其子类,List<Integer>、List<Float>都是List<? extends Animal>的子类型,如果能将Float的子类添加到List<? extends Animal>中,就说明Float的子类也是可以添加到List<Integer>中的,显然是不可行。故java为了保护其类型一致,禁止向List<? extends Number>添加任意对象,不过却可以添加null。
-
逆变:
List<? super Number> list = new ArrayList<>(); List<? super Number> list001 = new ArrayList<Number>(); List<? super Number> list002 = new ArrayList<Object>(); list001.add(new Integer(3)); list002.add(new Integer(3));
“? super Number” 则表示通配符”?”的下界为Number。为了保护类型的一致性,因为“? super Number”可以是Object或其他Number的父类,因无法确定其类型,也就不能往List<? super Number >添加Number的任意父类对象。但是可以向List<? super Number >添加Number及其子类。
-
PECS(《Effective Java》,producer-extends, consumer-super):协变只能取(生产者),逆变只能写(消费者),如java的几个api:
public class Stack<E>{ public Stack(); public void push(E e): public E pop(); public boolean isEmpty(); } //push all // Wildcard type for parameter that serves as an E producer public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); } //pop all // Wildcard type for parameter that serves as an E consumer public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); } // java.util.Collections的copy方法 public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
-
Kotlin 用out ,in修饰符使协变与逆变使用起来更方便,out表示只生成,in表示只消费,称之为声明处变型。这与 Java 中的使用处变型相反:
abstract class Source<out T> { abstract fun nextT(): T } fun demo(strs: Source<String>) { val objects: Source<Any> = strs // This is OK, since T is an out-parameter // ... } //-------------------------// abstract class Comparable<in T> { abstract fun compareTo(other: T): Int } fun demo(x: Comparable<Number>) { x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number // Thus, we can assign x to a variable of type Comparable<Double> val y: Comparable<Double> = x // OK! }
-
类型投影
使用处变型:类型投影。有些类 不能 限制它只返回 T,如Array,T既要返回又要在参数中消费:
class Array<T>(val size: Int) { fun get(index: Int): T { /* ... */ } fun set(index: Int, value: T) { /* ... */ } } //这个类既不能是协变的也不能是逆变的,这会在一定程度上降低灵活性。考虑下面的函数形式: fun copy(from: Array<out Any>, to: Array<Any>) { // ... }
-
星投影
有时你对类型参数一无所知,但任然想安全的使用它。保险的方法就是定一个该范型的投影,每个该范型的正确实例都将是该投影的子类,Foo<*>
-
范型约束
上界:最常用的类型约束是上界,在 Java 中对应 extends关键字,这里的上界只是约束在给泛型定型时要满足的条件。
fun <T : Comparable<T>> sort(list: List<T>) { // ... } sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int> sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
默认的上界是 Any?。在尖括号内只能指定一个上界。如果要指定多种上界,需要用 where 语句指定:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T> where T : Comparable, T : Cloneable { return list.filter { it > threshold }.map { it.clone() } }
嵌套类
-
嵌套类,与java的静态内部类相似:
class Outer { private val bar: Int = 1 class Nested { fun foo() = 2 } } val demo = Outer.Nested().foo() //==2
-
内部类,相当与java的内部类,持有一个外部类的引用,不能单独使用:
class Outer { private val bar: Int = 1 inner class Inner { fun foo() = bar } } val demo = Outer().Inner().foo() //==1
-
匿名内部类,通过对象表达式创建:
window.addMouseListener(object: MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } }) //如果对象是函数式的 java 接口的实例(比如只有一个抽象方法的 java 接口),你可以用一个带接口类型的 lambda 表达式创建它 val listener = ActionListener { println("clicked") }
函数
-
函数参数
-
标准参数:
fun powerOf(number: Int, exponent: Int) {
...
}- 默认参数,函数参数可以设置默认值,当调用函数时参数被忽略会使用默认值。这样相比其他语言可以减少重载: ```kotlin fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size ) { ... }
-
命名参数:在调用函数时可以用参数的命名来赋值参数。这对于那种有大量参数的函数很方便:
fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') { ... } //调用: //使用默认参数: reformat(str) //调用非默认参数: reformat(str, true, true, false, '_') //使用命名参数: reformat(str, normalizeCase = true, uppercaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_' ) //不需要全部参数: reformat(str, wordSeparator = '_') //注意,命名参数语法不能够被用于调用Java函数中,因为Java的字节码不能确保方法参数命名的不变性
默认参数可能只是给参数一个默认值,而命名参数则给参数一个有意义的名字。
-
不带返回值的参数:
如果函数不会返回任何有用值,那么他的返回类型就是 Unit .Unit 是一个只有唯一值Unit的类型.这个值并不需要被直接返回:
fun printHello(name: String?): Unit { if (name != null) println("Hello ${name}") else println("Hi there!") // `return Unit` or `return` is optional } //Unit 返回值也可以省略: fun printHello(name: String?) { ... }
-
变长参数:
函数的参数(通常是最后一个参数)可以用 vararg 修饰符进行标记:
fun <T> asList(vararg ts: T): List<T> { val result = ArrayList<T>() for (t in ts) result.add(t) return result } //标记后,允许给函数传递可变长度的参数: val list = asList(1, 2, 3) //只有一个参数可以被标注为 vararg 。加入vararg并不是列表中的最后一个参数,那么后面的参数需要通过命名参数语法进行传值,再或者如果这个参数是函数类型,就需要通过lambda法则. //当调用变长参数的函数时,我们可以一个一个的传递参数,比如 asList(1, 2, 3),或者我们要传递一个 array 的内容给函数,我们就可以使用 * 前缀操作符: val a = array(1, 2, 3) val list = asList(-1, 0, *a, 4)
各种类型参数定义与使用与Python相似
-
-
单表达式函数:
//当函数只返回单个表达式时,大括号可以省略并在 = 后面定义函数体: fun double(x: Int): Int = x*2 //在编译器可以推断出返回值类型的时候,返回值的类型可以省略: fun double(x: Int) = x * 2
-
函数范围
Kotlin 中可以在文件顶级声明函数,这就意味者你不用像在Java,C#或是Scala一样创建一个类来持有函数。除了顶级函数,Kotlin 函数可以声明为局部的,作为成员函数或扩展函数:
//局部函数 fun dfs(graph: Graph) { fun dfs(current: Vertex, visited: Set<Vertex>) { if (!visited.add(current)) return for (v in current.neighbors) dfs(v, visited) } dfs(graph.vertices[0], HashSet()) } //局部函数可以访问外部函数的局部变量(比如闭包) fun dfs(graph: Graph) { val visited = HashSet<Vertex>() fun dfs(current: Vertex) { if (!visited.add(current)) return for (v in current.neighbors) dfs(v) } dfs(graph.vertices[0]) } //局部函数甚至可以返回到外部函数 fun reachable(from: Vertex, to: Vertex): Boolean { val visited = HashSet<Vertex>() fun dfs(current: Vertex) { if (current == to) return@reachable true if (!visited.add(current)) return for (v in current.neighbors) dfs(v) } dfs(from) return false }
-
成员函数
跟java一样,类中的成员。
-
泛型函数
跟java一样。
-
尾递归函数
Kotlin 支持函数式编程的尾递归。这个允许一些算法可以通过循环而不是递归解决问题,从而避免了栈溢出。当函数被标记为 tailrec 时,编译器会优化递归,并用高效迅速的循环代替它:
tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x)) //使用 tailrec 修饰符必须在最后一个操作中调用自己。在递归调用代码后面是不允许有其它代码的,并且也不可以在 try/catch/finall 块中进行使用。当前的尾递归只在 JVM 的后端中可以用
-
高阶函数
高阶函数就是可以接受函数作为参数或者返回一个函数的函数:
fun <T> lock(lock: Lock, body: () -> T ) : T { lock.lock() try { return body() } finally { lock.unlock() } } //body 是一个类型为 () -> T 的函数,可以这样使用 fun toBeSynchroized() = sharedResource.operation() val result = lock(lock, ::toBeSynchroized) //更方便的是传一个字面函数(lambda表达式) val result = lock(lock, { sharedResource.operation() }) //在 kotlin 中有一个约定,如果某一个函数的最后一个参数是函数,并且你向那个位置传递了一个 lambda 表达式,那么,你可以在括号外面定义这个 lambda 表达式: lock (lock) { sharedResource.operation() } //高阶函数map: fun <T, R> List<T>.map(transform: (T) -> R): List<R> { val result = arrayListOf<R>() for (item in this) result.add(transform(item)) return result } //调用: val doubled = ints.map {it -> it * 2} //如果字面函数只有一个参数,则声明可以省略,名字就是 it : ints.map {it * 2} //这样就可以写LINQ-风格的代码了: strings.filter{ it.length == 5 }.sortedBy{ it }.map{ it.toUpperCase() }
-
字面函数和函数表达式(lambda表达式),字面函数或函数表达式就是一个 "匿名函数",也就是没有声明的函数,但立即作为表达式传递下去:
max(strings, {a, b -> a.length < b.length }) //max 函数就是一个高阶函数,它接受函数作为第二个参数。第二个参数是一个表达式所以本生就是一个函数,即字面函数。作为一个函数,相当于: fun compare(a: String, b: String) : Boolean = a.length < b.length
如果只有一个参数lambda中可以不写参数变量,直接用it表示参数,如:
{it.length}
-
Lambda表达式接收器:
(函数字面量接收器,在定义高阶函数参数时使用)是上面两者的结合——一个以指定接收器的扩展函数为参数的高阶函数。所以在我们传递的Lambda表达式中我们可以直接访问接收器的公共方法和属性(在接受器的上下文环境中),就好像在接收器内部一样:
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) { val fragmentTransaction = beginTransaction() fragmentTransaction.func() fragmentTransaction.commit() } //或: inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) { beginTransaction().func().commit() } //这就是FragmentManager的扩展函数,接收一个Lambda表达式接收器作为参数,FragmentTransaction作为接收器 //调用 supportFragmentManager.inTransaction { //remove(fragmentA) add(R.id.frameLayoutContent, fragmentB) } //需要说明的是在Lambda表达式中我们调用FragmentTransaction的方法如add或者remove时并没有使用修饰符,因为这是对FragmentTransaction的扩展函数.
-
函数类型
一个函数要接受另一个函数作为参数,我们得给它指定一个类型。比如上面的 max:
fun max<T>(collection: Collection<out T>, less: (T, T) -> Boolean): T? { var max: T? = null for (it in collection) if (max == null || less(max!!, it)) max = it return max } //参数 less 是 (T, T) -> Boolean类型,也就是接受俩个 T 类型参数返回一个 Boolean:如果第一个参数小于第二个则返回真。在函数体第四行, less 是用作函数。 //一个函数类型可以像上面那样写,也可有命名参数 val compare: (x: T,y: T) -> Int = ...
-
函数文本语法
函数文本的完全写法:
val sum = {x: Int,y: Int -> x + y} //函数文本总是在大括号里包裹着,在完全语法中参数声明是在括号内,类型注解是可选的,函数体是在 -> 之后,像下面这样: val sum: (Int, Int) -> Int = {x, y -> x+y } //函数文本有时只有一个参数。如果 kotlin 可以从它本生计算出签名,那么可以省略这个唯一的参数,并会通过 it 隐式的声明它 ints.filter {it > 0}//这是 (it: Int) -> Boolean 的字面意思 //注意如果一个函数接受另一个函数做为最后一个参数,该函数文本参数可以在括号内的参数列表外的传递
-
函数表达式
指定返回值的函数在大多数情形中是不必要的,因为返回值是可以自动推断的。然而,如果你需要自己指定,可以用函数表达式来做:
fun(x: Int, y: Int ): Int = x + y //函数表达式很像普通的函数声明,除了省略了函数名。它的函数体可以是一个表达式(像上面那样)或者是一个块: fun(x: Int, y: Int): Int { return x + y } //参数以及返回值和普通函数是一样的,如果它们可以从上下文推断出参数类型,则参数类型可以省略: ints.filter(fun(item) = item > 0)
-
闭包
一个字面函数或者表达式函数可以访问闭包,即访问自身范围外的声明的变量。不像 java 那样在闭包中的变量是被捕获修改的:
var sum = 0 ints.filter{it > 0}.forEach { sum += it } print(sum)
-
函数表达式扩展
表达式函数的扩展和普通的扩展区别是它有接收类型的规范:
val sum = fun Int.(other: Int): Int = this + other //接收类型必须在表达式函数中明确指定,但字面函数不用。字面函数可以作为扩展函数表达式,但只有接收类型可以通过上下文推断出来,表达式函数的扩展类型是一个带接收者的函数: sum : Int.(other: Int) -> Int //使用 1.sum(2)
字面函数(lambda)用->分隔函数体,函数表达式用=分隔函数体。
-
内联函数
编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段,也就是说把被调用的函数体复制到调用处,好处:
- 减少了方法调用,压栈,出栈的成本。
- 在kotlin中,函数就是对象,当你调用某个函数的时候,就会创建相关的对象,内存的分配,虚拟调用都有开销,内联可以减少成本。
使用:
inline fun <T> check(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } } //---------调用----------------// fun run() { check(l, {"我是lambda方法体"})//l是一个Lock对象 } //编译器会把调用处换成这样: fun run() { l.lock() try { return "我是lambda方法体" } finally { l.unlock() } } //如一个函数是inline的,那么参数里的函数,lambda也默认为inline的。如果要部分参数为非inline,可以使用noinline关键字: inline fun doSomething(a:Int,b:Int,noinline doOther:(a:Int,b:Int)->Int){ //... }
-
非局部返回
Kotlin在lambda中不能直接使用return,要使用return配合标签,但如果内联,则可以在lambda中直接使用return,该return直接作用于调用者函数,也就是说直接作用在调用的地方,谁调用退出谁。其他内联函数中的return一样(return也被复制到了调用内联函数的函数体里)。如果要只退出lambda,可以使用return@xxx。
-
内联属性
对属性来说,我们会有get,set的方法来操作这个属性。
get,set就是个函数,我们可以标识他们为内联函数:val foo: Foo inline get() = Foo() var bar: Bar get() = ... inline set(v) { ... } // inline var bar: Bar get() = ... set(v) { ... }
-
实例化参数类型
有时我们需要访问传递过来的类型,把它作为参数:
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? { var p = parent while (p != null && !clazz.isInstance(p)) { p = p?.parent } @suppress("UNCHECKED_CAST") return p as T } //调用 myTree.findParentOfType(javaClass<MyTreeNodeType>() ) // myTree.findParentOfType(MyTreeNodeType::class.java) //我们想要的仅仅是给这个函数传递一个类型,如果即像下面这样就很方便: myTree.findParentOfType<MyTreeNodeType>() //为了达到这个目的,内联函数支持具体化的类型参数申明 reified inline fun <reified T> TreeNode.findParentOfType(): T? { var p = parent while (p != null && p !is T) { p = p?.parent } return p as T }
我们用 refied 修饰符检查类型参数,既然它可以在函数内部访问了,也就基本上接近普通函数了。因为函数是内联的,所以不许要反射,像 !is `as`这样的操作都可以使用。同时,我们也可以像上面那样调用它了 myTree.findParentOfType<MyTreeNodeType>() 。普通的函数(没有标记为内联的)不能有实例化参数。
在很多情况下会使用反射访问类型数据,我们仍然可以使用实例化的类型参数 javaClass() 来访问它:
inline fun methodsOf<reified T>() = javaClass<T>().getMethods() fun main(s: Array<String>) { println(methodsOf<String>().joinToString('\n')) }
协程
。。。
空安全
-
Kotlin 类型系统致力于消灭空引用(NPE),在 Kotlin 类型系统中可以为空和不可为空的引用是不同的,属性默认是要赋初值的,不能为空:
var a: String ="abc" a = null //编译错误 //允许为空,我们必须把它声明为可空的变量 var b: String? = "abc" b = null //调用 a 的方法,而不用担心 NPE 异常: val l = a.length() //使用 b 调用同样的方法就可能报错 val l = b.length() //错误:b 可能为空
-
使用可空属性(?)时的四种方式:
-
在条件中检查 null:
val l = if (b != null) b.length() else -1 //更复杂的条件: if (b != null && b.length() >0) print("Stirng of length ${b.length}") else print("Empty string")
-
安全调用,使用安全操作符,?.
b?.length() //如果 b 不为空则返回长度,否则返回空。这个表达式的的类型是 Int?,安全调用在链式调用是是很有用的。比如,如果 Bob 是一个雇员可能分配部门(也可能不分配),如果我们想获取 Bob 的部门名作为名字的前缀,就可以这样做: bob?.department?.head?.name //这样的调用链在任何一个属性为空都会返回空
-
Elvis 操作符,?:
val l = b.length()?: -1 //如if表达式: val l: Int = if (b != null) b.length() else -1 //如果 ?: 左边表达式不为空则返回,否则返回右边的表达式。注意右边的表带式只有在左边表达式为空是才会执行 //注意在 Kotlin 中 throw return 是表达式,所以它们也可以在 Elvis 操作符右边。这是非常有用的,比如检查函数参数是否为空: fun foo(node: Node): String? { val parent = node.getParent() ?: return null val name = node.getName() ?: throw IllegalArgumentException("name expected") //... }
-
!! 操作符
NPE-lovers,我们可以用 b!! ,这会返回一个非空的 b 或者抛出一个 b 为空的 NPE:
val l = b !!.length()
-
-
安全转换
普通的转换可能产生 ClassCastException 异常。另一个选择就是使用安全转换,如果不成功就返回空:
val aInt: Int? = a as? Int
等式
-
在 kotlin 中有两种相等
-
参照相等:参照相等是通过 === 操作符判断的(不等是!== ) a===b 只有 a b 指向同一个对象是判别才成立。另外,你可以使用内联函数 identityEquals() 判断参照相等:
a.identityEquals(b) a identityEquals b
-
结构相等:结构相等是通过 == 判断的。像 a == b 将会翻译成:
a?.equals(b) ?: b === null //如果 a 不是 null 则调用 equals(Any?) 函数,否则检查 b 是否参照等于 null //注意完全没有必要为优化你的代码而将 a == null 写成 a === null 编译器会自动帮你做的
kotlin中==相当于java的equals,===相当于java的==
-
多重申明(解构申明)
var (name, age) = person
-
意思就是一次性申明多个变量,并把=号右边的对象的属性拆箱出来赋值给变量。如:
data class Person(var name: String, var age: Int) { } var person: Person = Person("Jone", 20) var (name, age) = person println("name: $name, age: $age")// 打印:name: Jone, age: 20
如果拆箱出对象的属性:
val name = person.component1() val age = person.component2()
person.component1,component2怎么来的呢,Kotlin的数据类编译器会根据主构造器中声明的属性, 自动推断生成componentN() 函数群, 这些函数与类的属性对应, 函数名中的数字1到N,与属性的声明顺序一致。那么如果不是数据类就要自己编写对象的componentN函数:
class Person(val name: String, val age: Int) { operator fun component1(): String { return name } operator fun component2(): Int { return age } }
-
解构申明可以用在for循环中:
var personA: Person = Person("Door", 22, "ShanDong") var personB: Person = Person("Green", 30, "BeiJing") var personC: Person = Person("Dark", 23, "YunNan") var personD: Person = Person("Tool", 26, "GuanDong") var personE: Person = Person("Mark", 24, "TianJin") var pers = listOf(personA, personB, personC, personD, personE) for ((name, age) in pers) { println("name: $name, age: $age") }
-
Map使用结构申明,Kotlin的标准库中,对Map实现了这些扩展函数:
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator() operator fun <K, V> Map.Entry<K, V>.component1() = getKey() operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
所以在使用Map.Entry.getkey时使用调用到component1(),getvalue时调用component2():
var personA: Person = Person("Door", 22, "ShanDong")
var personB: Person = Person("Green", 30, "BeiJing")
var personC: Person = Person("Dark", 23, "YunNan")
var personD: Person = Person("Tool", 26, "GuanDong")
var personE: Person = Person("Mark", 24, "TianJin")
var map = HashMap<String, Person>()
map.put("1", personA)
map.put("2", personB)
map.put("3", personC)
map.put("4", personD)
map.put("5", personE)
for ((key, value) in map) {
println("key: $key, value: $value")
}
// Log打印
key: 1, value: Person(name='Door', age=22, addr='ShanDong', mobile=null)
key: 2, value: Person(name='Green', age=30, addr='BeiJing', mobile=null)
key: 3, value: Person(name='Dark', age=23, addr='YunNan', mobile=null)
key: 4, value: Person(name='Tool', age=26, addr='GuanDong', mobile=null)
key: 5, value: Person(name='Mark', age=24, addr='TianJin', mobile=null)
Ranges
-
表示从多少到多少,可用于if判断和for循环,与in关键字配合,常见用法:
// Checking if value of comparable is in range. Optimized for number primitives. if (i in 1..10) println(i) if (x !in 1.0..3.0) println(x) if (str in "island".."isle") println(str) // Iterating over arithmetical progression of numbers. Optimized for number primitives (as indexed for-loop in Java). for (i in 1..4) print(i) // prints "1234" for (i in 4..1) print(i) // prints nothing for (i in 4 downTo 1) print(i) // prints "4321" for (i in 1..4 step 2) print(i) // prints "13" for (i in (1..4).reversed()) print(i) // prints "4321" for (i in (1..4).reversed() step 2) print(i) // prints "42" for (i in 4 downTo 1 step 2) print(i) // prints "42" for (x in 1.0..2.0) print("$x ") // prints "1.0 2.0 " for (x in 1.0..2.0 step 0.3) print("$x ") // prints "1.0 1.3 1.6 1.9 " for (x in 2.0 downTo 1.0 step 0.3) print("$x ") // prints "2.0 1.7 1.4 1.1 " for (str in "island".."isle") println(str) // error: string range cannot be iterated over
原理参见标准库中接口:Range ,Progressiont和和操作函数的扩展。
-
for in :
如果你想通过 list 或者 array 的索引进行迭代,你可以这样做:
for (i in array.indices) print(array[i]) //-------------------------// for ((index, value) in array.withIndex()) { println("the element at $index is $value") }
类型检查和转换
-
类型检查:is !is 表达式:
//运行时检查一个对象是否是某个特定类: if (obj is String) { print(obj.length) } if (obj !is String) { // same as !(obj is String) print("Not a String") } else { print(obj.length) } //智能转换,编译器会跟踪 is 检查静态变量,并在需要的时候自动插入安全转换: fun demo(x: Any) { if (x is String) { print(x.length) // x is automatically cast to String } } if (x !is String) return print(x.length) //x 自动转换为 String //在 || && 操作符,when 表达式和 whie 循环中: // x is automatically cast to string on the right-hand side of `||` if (x !is String || x.length == 0) return // x is automatically cast to string on the right-hand side of `&&` if (x is String && x.length > 0) print(x.length) // x is automatically cast to String when (x) { is Int -> print(x + 1) is String -> print(x.length + 1) is Array<Int> -> print(x.sum()) }
-
转换:
用as 操作符来转换类型:
val x: String = y as String //null 不能被转换为 String 因为String不是 nullable,也就是说如果 y 是空的,则上面的代码会抛出空异常。为了 java 的转换语句匹配我们得像下面这样: val x: String?= y as String?
"安全"转换:
val x: String ?= y as? String //为了避免抛出异常,可以用 as? 这个安全转换符,这样失败就会返回 null
This表达式
-
如果 this 没有应用者,则指向的是最内层的闭合范围。为了在其它范围中返回 this ,需要使用标签:
//this@lable class A { // implicit label @A inner class B { // implicit label @B fun Int.foo() { // implicit label @foo val a = this@A // A's this val b = this@B // B's this val c = this // foo()'s receiver, an Int val c1 = this@foo // foo()'s receiver, an Int val funLit = @lambda {String.() -> val d = this // funLit's receiver val d1 = this@lambda // funLit's receiver } val funLit2 = { (s: String) -> // foo()'s receiver, since enclosing function literal // doesn't have any receiver val d1 = this } } } }
运算符号重载
。。。
operator fun get(position: Int) = dailyForecast[position]
//xxx[position]
一些使用时的笔记(建议)
-
当需要把一个对象转成另一个,或有多个当前类或对象的.调用等,可以使用这些扩展和函数,提高效率:
- let
- apply
- run
- with,with是个函数
-
使用.isNullOrEmpty(),isNullOrBlank(),isBlank(),isEmpty(),isNotBlank(),isNotEmpty()来代替TextUtils判断字符串。
-
善用集合中的各种扩展函数,如reduce,filter,map,any,all,count,max,sumBy等等。
-
构造函数里的变量如果要在类中使用(类属性)要标记定义关键字var或val,否则作用域不会是整个类,就像只是函数的参数一样。
-
一些高阶函数,lambda {}里不要用return,是返回的最后一行,如果用return他又是inline的话会返回了外层的函数。
-
利用默认参数减少(java)方法重载
-
可空也是一种类型(可空的xx类型),可接受实参为空或具体类型的实例,可空类型的实例变量要解包(!!)后才可以使用原类型的属性、方法。
-
for( index in 5..1),其中5和1只能是数值,如果用变量要用(x-1)包起来并转成数字:for (i in (x+1)..(y+1))。
-
kotlin 没有Volatile等并发编程的关键字,这是kotlin有意为之,kotlin让为这应该让函数库来做,但并不是不能用,可以使用@Volatile,@Synchronized注解来使用相应功能,@Volatile标记jvm的备用字段为volatile。wait(), notify()等Object(在Kotlin的Any中没有这些方法)的方法可以这么使用:
private val lock = java.lang.Object() fun produce() = synchronized(lock) { while (items >= maxItems) { lock.wait() } Thread.sleep(rand.nextInt(100).toLong()) items++ println("Produced, count is $items: ${Thread.currentThread()}") lock.notifyAll() } fun consume() = synchronized(lock) { while (items <= 0) { lock.wait() } Thread.sleep(rand.nextInt(100).toLong()) items-- println("Consumed, count is $items: ${Thread.currentThread()}") lock.notifyAll() }
-
当碰到用java时常用的如果不如为就…时可以用let等扩展:
if (data != null) { nameTv.setText(data.name); } //kotlin data?.apply { nameTv.text=name info{"xxx"} } data?.let{ nameTv.text=it.name } mOnActionListener?.onAction()
-
使用高阶函数+函数对象定义监听器(java中的onClickListener等)
-
当可变属性(var)定义为可空(?)时编译器报错:Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time 解决的几种办法:
var name: String? = null val names: ArrayList<String> = ArrayList() //1:如果能用只读,改成val。 //2::用一个本地变量接收再使用: fun foo() { val nameLoc = a.name if(nameLoc != null) { names.add(name); } } //3:用?操作符 name?.let{ names.add(name); } //4:如果是在用Elvis操作符 foo1(name?:"") //循环中 names.add(name?:continue);
作者:竹尘居士
博客:http://zhuchen.vip/2018/04/01/kotlin/kotlin-learn-summary.html