在使用 Kotlin 实现 Java 接口时出现 NullPointerException。

9

我正在尝试在Kotlin中实现RxJava的BiFunction接口,但是遇到了一个NullPointerException

这是我在Kotlin中实现的Java接口,它来自于RxJava 2。

package io.reactivex.functions;

import io.reactivex.annotations.NonNull;

/**
 * A functional interface (callback) that computes a value based on multiple input values.
 * @param <T1> the first value type
 * @param <T2> the second value type
 * @param <R> the result type
 */
public interface BiFunction<T1, T2, R> {

    /**
     * Calculate a value based on the input values.
     * @param t1 the first value
     * @param t2 the second value
     * @return the result value
     * @throws Exception on error
     */
    @NonNull
    R apply(@NonNull T1 t1, @NonNull T2 t2) throws Exception;
}

这是我的实现。
class MonitoringStateReducer: BiFunction<MonitoringViewState, MonitoringResult, 
    MonitoringViewState> {
    override fun apply(
        previousState: MonitoringViewState,
        result: MonitoringResult
    ): MonitoringViewState {
        when (result) {
           //Returns a non-null new state
        }
    }
}

然后,在ViewModel中,我尝试使用它,但是它抛出了一个NullPointerException异常。

2019年08月22日09:57:41.049 6925-6925/com.name.app E/AndroidRuntime: 致命异常: main 进程: com.name.app, 进程ID: 6925 java.lang.RuntimeException: 无法启动Activity ComponentInfo{com.name.app/com.name.app.features.monitoring.presentation.MonitoringActivity}: java.lang.NullPointerException: accumulator为空 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2907) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1641) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6694) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769) 原因: java.lang.NullPointerException: accumulator为空 at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39) at io.reactivex.Observable.scanWith(Observable.java:11537) at io.reactivex.Observable.scan(Observable.java:11502) at com.name.app.features.monitoring.presentation.MonitoringViewModel.compose(MonitoringViewModel.kt:47) at com.name.app.features.monitoring.presentation.MonitoringViewModel.(MonitoringViewModel.kt:18) at com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:25) at com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:8) at dagger.internal.DoubleCheck.get(DoubleCheck.java:47) at com.name.app.di.viewmodel.ViewModelFactory.create(ViewModelFactory.kt:12) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130) at com.name.app.features.monitoring.presentation.MonitoringActivity$viewModel$2.invoke(MonitoringActivity.kt:46) at com.name.app.features.monitoring.presentation.MonitoringActivity$viewModel$2.invoke(MonitoringActivity.kt:26) at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:81) at com.name.app.features.monitoring.presentation.MonitoringActivity.getViewModel(Unknown Source:7) at com.name.app.features.monitoring.presentation.MonitoringActivity.bind(MonitoringActivity.kt:85) at com.name.app.features.monitoring.presentation.MonitoringActivity.onCreate(MonitoringActivity.kt:119) at android.app.Activity.performCreate(Activity.java:6984) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1235) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2860) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1641) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6694) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)
class MonitoringViewModel @Inject constructor(
    private val processor: MonitoringProcessor
) : BaseViewModel<MonitoringIntention, MonitoringViewState>() {
    //Properties that are not relevant for the question

    private val reducer: MonitoringStateReducer = MonitoringStateReducer()

    private fun compose(): Observable<MonitoringViewState> {
        return intentsSubject.compose(intentFilter)
            .map(actionFromIntent)
            .compose(processor)
            .scan(MonitoringViewState.init(), reducer) //Exception is here
            .distinctUntilChanged()
            .replay(1)
            .autoConnect(0)
    }

    override fun state(): Observable<MonitoringViewState> = compose()

    //Functions that are not relevant for the question
}

这段代码还是无法正常工作。

private val reducer by lazy(LazyThreadSafetyMode.NONE) {
    MonitoringStateReducer()
}

然而,如果我使用以下代码替换reducer,它可以正常工作。

private val reducer: BiFunction<MonitoringViewState, MonitoringResult, MonitoringViewState>
    get() = MonitoringStateReducer()

已在Kotlin 1.3.40和1.3.50上进行测试。


请检查堆栈跟踪,可能是从父构造函数中调用并订阅了state() - Aarjav
请发布完整的 MonitoringStateReducer.apply 方法。如果太长,请至少发布 when 块的几个分支。问题可能与其中的某些内容有关。 - Leo Aso
3个回答

