前言: 可空性是kotlin类型系统提供的功能, 帮助你避免
NullPointerException
是一种可以为 null
的类型, 本质是下面这样:
Type? == Type or null
var str: String? = null
说白了, 你就把他当作一种新的类型就好, 这样的话, 如果遇到
var a: Int? = 10 var b: Int = a // 报错
- 1
- 2
这面这种情况时, 不会觉得诧异, 毕竟是不同的类型不是么???
在不影响程序运行性能的前提下, 显示的帮助程序员避免空指针异常 NullPointerException
可空类型在编译期间, 就把空指针异常解决了, 在运行期间不做任何操作, 所以不影响运行时性能
在java中这样容易出现空指针异常
int strLen(String s) {
return s.length(); // 这句话无法确定 s 是否为 null
}
在实际的java项目, 都需要 if
判断
// 可以用 三目运算符, 一行解决, 但也很麻烦
int strLen(String s) {
int len = 0;
if (null == s || (len = s.length()) <= 0) {
throw new RuntimeException("字符串长度为空")
}
return len;
}
当然 jdk1.8
之后出现的 Optional
, 但还是麻烦的, 不仅使代码变得冗长而且还存在性能问题
int strLen(String s) {
return Optional.ofNullable(s).orElse("").length();
}
使用 kotlin 重写这个函数前需要程序员主动判断该函数是否接受实参为空的情况, 如果需要支持的话,
fun strLen(s: String?) = s?.length
在上面代码中, s?.length
如果 s
为 null
的话, 则该函数直接返回 null
, 函数调用者 可以借助返回值 null
使用 if
判断是否为空
如果实参一定不为 null
的话, 则
fun strLen(s: String) = s.length
对了, 和前面的 when
的 is Int
智能转换一样, 可空类型也存在智能转换
var a: String? = "zhazha"
var b: String
if (a != null) {
b = a // 这行代码不会报错
println(b)
}
?.
前面的示例代码中 s?.length
会发现 ?
运算符, 这种方式相当于
if (s == null) null else s.length
如果 s == null
的情况下 整个 s?.length
表达式的值为 null
, 在该表达式为 null
的情况下, 会出现
val len: Int? = s?.length
// 👆
接收该函数返回值的变量类型也应该是 可空的, 毕竟结果可能是 null
所以使用安全调用操作符?
, 其接收结果的变量也需要可空操作符
另外 ?
运算符还可以链式调用, 比如:
val name:String? = person?.children?.name
只要有一步骤结果为 null
, 后面的代码不再运行, 整个表达式的结果为 null
?
这种方式是线程安全的
?:
val firstName: String? = "zhazha"
val lastName: String = firstName ?: ""
可以看到使用这种方式之后 ?
运算符消失了
类似于:
if (firstName == null) "" else firstName
Elvis 还是这样:
val lastName: String = firstName ?: throw Exception("错误")
if (firstName != null) {
val lastName: String = firstName
}
这种方法在你觉得代码可读性比较低时,使用, 但是有个前提,
firstName
不为共享变量
(多线程的共享变量), 否则还是会报错
!!.
使用这种方式确实可以脱下?
外衣, 但对于空指针的检测直接关闭了, 表达式中的变量是否会发生空指针异常已经不管了
这里的“已经不管了”,是错的。正确的说法是
null!!
如果对象本身就是null
直接抛出空指针异常,所以!!
是一种不负责任的行为,除非你能保证该变量百分百不会是null
,最好别用,可以使用?:
代替
val firstName: String? = null
val lastName: String = firstName
这种方式不推荐使用, 除非你能保证该值绝对不为空, 比如: 不使用
object
定义的单例
这些函数都能脱下 ?
外衣
fun main(args: Array<String>) {
val firstName: String? = null
// checkNotNull(firstName)
// checkNotNull(firstName) { "firstName 为空" }
// requireNotNull(firstName) { "firstName 为空" }
// check(firstName != null)
require(firstName != null)
val s: String = firstName
}
如果要给函数类型添加可空性,
val funType: (() -> T)?
这样做
as?
前面的章节学过, as 作为强转操作符, 在使用的过程中可以 配合 is 强制转换, 但如果类型转化不成功就会报ClassCastException
所以kotlin创造了 as?
使用方法
private fun sum(a: Any, b: Any) {
val c: Int? = a as? Int
val d: Int? = b as? Int
}
在例子中, 如果 Any 参数指向的类型不是 Int , 则返回 null 给 c 变量, 否则强转成功
一般 as?
配合 ?:
使用
private fun sum(a: Any, b:Any): Int {
val c: Int = a as? Int ?: 0
val d: Int = b as? Int ?: 0
return c + d
}
let
函数let
函数源码:
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
可以看出它就是个扩展函数
fun main(args: Array<String>) {
val str:String? = null
println(str?.let { it.length + 100 }) // null
}
打印出了 null
, str == null
, 所以 str? == null
后面的let
函数将不执行, 直接返回 null
但是我们需要 null
的时候等于 0
最终要打印 100
fun main(args: Array<String>) {
val str:String? = null
println(str.let { (it?.length ?: 0) + 100 })
}
看到代码中的
str.let
了么?str == null
但是str.let
却不会报错? 看的出来 扩展函数 的优势了么?null.let
不会报错, 了解扩展函数的本质后, 会发现不报错也合理, 扩展函数仅仅是把目标对象的this
当作 形式参数 , 但这里的this
是null
, 传递一个等于null
的参数没问题吧???
为可空类型定义扩展函数处理 null
问题
val str: String? = null
if (str.isNullOrBlank()) {
throw Exception("str == null or str is blank")
}
源码就类似这样: return this == null || this.isBlank()
可以看的出来 str == null
而 null
能调用 null.isNullOrBlank()
(null直接调用 isNullOrBlank
会报错, 但 把 null
赋值给 str
, 再调用 isNullOrBlank
不会报错)
只有扩展函数才能做到这一点,普通成员方法的调用是通过对象实例来分发的,因此实例为
null
时(成员方法)永远不能被执行。
lateinit
很多时候, 成员属性的初始化未必全部都需要在构造函数内完成, 看下面这段代码的成员属性 a
private class MyClass(val b: Int) {
var a: Person // 报错
constructor(a: Person, b: Int) : this(b) {
this.a = a
}
init {
// init balabala
}
}
这里的 a
报错, 主要的问题是 kotlin 对象的初始化顺序是
调用主构造函数
=> 主构造外的成员属性或者init代码块
(根据这俩的定义顺序判断) => 再调用次构造函数
比如
b
在主构造函数内, 而a
在主构造函数外
次构造函数在构建一个对象的时候, 会调用两个构造函数, 一个是主构造函数, 另一个是次构造函数(在有主构造函数的前提下, 如果没有, 类里面全都是次构造函数则不然)
而 主构造函数外属性
和 init代码块
在构造一个对象时, 都会被编译器放入到 主构造函数体内
// 假设这是主构造函数
constructor(b: Int) {
this.b = b
// 上面就是主构造全部的内容
// 接下来是init和主构造函数外属性的内容
this.a = ? // error, 在初始化变量 a 的时候不清楚要给它初始化成什么???? 所以报错了
// init balabala
// 然后再调用次构造函数(如果你使用次构造函数构造一个对象的话)
}
// 然后调用次构造函数
constructor(a: Int) {
this.a = a // 在次构造函数初始化时, 主构造函数报错了, 次构造函数来不及构建一个对象
}
遇到这种情况一般都解决方案都是 var a: Int = 0
给它初始化, 但是在一些架构中, 人家有专门的初始化方案, 不需要程序员主动帮助初始化, 比如: Spring
这时候就需要 lateinit
关键字
private class MyClass(val b: Int) {
lateinit var a: Person
constructor(a: Person, b: Int) : this(b) {
this.a = a
}
init {
// init balabala
}
}
但是这关键字有限制的:
val
属性, 只能修饰 var
Int Double Float Long
之类的属性lateinit
修饰的对象是否已经被初始化private class Player {
private lateinit var equipment: String
fun ready() {
equipment = "sharp knife"
}
fun battle() {
if (this::equipment.isInitialized) {
println(equipment)
}
}
}
class Player {
val config: String by lazy { loadConfig() }
fun loadConfig(): String {
println("load Config...")
return "xxxxxxxxx"
}
}
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
lazy 后面传入的是 函数类型 , 一个无参数的返回 T 类型的函数类型 () -> T
类型参数传递可以是空的
fun <T> printHashCode(t: T) {
print(t?.hashCode())
}
fun main(args: Array<String>) {
printHashCode(null)
}
类型参数传递的是 T
没有任何的 ?
, 但是仍然可以传递 null
, 这时在函数内部如果没写上 t?
那么就会报 空指针异常
null
的类型是Any?
kotlin调用 java 的函数时, 无法判断 java 的参数是否为 可空性 , 所以专门推出了 平台类型
java的平台类型 = kotlin的可空类型 or kotlin的非空类型
这项判断由程序员自主判断
在java下, 创建 Person
public class Person {
private final String name;
public String getName() {
return name;
}
public Person(String name) {
this.name = name;
}
}
在 kotlin 中使用
fun yellAt(person: Person) {
// println(person.name.toUpperCase() + "!!!") // java.lang.NullPointerException: person.name must not be null
println(person.name?.toUpperCase() + "!!!")
}
fun main(args: Array<String>) {
val person = Person(null)
yellAt(person)
}
person: Person
参数没有 可空性 ?
, 但他是 平台类型
, 程序员可以选择是否按照可空类型判断 person.name?.toUpperCase()
, 也可以按照非空判断 person.name.toUpperCase()
怎么不报错怎么来
kotlin 用 Person!
表示一个来自java平台的 平台类型 , 用户不可以自行使用 !
, 它仅仅是提示程序员 该变量 未知可空性
kotlin 继承重写 java 函数时, 可以选择 类型为 可空的 ,也可以选择类型为 非空的
public interface StringProcessor {
void process(String value);
}
class StringPrinter : StringProcessor {
override fun process(value: String) {
println(value)
}
}
class NullableStringPrinter : StringProcessor {
override fun process(value: String?) {
println(value ?: "")
}
}
kotlin 没有包装类型
var a: Int = 10
val plus = a.plus(1)
java 反编译后:
int a = 10;
int plus = a + 1;
不得不吹一波 kotlin 编译器的强大, 但强大带来的却是编译速度缓慢, 哎~~~
Integer
val list = ArrayList<Int>()
Java:
ArrayList<Integer> list = new ArrayList<Integer>();
null
kotlin的基本数据类型和java的一致, 都不能存储 null
, java的基本数据类型在 kotlin 中不会变成 平台类型 而是直接变成 基本数据类型
kotlin的可空基本数据类型无法翻译成 java 的 基本数据类型, 所以任何可空类型, 最终都会变成 包装类型
class Person(val name: String, val age: Int?) {
fun isOldThan(other: Person): Boolean? = this.age?.let {
other.age?.let { it2 ->
it > it2
}
}
}
fun main(args: Array<String>) {
val person = Person("zhazha", 23)
val person1 = Person("xixix", 21)
val b = person.isOldThan(person1)
if (null == b) println("不清楚") else if (b) println("大于") else println("小于")
}
public final class Person {
@NotNull
private final String name;
@Nullable
private final Integer age;
// 略
}
Int
变量转换成 Long
, 这和java还是有区别, 这样做的好处在于更加的安全可控val a: Int = 100
val b: Long = a // 报错
kotlin 对每个基本数据类型提供了 toXXXX
函数(除了 Boolean
), 这种显示的转换可以大范围转小范围, 也可小范围转大范围
在 java 中, 包装类型的比较会出现下面这种问题
new Integer(42).equals(new Long(42)) // false
这俩明明都是 42
但不相等, 在 java 中 equals
有判断类型的, 所以会返回false
, 如果需要则要转换成相同类型
在 kotlin 中, 如果变量没有转换到同一个类型, 也无法比较
需要转换
Any
类似于 java 的Object
对象, 是 kotlin 所有非空类的共有根类, 而不论是空类还是非空类的所有类都可以传给 Any?
Any
有很多 Object
的函数, 但并不是所有, 有些函数 比如 wait / notify
函数只能通过 Any
强转成 Object
来调用该函数
Unit
和 void
的差别在于:
在 kotlin 中, Unit
是一个类, Unit
可以当作函数的参数, 平时使用时 Unit
会被转化成 java 的 void
Unit
不需要主动的 return
, 会隐式的返回 Unit
public object Unit {
override fun toString() = "kotlin.Unit"
}
Nothing
没有值, 只有被当作函数返回值或者被当作泛型函数返回值的类型参数使用才会有意义
源码:
public class Nothing private constructor()
使用:
fun fail(message: String): Nothing {
throw Exception(message)
}
List<Int?>
和 List<Int?>?
wait / notify函数只能通过
Any强转成
Object` 来调用该函数
Unit
和 void
的差别在于:
在 kotlin 中, Unit
是一个类, Unit
可以当作函数的参数, 平时使用时 Unit
会被转化成 java 的 void
Unit
不需要主动的 return
, 会隐式的返回 Unit
public object Unit {
override fun toString() = "kotlin.Unit"
}
Nothing
没有值, 只有被当作函数返回值或者被当作泛型函数返回值的类型参数使用才会有意义
源码:
public class Nothing private constructor()
使用:
fun fail(message: String): Nothing {
throw Exception(message)
}
List<Int?>
和 List<Int?>?