Android 房间 returns Null 作为 Non-Null 类型
Android Room returns Null as Non-Null type
我有 Dao
到 return 简单对象。如果对象不存在,房间 return null
,但 Android 应用程序没有崩溃。此外,如果我将该值分配给非空变量,则应用程序中不会发生崩溃。
道:
@Query("SELECT * FROM users WHERE id LIKE :id LIMIT 1")
abstract fun getById(id: Long): User
没有崩溃代码:
doAsync {
val user: User = userDao.getById(999) // user 999 not exist, userDao returns null
uiThread {
if (user == null) {
Timber.d("user is $user") // "user is null" in log
} else {
Timber.d("user is ${user.email}")
}
}
}
我有两个问题:
- Room 怎么可能 return Null 值作为 Non-Null 变量?
- 将 null 赋给 Non-Null 变量的代码怎么可能没有崩溃?
据我了解,您生成的 Dao class 没有在 Java 代码中将 return 值注释为 @Nullable。
@Override
public User getById(long id) {
应该是
@Override
public @Nullable User getById(long id) {
因此您没有看到警告
你没有得到 NullPointerException
的原因很简单:Room 是一个 Java 库,而不是 Kotlin,所以它不知道你的 User
不应该为 null。
当与 Java 代码交互时,Kotlin 引入了一些 null 检查来为您提供该异常,但由于您的接口是用 Kotlin 编写的,它假定这不会成为问题并跳过检查。
我不是 Room 专家,但在快速 google 搜索后,我找不到强制 Room 检查空值的方法。我看到有两种方法可以解决您的 "problem":
- 将
getById
函数改为returnUser?
- 将您的
@Dao
转换为 Java 接口,强制 Kotlin 检查 null。
全部都是关于 Kotlin 如何使用 Java 代码处理边界上的空值。
如果您将 null 从 Java 传递给需要非空值的 Kotlin 方法,您将得到异常。这是通过调用 Kotlin 编译器在方法开头添加的 Intrinsics.checkNotNull
函数来实现的。
例如:
fun hello(who: String): Unit {
println ("Hello $who")
}
变成
public final void hello(@NotNull String who) {
Intrinsics.checkParameterIsNotNull(who, "who");
String var2 = "Hello " + who;
System.out.println(var2);
}
当您从 Kotlin 调用 Java 方法时添加了类似的检查。
但在你的情况下,你有 Kotlin 接口,它在 Java 中由 Room 生成。
所以 Kotlin 编译器无法添加检查,因为它无法控制接口的所有实现。否则它必须在每次调用 Kotlin class 或接口后添加检查,因为它可以在 Java 中实现,这对性能不利。
UPD:在 Room bugtracker https://issuetracker.google.com/issues/112323132 上发现了类似的问题。 Googler 说这是有意为之的行为,如果您编写的查询可以 return 空值,那么您有责任在 dao 接口中将其标记为可为空。
我有 Dao
到 return 简单对象。如果对象不存在,房间 return null
,但 Android 应用程序没有崩溃。此外,如果我将该值分配给非空变量,则应用程序中不会发生崩溃。
道:
@Query("SELECT * FROM users WHERE id LIKE :id LIMIT 1")
abstract fun getById(id: Long): User
没有崩溃代码:
doAsync {
val user: User = userDao.getById(999) // user 999 not exist, userDao returns null
uiThread {
if (user == null) {
Timber.d("user is $user") // "user is null" in log
} else {
Timber.d("user is ${user.email}")
}
}
}
我有两个问题:
- Room 怎么可能 return Null 值作为 Non-Null 变量?
- 将 null 赋给 Non-Null 变量的代码怎么可能没有崩溃?
据我了解,您生成的 Dao class 没有在 Java 代码中将 return 值注释为 @Nullable。
@Override
public User getById(long id) {
应该是
@Override
public @Nullable User getById(long id) {
因此您没有看到警告
你没有得到 NullPointerException
的原因很简单:Room 是一个 Java 库,而不是 Kotlin,所以它不知道你的 User
不应该为 null。
当与 Java 代码交互时,Kotlin 引入了一些 null 检查来为您提供该异常,但由于您的接口是用 Kotlin 编写的,它假定这不会成为问题并跳过检查。
我不是 Room 专家,但在快速 google 搜索后,我找不到强制 Room 检查空值的方法。我看到有两种方法可以解决您的 "problem":
- 将
getById
函数改为returnUser?
- 将您的
@Dao
转换为 Java 接口,强制 Kotlin 检查 null。
全部都是关于 Kotlin 如何使用 Java 代码处理边界上的空值。
如果您将 null 从 Java 传递给需要非空值的 Kotlin 方法,您将得到异常。这是通过调用 Kotlin 编译器在方法开头添加的 Intrinsics.checkNotNull
函数来实现的。
例如:
fun hello(who: String): Unit {
println ("Hello $who")
}
变成
public final void hello(@NotNull String who) {
Intrinsics.checkParameterIsNotNull(who, "who");
String var2 = "Hello " + who;
System.out.println(var2);
}
当您从 Kotlin 调用 Java 方法时添加了类似的检查。
但在你的情况下,你有 Kotlin 接口,它在 Java 中由 Room 生成。 所以 Kotlin 编译器无法添加检查,因为它无法控制接口的所有实现。否则它必须在每次调用 Kotlin class 或接口后添加检查,因为它可以在 Java 中实现,这对性能不利。
UPD:在 Room bugtracker https://issuetracker.google.com/issues/112323132 上发现了类似的问题。 Googler 说这是有意为之的行为,如果您编写的查询可以 return 空值,那么您有责任在 dao 接口中将其标记为可为空。