如何规避 Kotlin 的限制“类型参数被禁止用于捕获参数”

Min*_*eet 6 generics exception-handling kotlin

我定义了以下函数:

inline fun <T> T.tryTo(block: T.() -> Unit): T? {
    try {
        block()
    } catch (ex: IllegalArgumentException) {
        return this
    }
    return null
}
Run Code Online (Sandbox Code Playgroud)

目的是在对象上构建一系列尝试操作,例如:

val input: String = getInput();

input.tryTo /* treat as a file name and open the file */ {
    Desktop.getDesktop().open(File(this))
}?.tryTo /* treat as a number */ {
    try {
        doSomethingWithTheNumber(parseInt(this))
    } catch (ex: NumberFormatException) {
        throw IllegalArgumentException()
    }
}?.tryTo {
    println("All options tried, none worked out. Don't know how to treat this input.")
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,效果很好。

但是,正如您在中间tryTo -block(“视为数字”)中所见,将“预期”异常重新抛出为 IllegalArgumentException 以保持架构正常工作是不方便的。最好写成:

val input: String = getInput();

input.tryTo<IllegalArgumentException> /* treat as a file name and open the file */ {
    Desktop.getDesktop().open(File(this))
}?.tryTo<NumberFormatException> /* treat as a number */ {
    doSomethingWithTheNumber(parseInt(this))
}?.tryTo<Exception> {
    println("All options tried, none worked out. Don't know how to treat this input.")
}
Run Code Online (Sandbox Code Playgroud)

所以,我将函数tryTo重写为:

inline fun <T, X: Exception> T.tryTo(block: T.() -> Unit): T? {
    try {
        block()
    } catch (ex: X) {
        return this
    }
    return null
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,后者不能编译:“类型参数被禁止用于捕获参数”。

如何规避这个限制?


附录:

现在我已经做到了:

inline fun <T, reified X: Exception> T.tryTo(block: T.() -> Unit): T? {
    try {
        block()
    } catch (ex: Exception) {
        return if (ex is X) this else throw ex
    }
    return null
}
Run Code Online (Sandbox Code Playgroud)

但是我对此仍然不满意,因为它要求我明确指定两种类型(“类型推断失败...”/“需要 2 个类型参数...”):

input.tryTo<String, IllegalArgumentException> /* treat as a file in the stapel-directory */ {
    ...
}
Run Code Online (Sandbox Code Playgroud)

尽管第一个类型参数很明显可以从接收器对象推断出来。

zsm*_*b13 10

我认为如果您只是使类型参数具体化,这是可能的,但显然不是。我确实找到了这个检查的来源,并且很明显,catch 子句中任何类型的类型参数都会出错,无论它是否具体化。

添加这些检查的提交消息引用了这个问题- 显然,带有类型参数的 catch 子句正在捕获所有抛出的Exception实例,ClassCastException如果异常不是指定类型的,则崩溃。

您的情况的可能解决方法来自类似 Java 问题的这个答案- 如果泛型类型被具体化,您可以检查抛出的异常是否属于该特定类型,我相信这使该函数成为您正在寻找的:

inline fun <T, reified X : Exception> T.tryTo(block: T.() -> Unit): T? {
    try {
        block()
    } catch (ex: Exception) {
        if (ex is X) {
            return this
        }
    }
    return null
}
Run Code Online (Sandbox Code Playgroud)

尽管调用站点变得非常难看,因为如果函数调用有两个类型参数,则不能只指定它的第二个类型参数:

val input: String = getInput()

input.tryTo<String, IllegalArgumentException> /* treat as a file name and open the file */ {
    Desktop.getDesktop().open(File(this))
}?.tryTo<String, NumberFormatException> /* treat as a number */ {
    doSomethingWithTheNumber(parseInt(this))
}?.tryTo<String, Exception> {
    println("All options tried, none worked out. Don't know how to treat this input.")
}
Run Code Online (Sandbox Code Playgroud)

一个比上面更好的替代方案,更接近原始的 Java 答案:

inline fun <T> T.tryTo(exceptionType: KClass<out Exception>, block: T.() -> Unit): T? {
    try {
        block()
    } catch (ex: Exception) {
        if (exceptionType.isInstance(ex)) {
            return this
        }
    }
    return null
}
Run Code Online (Sandbox Code Playgroud)

随着KClass像这样通过实例:

input.tryTo(IllegalArgumentException::class) /* treat as a file name and open the file */ {
    Desktop.getDesktop().open(File(this))
}?.tryTo(NumberFormatException::class) /* treat as a number */ {
    doSomethingWithTheNumber(parseInt(this))
}?.tryTo(Exception::class) {
    println("All options tried, none worked out. Don't know how to treat this input.")
}
Run Code Online (Sandbox Code Playgroud)