【问题标题】:Why do I get a type mismatch when attempting to return a value for a checked generic parameter?为什么在尝试为已检查的泛型参数返回值时会出现类型不匹配?
【发布时间】:2017-03-14 01:27:50
【问题描述】:

在以下代码中,"Happy Halloween!"42 等被标记为“类型不匹配”。 (必需:T,找到:String(或 Int))但编译器不应该能够从类型检查中推断出返回值是正确的类型吗?

interface Type<T>
class StringType() : Type<String>
class IntType1() : Type<Int>
class IntType2(val a: Int, val b: Int) : Type<Int>

fun <T> something(type: Type<T>): T = when (type) {
    is StringType -> "Happy Halloween!"
    is IntType1 -> 42
    is IntType2 -> type.a * type.a + type.b * type.b + type.a * type.b
    else -> throw IllegalArgumentException()
}

【问题讨论】:

  • 您已将函数的返回类型设置为T,但您返回的是字符串Happy Halloween!。这就是你得到类型不匹配的原因。
  • @marstran 返回类型是T 但在type is StringType 之后返回true 我希望编译器现在知道T 确实是String
  • 啊,现在我明白了
  • @marstran 这样的强制转换是不安全的,因为is StringType -&gt; 42 as T 也会编译,这不是我想要的。我希望编译器帮助我确保返回适当的类型。
  • 我认为只是 Smart Cast 不够聪明,无法推理泛型类型。我假设它只检查当前级别的类型,而不深入类型声明。

标签: kotlin


【解决方案1】:

你可以这样写:

interface Type<T>
class StringType() : Type<String>
class IntType1() : Type<Int>
class IntType2(val a: Int, val b: Int) : Type<Int>

inline fun <reified T> something(type: Type<T>): T {
    val result = when(type) {
        is StringType -> "Happy Halloween"
        is IntType1 -> 42
        is IntType2 -> type.a * type.a + type.b * type.b + type.a * type.b
        else -> throw IllegalArgumentException()
    }
    return if (result is T) result else throw Exception() 
}

运行以下:

fun main(args: Array<String>) {
    println(something(StringType()))
    println(something(IntType1()))
    println(something(IntType2(2, 3)))
}

会给你这个输出:

Happy Halloween
42
19

在此处了解有关内联函数和具体参数的更多信息:Reified type parameters

【讨论】:

    【解决方案2】:

    当编译器应用类型擦除时,将定义返回类型。所以,假设你使用一个字符串......你的方法将是这样的:

    fun something(type: Type<String>): String = when (type) {
        is StringType -> "Happy Halloween!"
        is IntType1 -> 42 //Wrong: you must return String!
        is IntType2 -> type.a * type.a + type.b * type.b + type.a * type.b   
        else -> throw IllegalArgumentException()
    }
    

    这件事是:你必须在编译时知道你的返回类型。如果你不知道这一点,你必须告诉编译器:

    fun <T> something(type: Type<T>): Any = when (type) {
        is StringType -> "blabla"
        is IntType1 -> 42
        is IntType2 -> type.a * type.a + type.b * type.b + type.a * type.b
        else -> throw IllegalArgumentException()
    }
    

    对不起,这段代码不是你要跳的,你要在方法返回后进行强制转换......

    但你可以这样做:

    fun <T> something(type: Type<T>): T = when (type) {
            is StringType -> type.b
            //is IntType1 -> 42 remove this!
            is IntType2 -> type.a * type.a + type.b * type.b + type.a * type.b
            else -> throw IllegalArgumentException()
        }
    

    假设 type.a 和 type.b 被参数化为 T。那么你的代码就可以正常工作了。

    【讨论】:

    • 我更新了示例代码以阐明我的用例。我应该更明确一点,Type&lt;T&gt; 将有多个子类型,并且该函数将执行多个 is &lt;SubType&gt; 检查以确定返回什么。
    • 对不起,我的错。我编辑了我的答案。我希望它现在有所帮助。
    【解决方案3】:

    如果我稍微简化一下你的例子:

    interface Type<T>
    
    fun <T> something(type: Type<T>): T = when (type) {
        is Type<String> -> "Happy Halloween!"
        else -> throw IllegalArgumentException()
    }
    

    编译器现在抱怨他:cannot check for instance of erased type

    所以问题是由于类型擦除,Type&lt;String&gt;Type&lt;Int&gt; 在运行时没有区别,所以编译器不允许这样做。

    您可以尝试使用 Gson 的 TypeToken&lt;T&gt; 或 Jackson 的 TypeReference&lt;T&gt; 文档参考这篇博客文章来解释这个想法:http://gafter.blogspot.ca/2006/12/super-type-tokens.html

    【讨论】:

    • 事实并非如此:作者故意使用StringTypeType&lt;String&gt; 的子类),因为可以对@987654331 进行is-check @ 在运行时。
    • 可以进行检查,但它仍然会认为StringType 只是type
    • 抱歉,我的意思是StringType 仍然只是Type&lt;&gt;,没有添加任何实际类型信息。所以when 无法分辨它们之间的区别。
    【解决方案4】:

    is 运算符,很像 Java 的 instanceof 运算符,在运行时执行。

    因此,在编译时,编译器不知道实际类型,因此会出现编译错误。

    这是另一个简单的例子:

    fun <T>f(t: T): T
    {
        if (t is Int) return 3 // compilation error
        else return t
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-17
      • 1970-01-01
      • 2014-02-22
      相关资源
      最近更新 更多