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

≯℡__Kan透↙ 提交于 2020-01-16 05:04:46

问题


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, e.g.:

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. 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:

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

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.


回答1:


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.

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.

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:

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:

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:

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



回答2:


you can simply remove the receiver parameter

and it is better to use ?: then ?. for else semantic

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


来源:https://stackoverflow.com/questions/49984075/how-to-circumvent-kotlins-restriction-type-parameter-is-forbidden-for-catch-pa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!