如何在 RxJava 2 和 Kotlin 中将 null 传递给具有可空类型的 Observable

Posted

技术标签:

【中文标题】如何在 RxJava 2 和 Kotlin 中将 null 传递给具有可空类型的 Observable【英文标题】:How to pass null to an Observable with nullable type in RxJava 2 and Kotlin 【发布时间】:2017-12-30 20:41:20 【问题描述】:

我像这样初始化我的变量:-

 val user: BehaviorSubject<User?> user = BehaviorSubject.create()

但我不能这样做。 IDE 报错:-

user.onNext(null)

这样做,IDE 说你永远不会为空:-

user.filter( u -> u!=null)

【问题讨论】:

您不能 - 即使在 Java 中,.onNext(null) 的计算结果也会为 .onError(new NullPointerException(...)) 在 RxJava 1 中这是可能的。但在 2 中不是。 顺便说一句:您的代码甚至没有编译。请在此处仅发布有效代码。您可以编辑帖子以解决此问题。 @guenhter 如果问题是如何使代码编译,则代码不必编译。 RxJava 实现了不支持空值的反应流规范。您可以使用 github.com/gojuno/koptional 包装这些值。包括 RxJava 的过滤器 【参考方案1】:

正如Guenhter 解释的那样,这是不可能的。但是,我建议使用 Optional 类型的实现,而不是提出空对象模式:

data class Optional<T>(val value: T?)
fun <T> T?.asOptional() = Optional(this)

这使您的意图更加清晰,您可以在函数中使用解构声明:

Observable.just(Optional("Test"))
  .map  (text: String?) -> text?.substring(1)?.asOptional() 
  .subscribe()

在这里使用空对象模式可能会导致比它解决的错误更多。

【讨论】:

是的,我想这是最干净的方法(即使我不太喜欢 Optionals :))。 Optional 的“完整”实现(使用 java.util.Optional 之类的方法)非常短:gist.github.com/ephemient/2dec1165b7993e6a6cd7cdfa005fe277 @ehemient 有趣的方法! 有人能解释一下这段代码吗?我不明白`data class Optional,还有fun T?.asOptional() = Optional(this)。看起来他正在创建一个名为“可选”的数据类,但它后面的 是做什么的? “可选”类是否具有相同类型的参数,类型为“T”的“值”?【参考方案2】:

如果您使用 rxkotlin/rxjava 2.0(我假设是这样),那么答案是:您不能。原因在这里解释。

这是界面的中断。看看Observable接口

public interface Observer<T> 

    /** ... */
    void onSubscribe(@NonNull Disposable d);

    /** ... */
    void onNext(@NonNull T t);

    /** ... */
    void onError(@NonNull Throwable e);

    /** ... */
    void onSubscribe(@NonNull Disposable d);

    /** ... */
    void onNext(@NonNull T t);

    /** ... */
    void onError(@NonNull Throwable e);
...

@NonNull 将被 Kotlin 编译器考虑,因此您不能传递 null。

即使可以,onNext 也会立即抛出错误:

@Override
public void onNext(T t) 
    if (t == null) 
        onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
        return;
    
    ...


如果你真的需要 null 这样的东西,你必须伪造它。例如通过创建User 的静态对象,它代表您的null-元素。

例如

data class User(val username, val password) 

    companion object 
        val NULL_USER = User("", "")
    

...
val user = BehaviorSubject.create<User>()
...
user.onNext(User.NULL_USER)
...
user.filter  it !== User.NULL_USER 

但如果可能的话,尽量避免null 的概念,并考虑另一种不需要的解决方案。

【讨论】:

【参考方案3】:

非常感谢您的所有回答,但我最终选择了这个解决方案:-

class UserEnvelope(val user:User?) 

并在 observables 中使用它。

这最符合我的要求。

我是 Kotlin 的新手,所以我不知道如何使用 Optionals。但据我了解,每次我需要观察这些值时,我都必须将其类型转换为用户类型,对吗?

【讨论】:

我喜欢这种方法,它简单明了。【参考方案4】:

要实现nhaarman 的答案中提到的解决方案,您可以使用 API 级别 24 中添加的 android SDK 中的 util 类 Optional (doc)。

如果您的应用的minSdkVersion 小于24,那么您仍然需要自己实现它。

【讨论】:

【参考方案5】:

由于 RxJava 2 不支持 null 值,您可以使用其他一些可接受的解决方案:

使用自定义的或第三方的可选包装库,就像一些已发布的答案所建议的那样。当我摆脱 Java 转而支持 Kotlin 时,Optionals 在同一个包中消失了,因为 Kotlin 本身支持可空性作为其类型 System 的一部分。只是通过这种更改,代码更加清晰,我个人不希望在我的代码中重新使用 Optional,只要我可以避免它们。 使用您的主题类型发出 Any 类实例。例如,您可以创建一个 Empty.INSTANCE 枚举类,该类将模拟 null 值,然后按枚举类进行过滤。

最后一个是我使用的,并且更喜欢作为之前解决方案的变体,并且基于专业化。我们的 JetBrains 朋友总是强调 Kotlin 中的课程非常便宜,因此这是区分已登录用户和未登录用户的快速示例:

abstract class SessionUser
sealed class LoggedUser(val username: String, val password: String) : SessionUser()
sealed class LogoutUser : SessionUser()

private val user = BehaviorSubject.create<SessionUser>()
private val loggedUser = 
   user.filter  it is LoggedUser .cast(LoggedUser::class.java)

fun login(username: String, password: String) 
   user.onNext(LoggedUser(username, password))


fun logout() 
   user.onNext(LogoutUser())

【讨论】:

【参考方案6】:

我采用了类似于Optional&lt;User&gt;UserEnvelope 的方法。我做了一个简单的User 类和一个继承自它的ReifiedUser 类。 User 类有一个 companion object,它有一个 NONE 实例。 BehaviorSubject 使用 User.NONE 实例进行实例化。它看起来像这样:

open class User 
    companion object 
        val NONE = User()
    


class ReifiedUser(
        @field:JsonProperty(J.FirstName) val firstName: String,
        @field:JsonProperty(J.LastName) val lastName: String
) : User()

我的BehaviorSubject 是这样实例化的:

val activeUser: BehaviorSubject<User> = BehaviorSubject.createDefault(User.NONE)

在我需要使用activeUser 的任何地方,我要么将flatMap 使用Observable.empty(),如果它不是NONE,或者只是弄清楚它是什么以及在订阅者中做什么。

我不喜欢将 java Optional 与可空的 kotlin 混合,因为混合 maplet 会变得非常混乱和丑陋。这样一来,发生的事情就很明显了。

【讨论】:

【参考方案7】:

我觉得写一个像Result这样的容器类更有意义。一个例子是

data class Result<T>(value: T?, error: Throwable?)

用法

Observable.create  observer ->
   upstreamService.listen(object: UpstreamListener 
     onSuccess(data: User) 
       observer.onSuccess(Result(data))
     
     onError(exception: Throwable) 
       observer.onSuccess(Result(null, exception))
     
   

【讨论】:

以上是关于如何在 RxJava 2 和 Kotlin 中将 null 传递给具有可空类型的 Observable的主要内容,如果未能解决你的问题,请参考以下文章

使用 Kotlin 进行 RxJava 和改造

如何使用 Rxjava 在 Android Kotlin 中过滤数据

如何在 RxJava 2 和 Android 中延迟 onError()?

RxJava/Kotlin Observable 方法调用链 - 如何终止?

使用 Kotlin 的 RxJava 中的花括号和普通括号有啥区别

Kotlin Coroutines 中是不是有 RxJava 主题的类似物?