Java如何解决NPE问题?
1.函数内对于无效值,更倾向于抛异常处理。特别地,在Java里应该使用专门的自定义Checked Exception。对于经常出现无效值的、有性能需求或在代码中经常使用的函数并不合适。对于自身可取空值的类型,比如说集合类型,通常返回零长度的数组或者集合,虽然会多出内存开销。
2.采用@NotNull/@Nullable标注。对于一段复杂的代码,检查参数是否为空是一件比较耗时的事情。对于不可为空的参数,可以使用 @NotNull来注解,明确参数是否为空,在模块入口加以控制,避免非法null进一步传递。
3.使用专门的Optional对象对可能为null的变量进行装箱。这类Optional对象必须拆箱后才能参与运算,因而拆箱步骤就是提醒使用者必须处理null值的情况。
Kotlin中区分非空(non-null)和可空(nullable)类型。默认是非空类型,通过加上?表示可空类型。
由于null只能被存储在Java的引用类型的变量中,所以在Kotlin中基本数据的可空版本都会使用该类型的包装类型。同样如果使用基本数据类型作为泛型类的类型参数,Kotlin同样使用该类型的包装形式。
例:
data class Glasses(val degreeOfMyopia: Double)
data class Student(val glasses: Glasses?)
data class Seat(val student: Student?)
fun main() {
val glasses = Glasses(100.0)
val student = Student(glasses)
val seat = Seat(student)
println("该位置上学生眼镜的度数:${seat.student?.glasses?.degreeOfMyopia}")
}
该位置上学生眼镜的度数:100.0
Elvis操作符 ?:
Kotlin中与三目运算符相似的用法
val result = seat.student?.glasses?.degreeOfMyopia ?: -1
非空断言 !!
println(seat1.student!!.glasses!!.degreeOfMyopia)
Kotlin可空类型实现
Kotlin
fun payPrint(str: String?) {
println(str?.length)
}
转Java
public static final void payPrint(@Nullable String str) {
Integer var1 = str != null ? str.length() : null;
System.out.println(var1);
}
通过参数上标注@Nullable。这样做的原因:
1.兼容Java老版本(兼容Android)。
2.实现Java与Kotlin的100%转换。
3.性能上达到最佳。
Kotlin中抛出异常
val student1 = Student(null)
val seat1 = Seat(student1)
val result1 = seat1.student?.glasses?.degreeOfMyopia ?:throw NullPointerException("some reasons")
let的概念
调用某对象的let函数,该对象会作为函数的参数,在函数块内可以通过it指代该对象。返回值为函数块的最后一行或指定return表达式。
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
类型检查
在Java中使用A instanceof T 来判断A是T或者T的子类的一个实例。而在Kotlin中,使用is关键字来判断。
fun main() {
val str = "Pay"
when (str) {
is String -> println(str.length)
!is String -> println("Not a String")
}
}
3
类型智能转换
Smart Casts可以将一个变量的类型转变为另一种类型,它是隐式完成的。其实是Kotlin编译器帮助我们做出了转换。
当且仅当Kotlin的编译器确定在类型检查后该变量不会再发生改变,才会产生Smart Casts。
val stu: Any = Student(Glasses(666.0))
if (stu is Student) {
println(stu.glasses?.degreeOfMyopia)
}
对比下面两段代码(一个用val修饰,一个用var修饰)
data class PayGlasses(val degreeOfMyopia: Double)
data class PayStudent(val glasses: PayGlasses?)
data class PaySeat(val student: PayStudent?)
class PayKot {
//正确,编译器不会报错
val stu: PayStudent? = PayStudent(PayGlasses(999.0))
fun dealStu() {
if (stu != null) {
println(stu.glasses)
}
}
}
class PayKot {
var stu: PayStudent? = PayStudent(PayGlasses(999.0))
fun dealStu() {
if (stu != null) {
//错误,编译器会报错
println(stu.glasses)
}
}
}
//也可以使用let来修改
class PayKot {
var stu: PayStudent? = PayStudent(PayGlasses(999.0))
fun dealStu() {
stu?.let {
println(it.glasses)
}
}
}
在上面的代码中,用val修饰的stu是可以保证线程安全的,所以编译器不会报错。
用var修饰的stu,意味着在判断(stu != null)之后,stu在其他线程中还是被修改的,所以编译器会报错。
通过let函数也可以解决这个问题。
Kotlin中使用as关键字来实现强制转换。
关于as关键字的使用注意点:
下面这种写法是不安全的,如果getStudent()返回null,则会导致类型转换异常。
//可能产生类型转换异常
val stu: PayStudent? = getStudent() as PayStudent
通过as?实现安全的转换。此时如果stu为空,不会抛出异常,而是会返回null。
val stu: PayStudent? = getStudent() as? PayStudent
可以配合泛型封装一个类型转换的方法
//目标是将任意不为空的类型转换为目标类型T,因为可能转换失败,则返回类型为T?
fun <T> cast(original: Any): T? = original as? T
但还是会有一个问题,观察下面代码(会报错)
java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
fun <T> cast(original: Any): T? = original as? T
fun main() {
val ans = cast<String>(123456L)
}
这是类型擦除造成的影响。
解决方案如下:
Kotlin 中使用关键字reified,我们可以理解为“具体化”,利用它,我们可以在方法体内访问泛型指定的JVM对象(注意,需要方法前加上inline修饰)。
inline fun <reified T> cast(original: Any): T? = original as? T
fun main() {
val ans = cast<String>(123456L)
println(ans)
}
null
参考Kotlin核心编程