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

时间:2018-04-23 14:53:58

标签: generics exception-handling kotlin

我定义了以下功能:

index=True

目的是在对象上构建一系列try-actions,例如:

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

到目前为止一切正常。

但是,正如您在中间看到的那样 tryTo -block(“视为数字”),将“预期”异常重新抛出为IllegalArgumentException以保持模式正常运行是不方便的。写一下会更好:

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.")
}

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

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.")
}

不幸的是,后者没有编译:“禁止使用类型参数来捕获参数”。

如何规避此限制?

附录:

现在我已经明白了:

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

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

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
}

虽然第一个类型参数显然可以从接收器对象中推断出来。

2 个答案:

答案 0 :(得分:3)

我认为如果你只是将类型参数设置为reified,这是可能的,但显然它不是。我确实找到了这个检查的source,并且很明显错误的是catch子句中的任何类型的参数,无论它是否已经具体化。

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

对于类似的Java问题,您的案例的可能解决方法来自this answer - 如果通用类型已实现,您可以检查抛出的异常是否属于该特定类型,我相信这使得此函数与您相同正在寻找:

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
}

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

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.")
}

一种稍微好一点的替代方案,更接近原来的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
}

传递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.")
}

答案 1 :(得分:0)

您只需删除接收器参数

对于?:的语义,最好使用?.,然后使用else

inline fun <reified E : Throwable> runIgnoring(block: () -> Unit): Unit? {
    return try {
        block()
    } catch (e: Throwable) {
        if (e is E) null else throw e
    }
}

val input: String = getInput()

runIgnoring<IllegalArgumentException> /* treat as a file name and open the file */ {
    Desktop.getDesktop().open(File(input))
} ?: runIgnoring<NumberFormatException> /* treat as a number */ {
    doSomethingWithTheNumber(parseInt(input))
} ?: run {
    println("All options tried, none worked out. Don't know how to treat this input.")
}
相关问题