主要用来记录Kotlin的一些关键概念
Kotlin lazy关键字
lazy用在懒初始化的场景下,在参数不使用时无需进行初始化过程。
1 2 3 4 5 6 class Bird (var weight:Double = 0.00 ,var age:Int = 1 ,var color:String = "blue" ){ val sex : String by lazy{ if (color=="yellow" ) "male" else "female" } }
语法特点如下:
修饰变量必须是val
只有被首次调用时才可以赋值,后续不能进行修改
另外可以对lazy属性进行设置。共支持三种属性:
LazyThreadSafetyMode.SYNCHRONIZED(默认属性):加上同步锁,在同一时刻只允许一个线程对lazy属性进行初始化,所以是线程安全 。
LazyThreadSafetyMode.PUBLICATION:允许多个线程进行初始化操作
LazyThreadSafetyMode.NONE:若确定初始化总是发生单线程上,可以设置该参数,就不会存在线程方面的开销。
lateinit
lateinit用在延迟初始化的场景下,允许编译器识别非空类型属性的值然后跳过空检查,使之正常编译。
1 2 3 class Bird (var weight:Double = 0.00 ,var age:Int = 1 ,var color:String = "blue" ){ lateinit var sex : String }
使用lateinit时,切记后续一定要进行初始化,否则还是会报错。
Delegates.notNull<T>可以针对var修饰变量进行延迟初始化。
1 2 3 4 var a by Delegates.notNull<String>()fun setValue () { a="test" }
Kotlin 作用域函数 主要有let、run、with、apply和also
他们之间的主要区别在于
上下文对象
做为lambda表达式的 接收者this 或者 参数it。
this
run、with以及apply通过this引用上下文对象
1 2 3 4 5 val adam = Person("Adam" ).apply { this .age = 20 city = "London" } println(adam)
it
let和also将上下文对象做为lambda表达式参数。
1 2 3 4 5 6 7 fun getRandomInt () : Int { return Random.nextInt(100 ).also { writeToLog("getRandomInt() generated value $it " ) } }val i = getRandomInt()
作用域函数 let
上下文对象为it,返回值为lambda表达式的结果
let经常用于仅使用非空值执行代码块。
1 2 3 4 5 val numbers = mutableListOf("one" , "two" , "three" , "four" , "five" ) numbers.map { it.length }.filter { it > 3 }.let { println(it) }
另一种情况就是引入作用域受限的局部变量以提高代码的可读性
1 2 3 4 5 val numbers = listOf("one" , "two" , "three" , "four" )val modifiedFirstItem = numbers.first().let { firstItem -> println("The first item of the list is '$firstItem '" ) if (firstItem.length >= 5 ) firstItem else "!" + firstItem + "!" }.toUpperCase()
with
上下文对象为this,返回值为lambda表达式的结果
with可以理解为对于这个对象,执行以下操作
1 2 3 4 5 val numbers = mutableListOf("one" , "two" , "three" ) with(numbers) { println("'with' is called with argument $this " ) println("It contains $this .size elements" ) }
with需要显式的传入参数
run
上下文对象为this,返回值为lambda表达式的结果
功能与with基本一致
1 2 3 4 5 val result = service.run { port = 8080 query(prepareRequest() + " to port $port " ) }
apply
上下文对象为this,返回值为上下文对象本身
apply可以理解为将以下赋值操作应用于对象,并且可以返回对象。
1 2 3 4 5 val adam = Person("Adam" ).apply { age = 32 city = "London" } println(adam)
also
上下文对象为it,返回值为上下文对象本身
also对于执行一些上下文对象做为参数的操作很有用
1 2 3 4 val numbers = mutableListOf("one" , "two" , "three" ) numbers .also { println("The list elements before adding new one: $it " ) } .add("four" )
使用场景
对一个非空(non-null)对象执行 lambda 表达式:let
将表达式作为变量引入为局部作用域中:let
对象配置:apply
对象配置并且计算结果:run
在需要表达式的地方运行语句:非扩展的 run
附加效果:also
一个对象的一组函数调用:with
Kotlin object关键字 在Kotlin代码中没有出现过static关键字。
在Java中,static是非常重要的特性,可以用来修饰类、方法或属性。
static修饰的内容都是属于类的,代码结构无法清晰区分,而且不是面对对象的。
违背了面向对象思想、static修饰的静态类、静态对象很难被GC。
伴生对象
伴随某个类的对象,属于这个类。并且全局只有一个单例,被声明在类的内部,在类装载时会初始化。
利用companion object实现伴生对象
1 2 3 4 5 class XX { companion object { } }
伴生对象就是Kotlin中用来代替static的。
单例模式
在系统中只能存在一个实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SingleInstance { private SingleInstance () { } private static volatile SingleInstance INSTANCE ; public SingleInstance getInstance () { if (INSTANCE==null ){ synchronized (SingleInstance.class){ if (INSTANCE == null ){ INSTANCE = new SingleInstace (); } } } return INSTANCE; } private Object readResolve () { return mInstance; } }
上述为Java通用的单例写法——双重校验锁。
在Kotlin中,只要以下代码
1 2 3 object SingleInstance { }
object的实现为饿汉式,需要提前初始化。
声明匿名内部类 object表达式可以赋值给一个变量,减少很多重复代码。
1 2 3 4 5 6 private val sThreadLocal = object : ThreadLocal<DNSThreadLocalModel>() { override fun initialValue () : DNSThreadLocalModel { } }
Kotlin 空安全机制 在使用Kotlin前,Java都一般通过如下方法去解决NPE
函数内对于无效值,倾向于抛异常处理
采用@NotNull/@Nullable标注
使用专门的Optional对可能为null的变量进行装箱。
可空类型
在任何类型后面加上?就表示对象可为空。
?.:安全调用,只有对象存在时,才可以继续调用方法
?::合并运算符,如果非空就使用它,为空则使用一个默认值
!!.:类似Assert,当对象为空时,继续抛出NPE
实现原理 针对?相关的代码进行反编译查看,内部是通过在参数上标注了@Nullable,然后调用时,采用了if..else进行非空判断,保证安全。
可能出于以下原因:
兼容Java老版本
实现Java与Kotlin的100%转换
Kotlin inline、noinline、crossinline inline
函数进行内联,将inline fun直接插入到调用函数的代码内,优化代码结构,从而减少函数类型对象的创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fun main (args:Array <String >) { testInline() print("world" ) }inline fun testInline () { print("Hello" ) } 输出结果: HelloWorld 实际编译结果:fun main (args:Array <String >) { print("Hello" ) print("world" ) }
noinline
局部关掉函数内联优化,摆脱inline不能使用函数类型的参数当对象用的限制。作用于函数的参数且参数必须为函数类型
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 inline fun test (noinline a : Int ) { }inline fun test (a: Int , b: (Int ) -> Unit ) : (Int ) -> Unit { return b }inline fun test (a:Int , noinline b :(String )->Unit ) : (String) -> Unit { println(a) b("World" ) return b }fun main (args:Array <String >) { println("Hello" ) test(3 ){ it-> println(it) } } 输出结果: Hello3 World 实际编辑结果:fun main (args:Array <String >) { println("Hello" ) println(3 ) b.invoke("World" ) }
crossinline
局部加强函数内联优化,将内联函数里的函数类型参数可以当作对象使用。
首先声明两个概念:
crossinline实质为了声明函数参数的lambda不能写return,避免lambda中的return影响外部的执行流程 。
使用inline修饰函数时需要注意以下几点:
inline修饰函数,最好函数参数也是函数类型,否则无法获得性能提升
避免内联大型函数 ,因为inline会增加代码的生成量
inline修饰的函数不持有函数的对象引用,也不能将函数参数传递给另一个函数
1 2 3 4 5 6 7 fun test123 (a:()->Unit ) { }inline fun test12 (a:()->Unit ) { test123(a) }
Kotlin Java与Kotlin互相调用 Java调用Kotlin 文件名
当文件包含顶级函数或属性时,需要使用@file:JvmName("XX")对其进行注释,可以在XX调用到具体方法和属性
1 2 3 4 5 6 7 8 9 10 11 @file:JvmName ("AAA" )package com.xx.xx;fun call () {} void test(){ AAA.call() }
成员变量
一般情况下调用Kotlin的成员变量时,默认会自动生成get、set方法,并不能直接去使用对应变量。
1 2 3 4 5 6 7 8 9 data class Data ( val a:String, @JvmField val b:String ) Data data = new Data("a" ,"b" ); String a = data .getA(); String b = data .b;
观察编译后源码就知道原因
1 2 3 4 5 6 7 8 9 10 11 public final class A { @NotNull private final String a; @NotNull public String b; public final String getA () { return this .a; } }
相对的设置@JvmField就没有get和set。
还可以通过@get:JvmName和@set:JvmName去指定对应方法
1 2 3 4 5 6 7 8 9 10 11 data class Data (@get:JvmName ("getAA" ) val a:String,@set:JvmName ("setBB" ) var b:String ) Data data = new Data("a" ,"b" );data .getAA();data .setBB("bb" )
伴生函数/伴生常量
companion object中定义的函数与对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class KotlinClass { companion object { const val CONST = "const" val value = "Value" fun doWork () {...} } }public static void main(String[] args){ KotlinClass.Companion.getValue(); String value = KotlinClass.CONST; KotlinClass.Companion.doWork(); }
在Java中调用伴生对象相关内容时,需要额外添加Companion才可以使用。
这时需要添加@JvmStatic注释,就可以直接调用对应参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class KotlinClass { companion object { const val CONST = "const" @JvmStatic val value = "Value" @JvmStatic fun doWork () {...} } }public static void main(String[] args){ KotlinClass.getValue(); String value = KotlinClass.CONST; KotlinClass.doWork(); }
编译后源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public final class KotlinClass { @NotNull public static final String CONST = "const" ; @NotNull private static final String value = "Value" ; public static final void doWork () { Companion.doWork(); } public static final class Companion { @NotNull public final String getValue () { return KotlinClass.value; } @JvmStatic public final void doWork () { } } }
被@JvmStatic修饰后的方法/变量,会被提取到被伴生的类上,就可以直接被调用了。
方法默认参数值
Kotlin实现的方法可以通过在参数后面写上= XX设置默认值,后续Kotlin的方法去调用时就不需要设置相关参数就可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Greeting { fun sayHello (prefix: String = "Mr." , name: String ) { println("Hello, $prefix $name " ) } }fun test () { Greeting().sayHello("wxy" ) }public static void main(String[] args){ Greeting greeting = new Greeting(); greeting.sayHello("Mr." ,"wxy" ); }
在Java中该设置是无法生效的,此时就需要@JvmOverloads去修饰
1 2 3 4 5 6 7 8 9 10 11 12 class Greeting { @JvmOverloads fun sayHello (prefix: String = "Mr." , name: String ) { println("Hello, $prefix $name " ) } }public static void main(String[] args){ Greeting greeting = new Greeting(); greeting.sayHello("wxy" ); }
实现原理就是实现了两个重载方法。
还有其他示例,例如自定义View
1 2 3 4 @JvmOverloads constructor (context: Context, attrs: AttributeSet? = null , defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { }
异常实现 实现@Throws()
1 2 3 4 @Throws(IOException::class) fun ex () { throw IOException("" ) }
synchronized 实现@Synchronized
1 2 3 4 @Synchronized fun test () { }
volatile 实现@Volatile
1 2 @Volatile var abc : String = ""
不允许Java调用 实现@JvmSynthetic
1 2 3 4 @JvmSynthetic fun forbiden () { }
Kotlin调用Java 不得使用硬关键字
请勿将Kotlin的任何硬关键字用做方法或字段的名称 。例如is、when、object等
若要使用必须用反引号进行转义( 反引号 ``)
1 2 3 4 5 6 7 8 public Object object ;public void is () { } testJava.`is `()
可为null性注释
如果要求Kotlin不能设置参数为null,在Java中就需要对对应参数添加@NonNull
1 2 3 4 5 6 7 8 9 class JavaClass { public void needNotNull(@NonNull String a); public void needNull(@Nullable String a); }fun main () { JavaClass().needNotNull(null ) }
可变长度参数
Java中实现可变参数为String...strs ,对应Kotlin的实现为vararg,但是两者不能互传
1 2 3 4 5 6 7 8 9 10 11 12 class JavaClass { void test(String... args){ } }fun main () { JavaClass().test(*arrayOf<String>("1" )) }
运算符过载
Kotlin允许使用特殊调用点语法。可以有效缩短语法。
运算符过载
Lambda参数位于最后
在Kotlin中调用带有接口参数的方法时,如果接口只有一个方法,可以通过Lambda表达式实现SAM转换。
SAM转换:只能应用于接口上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface IListener { void test(); }public class JavaClass { void singleFun(IListener listener){} void hasParamFun(int a,IListener listener){} }fun main () { JavaClass().sinlgeFun{ } JavaClass().hasParamFun(123 ){ } }
将Lambda参数置于最后,可以简化写法。
Kotlin 泛型 泛型的优势
类型检查,编译时就可以检测错误
自动类型转换,不用强制类型转换
Java中使用泛型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface Genertor <T>{ public T get () ; }public class GenertorImpl <T> implements Gentor <T>{ @Override public T get () { } }public <T> T genericMethod (T t) { }private class C <T extends Number > { }
通配符 ? extends X:表示X是方法传入类型的上界,即X或者X的父类
1 2 3 4 5 6 7 8 9 10 public static void print2 (GenericType<? extends Fruit> p) { System.out.println(p.getData().getColor()); }public static void use2 () { GenericType<Fruit> a = new GenericType <>(); print2(a); GenericType<Orange> b = new GenericType <>(); print2(b); }
主要用于安全的访问数据,可以访问X及其子类型
? super X:表示X是方法传入类型的下界,即X或者X的子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void printSuper (GenericType<? super Apple> p) { System.out.println(p.getData()); }public static void useSuper () { GenericType<Fruit> fruitGenericType = new GenericType <>(); GenericType<Apple> appleGenericType = new GenericType <>(); GenericType<HongFuShi> hongFuShiGenericType = new GenericType <>(); GenericType<Orange> orangeGenericType = new GenericType <>(); printSuper(fruitGenericType); printSuper(appleGenericType); printSuper(hongFuShiGenericType); printSuper(orangeGenericType); }
主要用于安全的写入数据,可以写入X及其子类型
PECS原则 Producer Extends,Consumer Super
得出以下结论:
需要从数据类型获取数据,就使用? extends 通配符
需要写入数据到数据类型,就使用? super通配符
如果既想存,又想取,就不要使用通配符
Kotlin中使用泛型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class SmartList <T > : ArrayList <T >(){ fun find (t:T ) :T?{ val index = super .indexOf(t) } }fun <T> singletonList (item: T ) : List<T> { }fun <T : Number> sum (vararg param: T ) = param.sumByDouble { it.toDouble() }
where关键字Java使用的就是&
1 2 3 4 5 6 7 class ClassA { }interface InterfaceB { }public class MyClass <T extends ClassA & InterfaceB> { Class<T> variable; }
对应Kotlin的实现,需要使用where关键字
1 2 3 4 5 open class ClassA interface InterfaceB class MyClass <T >(var variable: Class<T>) where T : ClassA, T : InterfaceB
通配符 相对于Java使用的? extends和? super,Kotlin使用的是out、in
1 2 3 4 5 6 7 public static void print2(GenericType<? extends Fruit> p){ System.out .println(p.getData().getColor()); }fun print2 (p:GenericType <out Fruit >) {}
1 2 3 4 5 6 7 public static void printSuper(GenericType<? super Apple> p){ System.out .println(p.getData()); }fun printSuper (p:GenericType <in Apple >)
相对于PECS原则,对应起来就是生产者使用 out,消费者使用 in
星投影 安全方式是定义泛型类型的这种投影,该泛型类型的每个具体实例化将是该投影的子类型。
对于 Foo <out T : TUpper>,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo <*> 等价于 Foo <out TUpper>。 这意味着当 T 未知时,你可以安全地从 Foo <*> 读取 TUpper 的值。
对于 Foo <in T>,其中 T 是一个逆变类型参数,Foo <*> 等价于 Foo <in Nothing>。 这意味着当 T 未知时,没有什么可以以安全的方式写入 Foo <*>。
对于 Foo <T : TUpper>,其中 T 是一个具有上界 TUpper 的不型变类型参数,Foo<*> 对于读取值时等价于 Foo<out TUpper> 而对于写值时等价于 Foo<in Nothing>。
获取泛型类型 因为类型擦除的存在,导致无法获取到运行时的泛型参数的类型。
但是Kotlin提供reified关键字,可以获取到泛型的类型
1 2 3 inline fun <reified T> getType () { return T::class .java }
在编译的时候,会讲具体的类型插入到字节码中,就可以在运行时获取泛型类型。
泛型擦除
泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息。成功编译过后的Class文件是不会包含任何泛型信息的,泛型信息不会进入到运行时阶段。
例如List<String>在运行时用List表示,为了确保Java 5之前的版本可以进行兼容。
被擦除的泛型信息存放于Signature中
https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files/937999#937999
Collection & Sequence
kotlin提供了基于不同执行方式的两种集合类型:
立即执行的 Collection类型
延迟执行的 Sequence类型
立即执行和延迟执行的区别在于每次对集合进行转换时,这个操作会在何时真正执行。
Collection
在每次操作时都是立即执行的,执行结果都会存储到一个新的集合中。
1 2 3 4 public inline fun <T, R> Iterable<T> .map (transform: (T ) -> R ) : List<R> { return mapTo(ArrayList<R>(collectionSizeOrDefault(10 )), transform) }
Collection内部的每一步操作都会生成一个新的集合。
Sequence
是延迟执行的,主要有两种类型:
中间操作 :不会立即执行,所有中间操作的引用会存储起来
末端操作 :立即执行,按照顺序执行存储的中间操作
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 public fun <T, R> Sequence<T> .map (transform: (T ) -> R ) : Sequence<R> { return TransformingSequence(this , transform) }internal class TransformingSequence <T, R >constructor (private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> { override fun iterator () : Iterator<R> = object : Iterator<R> { val iterator = sequence.iterator() override fun next () : R { return transformer(iterator.next()) } override fun hasNext () : Boolean { return iterator.hasNext() } } }public inline fun <T> Sequence<T> .first (predicate: (T ) -> Boolean ) : T { for (element in this ) if (predicate(element)) return element throw NoSuchElementException("Sequence contains no element matching the predicate." ) }
最后调用first()、count()等末端操作之后,内部去遍历Sequence中的元素,挨个执行直到条件匹配为止。
性能 转换的顺序 .filter{}.map{} 性能是优于 .map{}.filter{}
数据量的选择 Collection会为每次转换操作创建一个新的列表,而Sequence仅仅是保留对转换函数的引用。
根据需要处理的数据量大小,按照如下规则选择:
数据量小 使用 Collection
数据量大 使用 Sequence