从 Kotlin 实现 Java 接口时出现 NullPointerException

Posted

技术标签:

【中文标题】从 Kotlin 实现 Java 接口时出现 NullPointerException【英文标题】:NullPointerException when implementing Java interface from Kotlin 【发布时间】:2019-12-26 23:52:09 【问题描述】:

我正在尝试在 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: 致命例外:主要 进程:com.name.app,PID:6925 java.lang.RuntimeException:无法启动活动 ComponentInfocom.name.app/com.name.app.features.monitoring.presentation.MonitoringActivity: java.lang.NullPointerException:累加器为空 在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2907) 在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986) 在 android.app.ActivityThread.-wrap11(未知来源:0) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1641) 在 android.os.Handler.dispatchMessage(Handler.java:105) 在 android.os.Looper.loop(Looper.java:164) 在 android.app.ActivityThread.main(ActivityThread.java:6694) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769) 引起:java.lang.NullPointerException:累加器为空 在 io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39) 在 io.reactivex.Observable.scanWith(Observable.java:11537) 在 io.reactivex.Observable.scan(Observable.java:11502) 在 com.name.app.features.monitoring.presentation.MonitoringViewModel.compose(MonitoringViewModel.kt:47) 在 com.name.app.features.monitoring.presentation.MonitoringViewModel.(MonitoringViewModel.kt:18) 在 com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:25) 在 com.name.app.features.monitoring.presentation.MonitoringViewModel_Factory.get(MonitoringViewModel_Factory.java:8) 在 dagger.internal.DoubleCheck.get(DoubleCheck.java:47) 在 com.name.app.di.viewmodel.ViewModelFactory.create(ViewModelFactory.kt:12) 在 androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164) 在 androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130) 在 com.name.app.features.monitoring.presentation.MonitoringActivity$viewModel$2.invoke(MonitoringActivity.kt:46) 在 com.name.app.features.monitoring.presentation.MonitoringActivity$viewModel$2.invoke(MonitoringActivity.kt:26) 在 kotlin.UnsafeLazyImpl.getValue(Lazy.kt:81) 在 com.name.app.features.monitoring.presentation.MonitoringActivity.getViewModel(未知 来源:7) 在 com.name.app.features.monitoring.presentation.MonitoringActivity.bind(MonitoringActivity.kt:85) 在 com.name.app.features.monitoring.presentation.MonitoringActivity.onCreate(MonitoringActivity.kt:119) 在 android.app.Activity.performCreate(Activity.java:6984) 在 android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1235) 在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2860) 在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986) 在 android.app.ActivityThread.-wrap11(未知来源:0) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1641) 在 android.os.Handler.dispatchMessage(Handler.java:105) 在 android.os.Looper.loop(Looper.java:164) 在 android.app.ActivityThread.main(ActivityThread.java:6694) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 在 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()

但是,如果我用这段代码替换减速器,它就可以工作。

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

在 Kotlin 1.3.40 和 1.3.50 上测试。

【问题讨论】:

检查堆栈跟踪,可能是 state() 被调用并订阅了父构造函数的某个位置。 发布完整的MonitoringStateReducer.apply 方法。如果它太长,那么至少发布when 块的几个分支。问题可能与那里的某些东西有关。 【参考方案1】:

问题来自于 Kotlin 类的初始化顺序。崩溃是因为BaseViewModel 构造函数调用了state() 方法,该方法在子MonitoringViewModel 类中被覆盖。结果,当reducer被访问时,它还没有初始化。在派生类的新实例的构造过程中,基类初始化作为第一步完成,并且发生在派生类的初始化逻辑运行之前。看看this article,它描述了一个非常相似的问题。 Derived class initialization order Kotlin 的文档部分也应该很有用。

【讨论】:

相关:What's wrong with overridable method calls in constructor【参考方案2】:

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

这是模拟相同场景的代码:

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 运算符运行此代码,您将看到异常:

线程“主”java.lang.NullPointerException 中的异常:累加器为空

如果您使用reducer2 运行带有scan 运算符的代码,它会成功。

为什么使用reduce1会崩溃?

来自@Valeriy Katkov 的answer:

崩溃是由于 BaseViewModel 构造函数调用了在子 MonitoringViewModel 类中覆盖的 state() 方法。结果,当reducer被访问时,它还没有初始化。在派生类的新实例的构造过程中,基类初始化作为第一步完成,并且发生在派生类的初始化逻辑运行之前。

【讨论】:

【参考方案3】:

我怀疑堆栈跟踪的这部分包含答案:

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() 将永远不会真正执行,因为这是类初始化的一部分。因此,reducer 永远不会被初始化,当您尝试在compose() 中使用它时,它就是null

我不确定为什么在使用 by lazy 委托时它会不起作用,但是当您提供自定义 get() 时它为什么起作用确实有意义:它不再是类初始化的一部分,而是按需评估。

尝试在不使用 Dagger 的情况下创建此类的实例,看看您的“正常”初始化是否有效。

【讨论】:

很好奇你是怎么做到的。听起来是一个非常好的可能性。我天真地在 dagger 的 repo 中搜索了 Unsafe,但没有发现任何提及。我问的原因也是因为它是一个在编译时生成代码并需要有关类的所有信息的框架,如果觉得奇怪的是它们需要不安全。 也许它不是明确的Unsafe,还有其他一些方法(我不知道)可以在没有正常初始化的情况下创建实例。至于我是怎么做的,真的只是我最近读了那篇博文,所以在我脑海中浮现出在没有初始化的情况下创建实例的想法。 我非常怀疑这是 Dagger 问题。他们不会在其存储库或生成的代码中的任何地方使用Unsafe。同样,ViewModel 也没有Unsafe。我认为这可能是 Kotlin 的问题,但我对 Kotlin 的内部了解不多。 赞成,因为这是一个很好的答案,但是对于这个问题的场景,@Valeriy Katkov 正确描述了这个问题

以上是关于从 Kotlin 实现 Java 接口时出现 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

从 Kotlin 调用 JJWT 时出现 NoMethodError

将 Qute TypeSafe 模板与 Kotlin 一起使用时出现 java.lang.UnsatisfiedLinkError

在 Java 对象的 Kotlin 中设置属性时出现奇怪的“无法重新分配 Val”错误

在 Java 对象的 Kotlin 中设置属性时出现奇怪的“无法重新分配 Val”错误

在 Kotlin 中编写具有特征的 Android 测试时出现 java.lang.VerifyError

尝试从 Kotlin 中的另一个活动访问 EditText 时出现空指针异常