有没有办法让编译器相信 @NonNull 变量在 Kotlin 中实际上是可空的?

Is there a way to convince the compiler that a @NonNull variable is actually Nullable in Kotlin?

在 Android 中,有一个 android.location.LocationListener class,Android 操作系统使用 class 更新位置,如下所示:

它说 Location 是@NonNull,但实际上 OS 有时会 return null,这会导致应用程序崩溃。

object : LocationListener {
    override fun onLocationChanged(location: Location) {
        doSomethingWithLocation(location) // App crashes here, because location is null
    }
}

有什么方法可以告诉编译器 Location 实际上是 Location? 吗?

我认为没有办法丢弃来自 java 的可空性信息。在您的情况下,这是一个问题,因为正如@RobCo 所指出的,kotlin 会自动为位置插入非空断言,并且断言将失败。

所以您剩下的唯一解决方案似乎是使用 java 包装器类型,它只会为您提供非空位置值,您可以在您的 kotlin 代码中使用它。这个包装器可能看起来像

import androidx.core.util.Consumer;

public class LocationHandler{
    LocationHandler(Consumer<Location> delegate){
        this.delegate = delegate;
    }

    private Consumer<Location> delegate;

    private LocationListener _listener = new LocationListener() {
        @Override
        public void onLocationChanged(@NonNull Location location) {
            // Only forward non-null location values
            if(location != null){ delegate.accept(location); }
            else {
                Log.d("LocationHandler", "Received null Location");
            }
        }
    };

    public LocationListener getListener(){
        return _listener;
    }
}

现在在您的 kotlin 代码中,您可以将其用作

class MainActivity : AppCompatActivity() {

    private val locationDelegate = Consumer<Location> {
        // Do something with non-null location
    }
    
    override fun somFun {
        val locationHandler = LocationHandler(locationDelegate)
        val locMan = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        locMan.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 
                0L, 
                0F, 
                locationHandler.listener)
    }
}

我找到了两个解决方法。

首先,如果接口只包含一个抽象方法,我们可以使用 SAM 转换并将参数的类型重写为 Location?,如下所示:

val listener = LocationListener { location: Location? ->
    doSomethingWithLocation(location)
}

有趣的是,如果使用 SAM 转换,我们可以覆盖参数,但如果使用子类型的完整语法,我们就不能。 LocationListener 似乎是 SAM,因此此解决方案适用于您的情况。

如果第一种方案不行,我们可以覆盖Java中的接口:

public interface MyLocationListener extends LocationListener {
    @Override
    void onLocationChanged(@Nullable Location location);
}
val listener = object : MyLocationListener {
    override fun onLocationChanged(location: Location?) {
        doSomethingWithLocation(location)
    }
}