如何在不使用Java的纯Kotlin中实现接口null与throw调度
问题描述
考虑此示例:
CommonHandler.java:
具有附加逻辑的通用处理程序。这是非常简化的:
import kotlin.jvm.functions.Function0;
public class CommonHandler {
private void doCoolStuffBefore() { /* do some cool stuff */ }
private void doCoolStuffAfter() { /* do some cool stuff */ }
public R getResult(Function0 provider) {
R result;
doCoolStuffBefore();
try {
result = provider.invoke();
} finally {
doCoolStuffAfter();
}
return result;
}
}
NullableHandler.kt:
如果操作引发异常,则返回null
的处理程序版本。结果的类型为R?
class NullableHandler : CommonHandler() {
override fun getResult(provider: Function0): R? {
return try {
super.getResult(provider)
} catch(ex: Throwable) {
null
}
}
}
ThrowingHandler.kt:
处理程序版本,将错误包装在其内部异常类型中。结果类型为R
。
class ThrowingHandler : CommonHandler() {
class WrappedException(message: String, cause: Throwable?): Exception(message, cause)
override fun getResult(provider: Function0): R {
return try {
super.getResult(provider)
} catch(ex: Throwable) {
throw WrappedException("Throwing handler failed with exception: ${ex.javaClass.name}", ex)
}
}
}
Api.kt:
基本上我们不拥有并且不能修改的任何API。
object Api {
fun find(query: String): Int =
if (query.length > 3) 42
else throw NoSuchElementException("Not found for $query")
fun select(query: String): String =
if (query.count { it == 'x' } > 2) "Selected"
else throw NoSuchElementException("Not found for $query")
}
现在,有了以上所有类,我们可以实现API包装器:
object ApiProxy {
private val throwingHandler = ThrowingHandler()
private val nullableHandler = NullableHandler()
fun find(query: String): Int = throwingHandler.getResult { Api.find(query) }
fun findOrNull(query: String): Int? = nullableHandler.getResult { Api.find(query) }
fun select(query: String): String = throwingHandler.getResult { Api.select(query) }
fun selectOrNull(query: String): String? = nullableHandler.getResult { Api.select(query) }
}
我的问题是,我如何实现类似的层次结构而又不依赖Java,这样就存在单个类/接口,其方法可以返回R
类型或R?
类型。据我所知,我们无法使用R!
语法在Kotlin中显式声明平台类型。
思路一:
我认为简单的解决方案是使CommonHandler的getResult返回可为空的结果,然后处理null返回,因为这是一个例外。至少那是我一开始想到的。
所以,根据提示,我们将公共处理程序定义为
open class CommonHandler {
private fun doCoolStuffBefore() { /* do some cool stuff */
}
private fun doCoolStuffAfter() { /* do some cool stuff */
}
open fun getResult(provider: Function0): R? {
doCoolStuffBefore()
return try {
provider.invoke()
} finally {
doCoolStuffAfter()
}
}
}
NullableHandler不变,但ThrowingHandler做了
class ThrowingHandler : CommonHandler() {
class WrappedException(message: String, cause: Throwable?): Exception(message, cause)
override fun getResult(provider: Function0): R {
return try {
super.getResult(provider) ?: throw AnotherWrappedException("whooops")
} catch(ex: Throwable) {
throw WrappedException("Throwing handler failed with exception: ${ex.javaClass.name}", ex)
}
}
}
如果不为null,则使用elvis运算符返回该值;如果为null,则抛出AnotherWrappedException("whooops")
。
您怎么看?能为您工作吗?
思路二:
如果将返回类型定义为可为空的R:
open class CommonHandler {
private fun doCoolStuffBefore() { /* do some cool stuff */
}
private fun doCoolStuffAfter() { /* do some cool stuff */
}
open fun getResult(provider: () -> R): R? {
doCoolStuffBefore()
return try {
provider.invoke()
} finally {
doCoolStuffAfter()
}
}
}
然后您可以将范围缩小到子类中的不可为N的R。您的ThrowableHandler假定API无法返回null,因此在此处使用!!
运算符并将任何KotlinNPE包裹在WrappedException中是完全合理的。如果需要包装可以返回null的API,那么我认为您还需要NullableThrowableHandler。
class ThrowingHandler : CommonHandler() {
class WrappedException(message: String, cause: Throwable?): Exception(message, cause)
override fun getResult(provider: () -> R): R {
return try {
super.getResult(provider)!!
} catch(ex: Throwable) {
throw WrappedException("Throwing handler failed with exception: ${ex.javaClass.name}", ex)
}
}
}