Kotlin 相比 Java 有哪些优点
- Kotlin 完全兼容 Java
- Kotlin 更简洁
- 去掉了语句结尾的分号
- 支持字符串模版、扩展方法、重写操作符、中缀表达式、头等函数 & 高阶函数、提供简化的Lambda 表达式语法
- 支持类型推导、智能判空、不强制捕获 CheckedException
- Kotlin 更安全
- 支持空类型检测
- 不支持隐式转换
- Kotlin 更强大
- 支持 Coroutines、Flow 事件流(异步编程、响应式编程)
- 支持具体化泛型
Kotlin数据类型 | Boolean | Byte-1 | Short-2 | Int-4 | Long-8 | Float-4 | Double-8 |
Java数据类型 | boolean | byte-1 | short-2 | int-4 | long-8 | float-4 | double-8 |
在 Kotlin 中,== 和 equals 等价,=== 用来比较对象的引用值
val a: Int = 999
val b: Int? = a
val c: Int? = a
Log.e("ebb", "${b === c} ${b == c}") // false true
val d: Int = 999
val e: Int = d
val f: Int = d
Log.e("ebb", "${e === f} ${e == f}") // true true
val o: Int? = 999
val p: Int? = o
val q: Int? = o
Log.e("ebb", "${p === q} ${p == q}") // true true
val x: Int = 100
val y: Int? = x
val z: Int? = x
Log.e("ebb", "${y === z} ${y == z}") // true true
//自动装箱、Integer缓存
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
单引号和双引号支持字符串模版,不支持特殊字符;三引号支持字符串模版,支持特殊字符
Log.e("scrutiny", """ aaa """.trim())
Log.e("scrutiny", """ aaa """.trimMargin("a"))
Log.e("scrutiny", """
aaa
bbb
ccc""".trimIndent())
//14562-14562/com.example.rxjava2 E/scrutiny: aaa
//14562-14562/com.example.rxjava2 E/scrutiny: aa
//14562-14562/com.example.rxjava2 E/scrutiny: aaa
// bbb
// ccc
空类型安全
- Kotlin 空类型安全:?、!!、?:
- Kotlin 中会导致空指针的情况:!!、lateinit、访问Java变量、Gson库解析
- 解决 Kotlin 访问 Java 的空指针问题:@NonNull、@Nullable
- 解决 Gson 反序列化为空的问题:成员设置为可空类型;为全部成员增加默认值
Kotlin 中val、var和 Java 的对应关系
//Kotlin
const val top_level_const_val_i = 1
val top_level_val_i = 2
var top_level_var_i = 4
class MainActivity : Activity() {
val val_i = 3 // kotlin类成员默认访问权限是public
}
//Java
public final class MainActivity extends Activity {
private final int val_i = 3;
public final int getVal_i() {
return this.val_i;
}
}
public final class MainActivityKt {
public static final int top_level_const_val_i = 1;
private static final int top_level_val_i = 2;
private static int top_level_var_i = 4;
public static final int getTop_level_val_i() {
return top_level_val_i;
}
public static final int getTop_level_var_i() {
return top_level_var_i;
}
public static final void setTop_level_var_i(int var0) {
top_level_var_i = var0;
}
}
头等函数
函数可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回,可以像操作任何其他非函数值一样操作函数
函数类型
- 所有函数类型都由一个圆括号括起来的参数列表以及一个返回类型表示:(A, B) -> C 表示接受 A 与 B 两个类型的参数并返回 C 类型值的函数。 参数类型列表可以为空,如 () -> A。Unit 返回类型不可省略
- 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用
- 挂起函数属于特殊的函数类型,通过 suspend 修饰符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C
- 函数类型也包含参数名:(a: A, b: B) -> C
匿名函数和 Lambda 表达式的区别
val lambda: (Int) -> Int = { x -> x * x }
val anonymousFunction: (Int) -> Int = fun(x: Int): Int {
return x * x
}
- 匿名函数需要通过 return 语句指定返回值;Lambda 表达式默认返回最后一个表达式对应的值
- 匿名函数中 return 是从自身返回;Lambda 表达式中的 return 语句是返回自身所在的函数(只有 inline 方法的 Lambda 参数才能使用 return,其他的都是 return@Xxx)
-
inline fun performOperation(operation: () -> Unit) { println("Before operation") operation() println("After operation") } fun main() { performOperation { println("Inside lambda") return // 从 performOperation 函数返回,而不是从 Lambda 表达式返回 } println("After performOperation") } //Before operation //Inside lambda fun performOperation(operation: () -> Unit) { println("Before operation") operation() println("After operation") } fun main() { performOperation { println("Inside lambda") return@performOperation // 从 performOperation 函数返回,而不是从 Lambda 表达式返回 } println("After performOperation") } //Before operation //Inside lambda //After operation //After performOperation
- Lambda 可以简化代码
- Lambda 作为方法调用的最后一个参数可以提到括号外
- 如果调用的方法只有一个 Lambda 参数,可以省略括号
- 如果匿名内部类实现的 Java 接口只有一个抽象方法,则可以通过 Lambda 表达式替代
闭包和匿名内部类的区别
- 闭包和匿名内部类都可以捕获当前环境的局部变量,且和 Java 不同,匿名内部类也能捕获非 final 类型的局部变量
- Kotlin 通过 Ref 类型将栈类型数据转为堆存储,下例中,被闭包引用的 Int 局部变量,会被封装成 IntRef 类。IntRef 里面保存着 Int 变量,原函数和闭包都可以通过 IntRef 来读写 Int 变量。Kotlin 正是通过这种办法使得局部变量可修改(栈数据转换为了堆数据)
// 基本方法
fun sum(arg1: Int, arg2: Int): Int {
return arg1 + arg2
}
fun sum(arg1: Int, arg2: Int): Int = arg1 + arg2
//lambda表达式
private val sumLambda: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
// 匿名函数
private val sumFunc = fun(x: Int, y: Int): Int {
return x + y
}
args.forEach {
print(it)
}
args.forEach(::print) //入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入
// var用lateinit延迟初始化、val用by lazy
lateinit var mContext = context
val mContext: Activity by lazy {
context as Activity
}
// first-class Function && Enclosure && 闭包通过XXXRef实现
val function1 = returnFun()
val function2 = returnFun()
println(function1()) // 0
println(function1()) // 1
println(function2()) // 0
println(function2()) // 1
private fun returnFun(): () -> Int {
var count = 0
return { count++ }
}
// Kotlin果然各种黑魔法,以为是栈上的数据,编译之后是放在堆上的Ref对象
public final Function0 testClosure() {
final IntRef count = new IntRef();
count.element = 1;
return (Function0)(new Function0() {
public Object invoke() {
return this.invoke();
}
public final int invoke() {
IntRef var10000 = count;
int var1;
var10000.element = (var1 = var10000.element) + 1;
return var1;
}
});
}
Kotlin 运算符重载 && 扩展方法
运算符是 Kotlin 中的特殊字符,每个运算符都会对应一个方法,中缀表达式不能使用 Kotlin 中的运算符。运算符大全:掘金
// 运算符重载:运算符本质上就是一个方法,比如,+ 对应plus,() 对应 invoke,in 对应 contains 等
class Complex(var real: Double, var imaginary: Double) {
operator fun plus(orther: Complex): Complex {
return Complex(real + other.real, imaginary + other.imaginary)
}
}
fun main() {
val c1 = Complex(3.0, 4.0)
val c2 = Complex(2.0, 5.0)
print(c1 + c2)
}
//运算符重载、扩展方法
operator fun String.times(int: Int): String {
return ""
}
println("abc".times(6))
println("abc" * 6)
// 扩展属性不能初始化,类似接口属性
val String.a: String get() = "abc"
Kotlin 中缀表达式
中缀表达式只有一个参数,且用 infix 修饰的方法
// mapOf("act" to "x","act2" to "y")
/** Performs a bitwise AND operation between the two values. */
public infix fun and(other: Long): Long
/** Performs a bitwise OR operation between the two values. */
public infix fun or(other: Long): Long
/** Performs a bitwise XOR operation between the two values. */
public infix fun xor(other: Long): Long
class Book {
infix fun on(any: Any): Boolean {
return false
}
}
Kotlin 分支表达式
// if、when
val mode = if (args.isNotEmpty()) true else false
- 具名参数:传参数时指定了参数名称
- 变长参数:vararg args: String,在 Java 中只能放在最后,但由于 Kotlin 中有具名参数,可以把变长参数放在中间
- 小技巧: val array = intArrayOf(1,3,4,5) ,*array 意思是把 array 展开,把所有元素传进去
抽象类和接口的区别
- 接口不能保存状态,抽象类可以保存状态
- 我们能实现多个接口,但是只能继承一个抽象类
- 接口没有构造方法,抽象类有构造方法
interface MyInterface {
val i: Int
fun bar()
fun foo() {
println()
}
}
- 普通父类需要 open 才能被继承,父类方法和属性需要 open 才能被覆写
- 接口、接口方法、抽象类、抽象类中的抽象方法默认为 open
- 覆写方法必须使用 Override 关键字
- Object 类可以实现单例模式,只有一个实例、不能自定义构造方法
- 伴生对象(companion object)可以实现类方法、类变量
- Kotlin 内部类默认为静态内部类,如果想要非静态内部类,需要 inner 关键字声明
- Kotlin 匿名内部类可以既实现接口又继承别的类,但 Java 中只能实现接口
Kotlin 可见性修饰符和 Java 可见性修饰符的区别
- Kotlin 与 Java 的默认修饰符不同,Kotlin 是 public,而 Java 是 default
- Kotlin 中有一个独特的修饰符 internal
- Kotlin 可以在文件中声明方法及常量,同样支持可见性修饰符
- Java 中除了内部类可以用 private 修饰以外,其他类都不允许 private 修饰,而Kotlin可以
- Kotlin 和 Java 中的 protected 的访问范围不同,Java 中是包、类及子类可访问,而 Kotlin 只允许类及子类
Java 可见性修饰符
Java 修饰符(默认 default) | 含义 |
---|---|
public | 全局可见 |
protected(只能用来修饰内部类) | 类、子类、包内可见 |
default | 类、包内可见 |
private(只能用来修饰内部类) | 只有类内可见 |
Kotlin 可见性修饰符
Kotlin 修饰符(默认 public) | 含义 |
---|---|
public | 全局可见 |
protected(只能用来修饰内部类) | 类、子类可见 |
internal | 模块内可见 |
private | 类内修饰只有本类可见,类外修饰文件内可见 |
Checked Exception
- Kotlin 并没有禁止捕获 CE,只是不强制要求你去捕获而已
- 理论上 IO 流应该总是能正常工作的,且在 catch 逻辑中一般不会有什么有意义的处理
- 还是可以通过 Thread.UncaughtExceptionHandler 来捕获全局异常
Kotlin 属性代理
声明的属性其实就是个傀儡,真实调用的是代理类的getValue和setValue方法
class Delegate {
val a by lazy {
"string"
}
val b by X()
}
class X {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "hello"
}
}
class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
}
// Lazy实现
public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}
Kotlin 数据类
数据类默认实现了 copy、toString、componentN 等一系列辅助方法。如果数据类用于 Json 解析,需要将变量设置为可空,或都赋予默认值
data class Country(val id: Int, val name: String)
val china = Country(110, "china")
println(china)
println(china.component1())
println(china.component2())
val (id, name) = china //数据类可以用括号来接收
class ComponentX {
operator fun component1(): String {
return "您好,我是"
}
operator fun component2(): Int {
return 1
}
operator fun component3(): Int {
return 1
}
}
val componentX = ComponentX()
val (a,b,c) = componentX
println("$a$b$c")
密封类和枚举类的区别
- 枚举值只能有统一的属性,密封类的子类可以有不同的属性
enum class DeliveryStatus(val trackingId: String?) {
PREPARING(null),
DISPATCHED("27211"),
DELIVERED("27211"),
}
interface IAnonymous
abstract class Anonymous : IAnonymous
sealed class DeliveryStatus : Anonymous { }
class Preparing() : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
class Delivered(val trackingId: String, val receiversName: String) : DeliveryStatus()
- 枚举类可以有普通方法和抽象方法,和属性类似,枚举值不能有自己的独有方法;密封类的子类可以有不同的方法
enum class DeliveryStatus {
PREPARING {
override fun cancelOrder() = println("Cancelled successfully")
},
DISPATCHED {
override fun cancelOrder() = println("Delivery rejected")
},
DELIVERED {
override fun cancelOrder() = println("Return initiated")
};
abstract fun cancelOrder()
}
sealed class DeliveryStatus
class Preparing : DeliveryStatus() {
fun cancelOrder() = println("Cancelled successfully")
}
class Dispatched : DeliveryStatus() {
fun rejectDelivery() = println("Delivery rejected")
}
class Delivered : DeliveryStatus() {
fun returnItem() = println("Return initiated")
}
- 枚举类只能继承接口,不能被其他类继承;密封类能继承其他类,也能被其他类继承
- 枚举值是 Object 类型,不能创建多实例,GC 时不会回收内存;密封类的子类可以创建多个实例,和普通类一样,会进行 GC 回收
- 枚举类默认继承了 java.lang.enum,实现了 Serialization、equals 等方法;密封类需要手动实现对应方法
Sealed 类进化史
- 在 Kotlin 1.0 时,子类只能在 Sealed Classes 内部中使用,因为 Sealed class 会被编译成 abstract class,并且默认的构造方法被私有化了,所以子类必须嵌套在 Sealed Classes 类中
- 在 Kotlin 1.1 时,允许顶级的 Sealed Classes 和它顶级子类在同一个文件中,因为编译器会自动生成了一个公有的构造方法,在子类的构造方法中调用了父类公有的构造方法,而这些都是 Kotlin 编译器帮我们做的
- 在 Kotlin 1.5 中,放宽了对 Sealed Classes 限制,只需要保证 Sealed Classes 和它的子类,在同一个包名和 Module 下面即可,这些都是 Kotlin 编译器帮我们做的
Kotlin 扩展方法能访问私有变量吗
扩展方法无法访问私有成员,原因是:扩展方法只是把要扩展的类作为参数传入方法中
private fun test() {
Sparrow().wzh()
}
private fun Sparrow.wzh(): String {
return this.s + "NB"
}
class Sparrow {
var s: String = "init"
}
// 反编译后的 Java
public final class Sparrow {
@NotNull
private String s = "init";
@NotNull
public final String getS() {
return this.s;
}
public final void setS(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.s = var1;
}
}
public final class MainActivityKt {
private static final void test() {
wzh(new Sparrow());
}
private static final String wzh(Sparrow $this$wzh) {
return $this$wzh.getS() + "NB";
}
}
高阶函数:函数作为参数或者返回值的函数
args.forEach(::println) //包级函数
也就是args.forEach(Kotlin.io.Console::println)
args.filter(String::isNotEmpty) //类名::方法
val pdfPrinter = PdfPrinter() //需要声明实例,否则println方法签名是两个参数(this、Any)
args.forEach(pdfPrinter::println) //实例::方法
class PdfPrinter {
fun println(any: Any) {
kotlin.io.println(any)
}
}
// 常见高阶函数-为简洁而生:forEach、filter、map、flatMap、reduce、
// fold、joinToString、takeWhile、let、apply、with、use
val list = listOf(1,2,3,4,5)
val newList = list.map { it * 2 + 3 }
val newList2 = list.map(Int::toDouble)
常用高阶方法:Standard.kt
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
复合函数:f(g(x)):Function的中缀扩展方法
infix fun <P1,P2,R> Function1<P1,P2>.andThen(function: Function1<P2,R>): Function1<P1,R> {
return fun(p1:P1): R{
return function.invoke(this.invoke(p1))
}
}
val add5AndMultiplyBy2 = add5 andThen multiplyBy2
inline 关键字
内联方法可以减少函数调用开销,高阶方法还可以减少创建临时变量开销
// Kotlin
fun main(args: Array<String>) {
multiplyByTwo(5) {
println("Result is: $it")
}
}
fun multiplyByTwo(num: Int, lambda: (result: Int) -> Unit) : Int {
val result = num * 2
lambda.invoke(result)
return result
}
// Java
public static final void main(@NotNull String[] args) {
multiplyByTwo(5, (Function1)null.INSTANCE);
}
public static final int multiplyByTwo(int num, Function1 lambda) {
int result = num * 2;
lambda.invoke(result);
return result;
}
内联方法副作用
- 内联方法无法引用所在类的私有属性
- 高阶方法设置为内联时,Lambda 参数中的 return 语句会从调用的方法体中返回
// Kotlin
inline fun multiplyByTwo(num: Int, lambda: (result: Int) -> Unit) : Int {
val result = num * 2
lambda.invoke(result)
return result
}
fun main(args: Array<String>) {
println("Start of main")
multiplyByTwo(5) {
println("Result is: $it")
return
}
println("End of main")
}
noinline 关键字
可以指定特定 Lambda 参数不参与内联
inline fun multiplyByTwo(num: Int,
lambda1: (result: Int) -> Unit,
noinline lambda2: () -> Unit): Int {
val result = num * 2
lambda1.invoke(result)
lambda2.invoke()
return result
}
crossinline 关键字
限制高阶内联方法的 Lambda 表达式使用 return 语句
reified 关键字
首先说一下类型擦除带来的问题:类转换异常
类转换问题可以通过传入 Class 参数来解决
fun <T> Bundle.plus(key: String, value: T, clazz: Class<T>) {
when(clazz) {
Long::class.java -> putLong(key, value as Long)
String::class.java -> putString(key, value as String)
Char::class.java -> putChar(key, value as Char)
Int::class.java -> putInt(key, value as Int)
}
}
由于上面的显式传递Class信息比较麻烦和崩溃,我们有时候会增加更多的方法
class Bundle {
fun putInt(key: String, value: Int) { }
fun putLong(key: String, value: Long) { }
fun putString(key: String, value: String) { }
}
对应 Kotlin 通过 reified 来解决这个问题,主要分为两步
- 在泛型类型前面增加 reified 关键字
- 在方法前面增加 inline 关键字
inline fun <reified T> Bundle.plus(key: String, value: T) {
when(value) {
is Long -> putLong(key, value)
is String -> putString(key, value)
is Char -> putChar(key, value)
is Int-> putInt(key, value)
}
}
具体原理
- Kotlin 编译器会将 reified 方法 asType 内联到调用的地方
- 方法被内联到调用的地方后,泛型 T 会被替换成具体的类型
序列生成器(惰性)
fun main(args: Array<String>) {
listOf(1, 2, 3).filter {
print("$it ")
it >= 2
}
print("- ")
listOf(1, 2, 3)
.asSequence()
.map {
print("$it")
it + 1
}
.filter {
print("$it ")
it >= 3
}
}
// 1 2 3 -