确认过眼神,遇上对的人;确认过“坑”点,遇上对的 kotlin
Num 1:方法入参是常量,不可修改
在Kotlin中,参数是常量,这一点可能让 Java 程序员的思维范式有点不太适应。
class Main {
/**
* Kotlin 入参是常量
*/
fun print(a: Int = 1, b: String = "") {
// a = 10; // 错误:Val cannot be reassigned!!!
}
}
Num 2:不想要 Companion
、INSTANCE
关键字?
Java 访问 Kotlin 中定义的静态变量以及静态方法,需要 Companion
关键字。例如:
// Main.kt
class Main {
companion object {
val EMPTY = ""
fun isEmpty(string: String = EMPTY) {
//todo code
}
@JvmField
val FULL_NUMBER = "1234567890"
@JvmStatic
fun isNumber(string: String = FULL_NUMBER) {
//todo code
}
}
}
// Test.java
class Test {
public static void main(String[] args) {
// Java 访问 Kotlin 中的常量
Keng.Companion.getEMPTY();
Keng.Companion.isEmpty("");
KengInstance.INSTANCE.getEMPTY();
// Java 访问 Kotlin 中带有 JvmField 修饰的常量,无需 Companion
String FULL_NUMBER = Keng.FULL_NUMBER;
// Java 访问 Kotlin 中带有 JvmStatic 修饰的方法,无需 Companion
Keng.isNumber("");
}
}
不想出现 Companion
关键字, 可以考虑下 @JvmField
、 @JvmStatic
注解,注解适用于通过 object Main{...}
定义的静态对象场景。
注:特别推荐在Kotlin中使用注解,这种方式,让Java与Kotlin互调,如丝般顺滑。
Num 3:Java 高效的调用 Kotlin 中的重载方法
Kotlin 调用自身中的方法,默认参数是可以不传递参数的,相当于方法重载了。但 Java 调用 Kotlin 的这些方法,还是需要传递默认参数的,例如:
例如: isNumber(string: String = FULL_NUMBER)
:
// Test.java
class Test {
public static void main(String[] args) {
Keng.isNumber("");// 必须要传递个参数
}
}
能不能让 Java 也享受到 Kotlin 默认参数的快乐?可以的,
// Test.java
class Test {
public static void main(String[] args) {
// JvmOverloads 注解的作用,默认实现了 重载 特性
Keng.isNumberWithOverLoads();
Keng.isNumberWithOverLoads("");
}
}
注意:@JvmOverloads与Android View 体系控件搭配使用时,可能会出现一些问题,需要额外关注下。详情传送门:不要总是相信 @JvmOverloads
Num 4:Kotlin 中的判空“姿势”
如下代码,在 Java
中应该还好,但在 Kotlin
中是无法通过编译器编译的。
var nullableString: String? = null
...
fun testNullableString() {
if (nullableString != null) {
var nullableStringLength = nullableString.length // 此处会报错!!!
}
}
会报如下错误:
Error:(9, 40) Kotlin: Smart cast to 'String' is impossible, because 'nullableString' is a mutable property that could have been changed by this time
所以,在kotlin中,正确的判空姿势应该是如下的样子:
fun testNullableString() {
var nullableStringLength = nullableString?.length
}
Num 5:Kotlin 复写 Java 父类中的方法时,有大坑:
第一步:JavaKeng 类中定义了 onDialogCreate
方法,该方法包含一个可空参数:savedInstanceState
,代码如下:
// JavaKeng.java
public class JavaKeng {
public void onDialogCreate(Object savedInstanceState) {
// todo nothings
}
}
第二步:Kotlin 继承并复写 JavaKeng
class Keng : JavaKeng() {
override fun onDialogCreate(savedInstanceState: Any) {// 注意:此处,是Any,不是Any?
super.onDialogCreate(savedInstanceState)
}
}
第三步:利用 Java 多态特性,调用 onDialogCreate,并传入 null
参数
public class KengJava {
public static void main(String[] args) {
JavaKeng keng = new Keng();
keng.onDialogCreate(null);// 注意:空参数
}
}
这里可以有两个问题:
第一个:"Error:overrides nothing"
原因就在 onDialogCreate(savedInstanceState: Any)
方法定义中的:Any
,不是Any?
上。错误日志如下:
Error:(17, 5) Kotlin: 'onDialogCreate' overrides nothing
注意: 不要相信 AS 编译器,使用快捷键 Override Method 时,还是需要额外关注参数是否 Nullable?
第二个:IllegalArgumentException: Parameter specified as non-null is null
就算通过了编译,但在运行时,可能会抛出 Parameter specified as non-null is null
异常,这个异常也是Java
与Kotlin
混合开发中的高频异常。
综上:上述问题,很好解决,只需要在方法参数后面,增加一个?
即可。
override fun onDialogCreate(savedInstanceState: Any?)
Num 6:Kotlin “狠”起来,连TODO
都不放过!
就下面这个平淡朴实无奇的 TODO()
,却会抛出一个 RuntimeException
!
fun testTodo(): Unit {
TODO()
}
这个错误就是:kotlin.NotImplementedError: An operation is not implemented.
,让我们看看 TODO()
的实现:
public inline fun TODO(): Nothing = throw NotImplementedError()
Num 7:is
、as
中的坑
obj is String
之后,作用域之中,类型就已经转换了,有点类似 java 的多态。
fun testAsIs() {
var obj: Any? = null
if (obj is String) {// 方法体内的作用域,obj 就是 String
var length = obj.length
}
}
如下两种错误的 as
写法,会抛出异常:TypeCastException: null cannot be cast to non-null type kotlin.String
//错误写法1,text不是String或为空时,会报异常
var strAble1 = text as String
//错误写法2,text不是String时,同样会报异常
var strAble2 = text as String?
as
的推荐写法:
//正确写法,转换失败自动转换为空对象
var strAble = text as? String
Num 8:Kotlin 中的 Property 的理解
在 Kotlin 中,属性(property)不管有没有 get/set 都称之为 property,而在 Java 中 field + get、set方法一起才能是一个 property。
class Person { var age = 0 }
在 Kotlin 里不需要额外的 get/set 方法,当然你也写不了,因为 Kotlin 已经默认实现了 get/set 。
Kotlin 中可以通过如下方式,复写 field 的 get/set :
var age = 0
set(value){
age = value + 1
}
但,这样写其实是会发生递归,无法赋值成功。
正确的写法应该是:
var age = 0
set(value){
field = value + 1
}
区别就在于 field 上。
关于这块,珠玉在前,就不重复造轮子了。感兴趣的,可以进入传送门 : Howshea 理解 Kotlin 中的属性(property)
因为 实践中的坑总是伴随着最佳实践一起出现,所以,最后附上几则最佳实践,以飨读者:
最佳实践 Num 1:also
关键字
while (bufferReader.readLine().also({ line = it }) != null) {
// do something
}
相当于 Java 中的:
while ((line = bufferReader.readLine()) != null) {
// do something
}
最佳实践 Num 2:takeIf
关键字
// 原代码
if (someObject != null && status) {
doThis()
}
// 最佳实践
someObject?.takeIf{ status }?.apply{ doThis() }
最佳实践 Num 3:单例模式的写法
关于设计模式的写法,珠玉在前,同样不重复制造轮子了,传送门:Kotlin的5种单例写法和java对比。
//Java实现
public class Singleton {
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
//kotlin实现
class Singleton private constructor() {
companion object {
val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Singleton()
}
}
}
此处:需要关注下 双重校验加锁
实现中的 lazy
关键字的用法。传送门
最佳实践 Num4:判空
// Java 实现
String value = TextUtils.isEmpty(content)?"":content;
// Kotlin 实现
val value = content ?: ""
最佳实践 Num5:if/elseif/else
// Java 实现
if(a != null) {
// do something
} else {
// do something
}
// Kotlin 实现
a?.let {
... *//do your non null logic*
} ?: {
... *//do your null logic*
}