有没有办法让编译器相信 @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)
}
}
在 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)
}
}