企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ### 前言 **Kotlin 引以为豪的是和Java 的互操作性,而你知道Java 的类型系统是不支持可空性的。那么当你混合使用Kotlin和Java 时会发生什么?会不会失去所有的安全性?或者每个值都必须检查是否为null ?** 有些时候Java 代码包含了可空性的信息,这些信息使用注解来表达。当代码中出现了这样的信息时,Kotlin 就会使用它。因此Java中的`@Nullable String` 被Kotlin 当作String?,而`@NotNull String` 就是String,如图所示。 根据Java 类型的注解, Java类型会在Kotlin 中表示为可空类型和非空类型 ![](https://box.kancloud.cn/f9aa0d226628c044cab51a52fb79684e_391x169.png) Kotlin可以识别多种不同风格的可空性注解,包括JSR-305 标准的注解(在javax.annotation 包之中)、Android 的注解( android.support.annotation )和JetBrains 工具支持的注解(org.jetbrains.annotations )。 如果这些注解不存在会发生什么?这种情况下, Java 类型会变成Kotlin 中的平台类型。 ### **平台类型** **平台类型本质上就是Kotlin不知道可空性信息的类型。既可以把它当作可空类型处理, 也可以当作非空类型处理**(如图所示)。这意味着,你要像在Java中一样,对你在这个类型上做的操作负有全部责任。编译器将会允许所有的操作,它不会把对这些值的空安全操作高亮成多余的,但它平时却是这样对待非空类型值上的空安全操作的。如果你认为这个值为null,在使用它之前可以用它和null比较。如果你认为它不为null,就直接使用它。就像在Java中一样,如果你错误地理解了这个值,使用的时候就会遇到NullPointerException 。 **Java 类型在Kotlin 中表示为平台类型,既可以把它当作可空类型也可以当作非空类型来处理** ![](https://box.kancloud.cn/a2623fb1961d8b4e5c6f9c6ae4a05ebd_356x130.png) **示例** Java代码 ~~~ public class Phone { private final String name; public Phone(String name){ this.name=name; } public String getName(){ return name; } } ~~~ kotlin代码 ~~~ fun main(args: Array<String>) { yellAt(Phone(null)) } fun yellAt(phone: Phone){ println(phone.name.toUpperCase()+"!!!") //toUpperCase()调用的接收者phone.name为null , 所以这里会抛出异常 } ~~~ 运行结果 ``` Exception in thread "main" java.lang.IllegalStateException: phone.name must not be null at A基础.可空性和JavaKt.yellAt(可空性和Java.kt:8) at A基础.可空性和JavaKt.main(可空性和Java.kt:4) ``` >[info]【注意】:这里你看到的不是一个干巴巴的NullPointerException ,而是一条更详细的错误消息,告诉你方法toUppercase 不能在null 的接收者上调用。 事实上,对于公有的Kotlin函数,编译器会生成对每个非空类型的参数(和接收者)的检查,所以,使用不正确的参数的调用尝试都会立即被报告为异常。注意,这种值检查在函数调用的时候就执行了,而不是等到这些参数被使用的时候。这确保了不正确的调用会被尽早发现,那些由于null值被传给代码不同层次的多个函数之后,并被这些函数访问时而产生难以理解的异常就能被避免。 另外一个选择是把`getName()`的返回类型解释为可空的并安全地访问它。 修改如下 ~~~ fun yellAtSafe(phone: Phone){ println((phone.name?:"Anyone").toUpperCase()+"!!!") } ~~~ 运行结果 ``` ANYONE!!! Process finished with exit code 0 ``` >[info]【**注意**】使用Java API 时要特别小心。大部分的库都没有(可空性)注解,所以可以把所有类型都解释为非空,但那样会导致错误。为了避免错误,你应该阅读要用到的Java 方法的文档( 必要时还要查看它的实现〉, 搞清楚它们什么时候会返回null,并给那些方法加上检查。 在Katlin 中不能声明一个平台类型的变量,这些类型只能来自Java 代码,如下面的代码就会报错 ~~~ val i:Int=phone.name ~~~ 会报错如下:`错误: 类型不匹配: 推断类型为字符串但是给的是Int整型` `ERROR: Type mismatch : inferred type is String! but Int was expected` String ! 表示法被Kotlin 编译器用来表示来自Java 代码的平台类型。你不能在自己的代码中使用这种语法。而且感叹号通常与问题的来源无关,所以通常可以忽略它。它只是强调类型的可空性是未知的。 **创建混合的Kotlin和Java 类层级关系时会遇到的一些陷阱** **继承** 当在Kotlin 中重写Java 的方法时, 可以选择把参数和返回类型定义成可空的,也可以选择把它们定义成非空的。例如,我们来看一个Java 中的StringProcessor 接口。 ``` interface StringProcessor { void process(String value); } ``` Kotlin中下面的两种实现编译器都可以接收 ~~~ class StringPrinter:StringProcesser{ override fun process(value:String){ println(value) } } class NullableStringPrinter:StringProcesser{ override fun process(value: String?){ if(value !=null){ println(value) } } } ~~~ >[info]【**注意**】,在实现Java 类或者接口的方法时一定要搞清楚它的可空性。因为方法的实现可以在非Kotlin的代码中被调用, Kotlin 编译器会为你声明的每一个非空的参数生成非空断言。如果Java 代码传给这个方法一个null 值,断言将会触发,你会得到一个异常,即便你从没有在你的实现中访问过这个参数的值。