Kotlin
kotlin data class
data class会自动生成以下方法:
equals()
hashCode()
1
2
3
4
5public int hashCode() {
int var10000 = Integer.hashCode(this.age) * 31;
String var10001 = this.name;
return var10000 + (var10001 != null ? var10001.hashCode() : 0);
}toString()
copy()
componentN()
编译器会为数据类生成 组件函数(Component function), 有了这些组件函数, 就可以在 解构声明(destructuring declaration) 中使用数据类:
1
2
3
4val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// 输出结果为 Jane, 35 years of age属性的get()/set()
val的属性不会有setter
constructor()
只有有参构造函数,没有无参构造函数。fastJson解析会抛该异常,需升级到高版本并引入kotlin-reflect依赖
Kotlin Object类
其字节码实现是:提供一个类,会在static方法块中实例化的该类的static final 对象。
是饿汉模式的,通过JVM类加载机制确保线程安全的
Kotlin空安全
Kotlin空安全原理:
String、String?
首先通过注解 @Lorg/jetbrains/annotations/NotNull;和@Lorg/jetbrains/annotations/Nullable;来向编译器标示参数是否为空,如果不为空则通过 INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)来进行检查,此时如果我们给个空值则编译器出现报错提示。
?.(elvis猫王表达式)
对用使用 ?. 操作符号kotlin会判断是否为null 如果不为null执行对应的逻辑,如果为null则什么也不执行(此时的默认结果也是null)
!!.
对用使用 !! 操作符号 Kotlin 同样会执行null判断如果不为null 则执行对应的逻辑,如果为null 则抛出异常,即执行 INVOKESTATIC kotlin/jvm/internal/Intrinsics.throwNpe ()
kotlin 泛型
Kotlin 泛型系统继承于 Java泛型,依然是一种语法糖的伪泛型,会在编译时发生类型擦除。但如果是内联函数+reified时,是字节码中保留类型的真泛型
Kotlin高阶函数
Kotlin中的高阶函数通过位于kotlin.jvm.functions
下的函数接口(SAM)来表示,在编译时将函数类型转换为这些接口的实现。编译器生成相应的字节码来处理高阶函数调用。(由于都是预先写好的函数接口,所以其实高阶函数最大支持的参数数量是有限的,现为22个)
1 | /** A function that takes 22 arguments. */ |
例如,以下高阶函数:
1 | fun doSomeThing(x: Int, predicate: (Int) -> String): String { |
当编译这个函数时,编译器会生成类似如下的字节码(伪代码,用于说明):
1 | public final class ExperimentalFiled3 { |
只有一个抽象方法的接口称为函数式接口或 单一抽象方法(SAM)接口
Kotlin闭包
Lambda表达式和匿名函数在Kotlin中都是闭包,意味着它们可以捕获并持有其定义作用域内的变量。
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包。
其中包括两个要点:
- 函数
- 周围环境(&状态&上下文)比如在a函数里定义了b匿名函数和变量x,b能引用到a的变量x,就叫闭包
这两者的实现原理类似的,通过编译器生成对应的
继承于kotlin.jvm.internal.Lambda并且实现kotlin.jvm.functions下函数接口的类,将表达式或匿名函数类方法体代码塞入该类的invoke方法中,并在被调用处生成该类的实例
比如
1 | val sum = { a: Int, b: Int -> a + b } |
编译器会生成类似如下的字节码(伪代码,用于说明):
1 | public final class SumLambda extends Lambda implements Function2<Integer, Integer, Integer> { |
使用时:
1 | val sum = SumLambda.INSTANCE |
Lambda表达式和匿名函数的区别主要还是在于语法的不同、返回类型是否需要显示声明、非局部返回上。
Kotlin 类、方法为什么默认final
为了让程序编写者慎用继承,仅当需要被继承时才手动使用open关键字修饰需要被集成的类或方法。这样可以增加对“降低耦合性、提高灵活性”的考虑
kotlin 嵌套类&内部类
java: 只分为 静态内部类 和 非静态内部类, 只要是非静态内部类都会固定持有外部类的对象,只要是静态内部类都是不持有外部类引用的
kotlin: 分为
内部类 inner 修饰的内部类(类成员), 相当于 java非静态内部类,固定持有外部类引用的(本质上是生成外部类引用的构造参数)
嵌套类 非inner修饰的成员类, 相当于 java静态内部类,不持有外部类引用的
3.1 匿名内部类(常见) 使用对象表达式创建的类(object: Interface{ }),可以访问到外部类成员,但默认不持有外部类引用,只有主动持有外部变量是才会将该外部类对象(如Activity对象)作为构造参数(见字节码)引入
3.2 方法内嵌套类 在方法内定义的非inner修饰的类,可以访问到外部类成员,但默认不持有外部类引用,只有主动持有外部变量是才会将该外部类对象(如Activity对象)作为构造参数(见字节码)引入
Kotlin注解
Kotlin
代码可以经过编译器转换成VM虚拟机
能识别的字节码,所以Java
与Kotlin
可以互相进行调用。而由于Java
与Kotlin
语言特性的差异,当Java
调用Kotlin
代码时,可以在Kotlin
代码中适当增加一些注解,从而更方便的调用Kotlin
代码。
@JvmOverloads
在Kotlin的方法里有多个默认参数时,如果在Java中直接调用,只能调用一个包含完整参数的方法,如果想暴露更多的重载函数给Java,可以使用@JvmOverloads 用于生成重载。对于每一个有默认值的参数,生成的重载会把当前有默认值的参数及其右边的参数都去掉,所以如果方法中所有的参数都有默认值,生成的重载函数中还会有一个无参的重载函数。
@JvmOverloads 主要用于构造函数、方法中,同时不能用于抽象方法、接口中的方法等。
常见应用在自定义View构造函数中
1 | VpLoadMoreView @JvmOverloads constructor( |
@JvmStatic
@JvmStatic用于声明静态方法。在具名对象及伴生对象中使用时,既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法,如:
1 | class KtA { |
在Java中调用:
1 | public void invokeKt() { |
@JvmField
@JvmField
使得编译器不再对该字段生成getter/setter
并将其作为公开字段
@JvmSynthetic
@JvmSynthetic可以修饰于方法上,控制只能在Kotlin中调用,如:
1 | class KtA { |
Java中调用:
1 | public void invokeKt() { |
如果想在Java中调用到Kotlin类中的方法,将@JvmSynthetic去掉即可。
@JvmInline
1 |
|
@JvmInline在1.5.0版本引入,可以指定一个类为内联类,需结合value一起使用;在1.5.0之前使用inline关键字。
1 | //1.5.0之前,inline标记内联类 |
@JvmName 、@JvmMultifileClass
@JvmName
注解可以生成类名;如果类名已存在,可以修改已生成的 Java
类的类名。
包名相同并且类名相同或者有相同的 @JvmName
注解有会错误,可以通过@JvmMultifileClass
把他们合并到一起
Kotlin lateinit 和 by lazy
简述:
lateinit是用于var的不可空类型属性,是声明延迟初始化,需开发者保证赋值的时序正确
by lazy是用于val的属性,by是用于委托的关键字,lazy是提供线程安全的懒加载的实现
lateinit var
适用于你在声明变量时不知道它的初始值是多少的场景,需要保证代码访问时序的正确性
lazy
更适用于,「一个对象的创建需要消耗大量的资源,而我不知道它到底会不会被用到」的场景,lazy
只有在第一次被调用到的时候才会去赋值
lazy
本质是生成了一个SynchronizedLazyImpl
对象,这个对象初始化的时候会持有一个函数的引用,当调用它的value的时候,会去检查是不是初始化过了,如果初始化过了直接返回,没有的话调用传入的函数,获取到返回值之后再返回,从而实现了一个懒加载的效果
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 private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
as T) (_v2
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
}但是上面的代码中,我们可以看到,这个实现它在第一次获取值的时候是有加锁来实现线程安全,但是很多时候我们的代码都是单线程调用的,不需要考虑线程安全问题,这个时候就会有额外的性能开销