11
问题源于Kotlin类初始化顺序。崩溃是由于BaseViewModel构造函数调用了在子类MonitoringViewModel中被覆盖的state()方法所致。因此,在访问reducer时,它尚未被初始化。在派生类的新实例构建期间,基类初始化作为第一步完成,并且在派生类的初始化逻辑运行之前发生。请参阅这篇文章,其中描述了一个非常相似的问题。Derived class initialization order Kotlin文档部分也应该很有用。


4

@Valeriy Katkov的答案是正确的,请接受他的答案。

这里是一个模拟相同情景的代码:

fun main() {
    MonitoringViewModel()
}

open abstract class BaseViewModel {

    init {
        state()
    }

    abstract fun state()
}

class MonitoringViewModel: BaseViewModel() {

    private val reducer1: MonitoringStateReducer = MonitoringStateReducer()
    private val reducer2: BiFunction<MonitoringViewState, MonitoringResult, MonitoringViewState>
        get() = MonitoringStateReducer()

    override fun state() {
        Observable.just(MonitoringResult(3))
                .scan(MonitoringViewState(0), reducer1)
                .subscribe { state -> println(state.value) }
    }

}

data class MonitoringViewState(val value: Int)

data class MonitoringResult(val value: Int)

class MonitoringStateReducer : BiFunction<MonitoringViewState, MonitoringResult,
        MonitoringViewState> {
    override fun apply(
            previousState: MonitoringViewState,
            result: MonitoringResult
    ): MonitoringViewState {
        return MonitoringViewState(previousState.value + result.value)
    }
}

如果您使用 reducer1 运行带有 scan 操作符的代码,会看到以下异常:

Exception in thread "main" java.lang.NullPointerException: accumulator is null

如果您使用 reducer2 运行带有 scan 操作符的代码,则执行成功。

为什么使用 reduce1 会崩溃?

根据 @Valeriy Katkov 的 答案

崩溃是由于 BaseViewModel 构造函数调用了在子 MonitoringViewModel 类中重写的 state() 方法导致的。因此,在访问 reducer 时,它尚未初始化。在派生类的新实例构造期间,基类初始化是第一步完成的,它发生在派生类的初始化逻辑运行之前。


1
我怀疑这个堆栈跟踪的部分能够提供答案:
at com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:8)
at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
at com.name.app.di.viewmodel.ViewModelFactory.create(ViewModelFactory.kt:12)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164)
我从中得出的结论是,您正在使用Dagger创建MonitoringViewModel类的实例。我不确定Dagger是否可以执行我即将描述的操作,但我知道其他库(例如Gson)可以执行此操作,并且符合该模式...
如果使用Unsafe类,则可以创建对象实例而无需实际调用构造函数。请参阅本文的“避免初始化”部分:http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/ 如果在此处发生这种情况,则您的private val reducer = MonitoringStateReducer()永远不会被执行,因为它是类初始化的一部分。因此,在compose()中尝试使用reducer时,reducer从未被初始化并且为null
我不确定为什么使用by lazy委托会导致它无法工作,但是如果提供自定义的get(),它可以正常工作是有道理的:它不再是类初始化的一部分,而是按需求进行评估。
尝试创建此类的实例,而不使用Dagger,看看您的“正常”初始化是否有效。

想知道你是如何完成这个的。听起来是一个非常好的可能性。我天真地在Dagger的repo中搜索Unsafe,但没有找到任何提及它的内容。我问的原因也是因为作为一个在编译时生成代码并需要所有类信息的框架,如果要求使用unsafe,我觉得很奇怪。 - Fred
也许它并不明确地是“不安全”的,而且可能有其他方法(我不知道)可以创建实例而不进行正常初始化。至于我如何做到的,实际上只是因为我最近读了那篇博客文章,所以在我的脑海中创建未初始化的实例的想法还很新鲜。 - Ben P.
我非常怀疑这是一个Dagger问题。他们在存储库中或生成的代码中都没有使用Unsafe。同样,ViewModel也不会使用Unsafe。我认为这可能是Kotlin的问题,但我对Kotlin的内部了解不够深入。 - GianMS
点赞,因为这是一个好答案,但对于这个问题的情况,@Valeriy Katkov正确描述了问题。 - Gustavo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接