简体   繁体   English

如何规避科特林的限制“捕获参数禁止使用类型参数”

[英]how to circumvent Kotlin's restriction “Type parameter is forbidden for catch parameter”

I have defined the following function: 我定义了以下功能:

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

The purpose is to build a chain of try-actions on an object, eg: 目的是在对象上建立一系列尝试操作,例如:

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

So far that works fine. 到目前为止,一切正常。

But, as you can see in the middle tryTo -block ("treat as a number"), it is inconvenient to rethrow an "expected" exception as an IllegalArgumentException to keep the schema working. 但是,正如您在中间的tryTo -block (“按数字处理”)中所看到的那样,将“期望的”异常作为IllegalArgumentException抛出以保持架构正常工作是不方便的。 It would be nicer to write: 这样写会更好:

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

So, I have rewritten the function tryTo to: 因此,我将函数tryTo重写为:

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

Unfortunately, the latter does not compile: "Type parameter is forbidden for catch parameter". 不幸的是,后者不能编译:“捕获参数禁止使用类型参数”。

How to circumvent this restriction? 如何规避这一限制?


Addendum: 附录:

Now I've got it to: 现在,我可以做到:

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
}

But I'm still not happy with this because it requires me to specify both types explicitly ("Type inference failed..." / "2 type arguments expected ..."): 但是我对此仍然不满意,因为它要求我显式地指定两种类型(“类型推断失败...” /“需要2个类型参数...”):

input.tryTo<String, IllegalArgumentException> /* treat as a file in the stapel-directory */ {
    ...
}

though the first type parameter is obvious as inferable from the receiver object. 尽管第一种类型的参数显然可以从接收者对象中推断出来。

I thought this would be possible if you just made the type parameter reified, but apparently it is not. 我认为,如果您只是对类型参数进行了类型化,那将是可能的,但是显然不是。 I did find the source of this check, and it quite clearly errors for any sort of type parameter in a catch clause, whether it's reified or not. 我确实找到了此检查的来源 ,并且很明显,在catch子句中的任何类型参数(无论是否经过了整形)都会出错。

The commit message that added these checks references this issue - apparently the catch clause with a type parameter was catching all thrown Exception instances, and crashing with a ClassCastException if the exception wasn't of the specified type. 添加了这些检查的提交消息引用了此问题 -带有类型参数的catch子句显然正在捕获所有抛出的Exception实例,如果异常不是指定的类型,则使用ClassCastException崩溃。

A possible workaround for your case comes from this answer for the similar Java question - if the generic type is reified, you can check if the exception thrown was of that specific type, which I believe makes this function what you're looking for: 对于这种情况,可能的解决方法来自类似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
}

Although the call site gets quite ugly because you can't just specify the second type parameter of a function call if it has two type parameters: 尽管调用站点变得很丑陋,因为您不能仅指定函数调用的第二个类型参数(如果它具有两个类型参数):

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

A slightly nicer alternative to the above, and closer to the original Java answer: 一个比上面更好的替代方案,并且更接近原始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
}

With the KClass instances passed in like so: 随着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.")
}

you can simply remove the receiver parameter 您只需删除接收器参数

and it is better to use ?: then ?. 最好使用?:然后?. for else semantic 对于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.")
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM