为什么Kotlin中的`as`在这种情况下没有投出

时间:2017-06-29 10:54:30

标签: java kotlin

为什么Java在这种情况下打印false

我今天开始写Kotlin,我想用泛型投射参数但是结果是出乎意料的。而且我不知道为什么?

fun main(args: Array<String>) {
    var t = TEST<Int, String>()
    print(t.returns("12") is Int) // print false
}
@Suppress("UNCHECKED_CAST")
class TEST<out T, in R> {
    fun returns(r: R): T {
        return r as T
    }
}

对不起,如果我的英语不好。

3 个答案:

答案 0 :(得分:3)

由于泛型如何在JVM上工作,这是因为您忽略了@Suppress("UNCHECKED_CAST")给出的警告。

只需将TEST类编译为以下java类:

class TEST {
    Object returns(Object r) {
        return r;
    }
}

因为泛型在运行时没有被统一,这意味着带有泛型的强制转换是没有意义的。

解决这个问题的一种方法是使用kotlin的reified泛型,内联函数和匿名类来强制进行实际演员,而不是将其简化为Object

open class TEST<out T, in R> {
    open fun returns(r: R): T {
        return r as T
    }
}

inline fun <reified T, R> createTest(): TEST<T,R> {
    return object : TEST<T,R>() {
        override fun returns(r: R): T {
            return r as T
        }
    }
}

fun main(args: Array<String>) {
    var t = createTest<Int, String>()
    print(t.returns("12") is Int) // Causes classcastexception
}

答案 1 :(得分:1)

Java泛型有type erasure。这使得JVM语言难以实现具体化的泛型。 Some languages like Scala do, some languages like Kotlin do not

这种未经检查的演员表实际上是未经检查的,因此警告。如果没有具体的泛型,则无法进行正确的检查,编译器只会在运行时在泛型和非泛型代码的边界处进行类型检查。因为您的示例实际上没有非泛型部分,所以不会执行此类类型的检查。 is表达式不要求左侧具有任何特定类型,因此甚至省略运行时检查,就像在Java中一样。

考虑以下示例,其中返回值存储在变量中:

fun main(args: Array<String>) {
    var t = TEST<Int, String>()
    var k = t.returns("12") // runtime error here
    print(k is Int)
}

@Suppress("UNCHECKED_CAST")
class TEST<out T, in R> {
    fun returns(r: R): T {
        return r as T
    }
}
...
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
    at MainKt.main(main.kt:3)

Kotlin在许多地方使用Java-s类型检查程序。如果考虑等效的Java代码,它的行为方式相同:

public class JMain {
    public static void main(String[] args) {
        TEST<Integer, String> t = new TEST<>();
        System.out.println(t.returns("12") instanceof Integer); // false
    }

    static class TEST<T, R> {
        @SuppressWarnings("unchecked")
        public T returns(R r) {
            return (T)r;
        }
    }
}

答案 2 :(得分:1)

结果是false而不是ClassCastException,因为通用参数Type Erasure发生在编译时。

  
      
  • 如果类型参数不受限制,则将通用类型中的所有类型参数替换为其边界对象。因此,生成的字节码只包含普通的类,接口和方法。
  •   
  • 如有必要,插入类型强制转换,以保护类型安全。
  •   

因此,您问题中上面的代码returns方法的返回类型为Object而不是Integer。即使没有额外的投射,String也可以分配给Object。因此,您检查String Int是否始终为false

另一种情况是使用bounded parameter types声明一个类,它将抛出一个ClassCastException。例如:

@Suppress("UNCHECKED_CAST")
class TEST<out T : Int, in R : String> {
    fun returns(r: R): T {
        return r as T
    }
}

fun main(args: Array<String>) {
    var t = TEST<Int, String>()
    print(t.returns("12") is Int) 
    //      ^--- throws ClassCastException
}

在使用bounded parameter types的通用类的类型擦除后如下所示:

public final class TEST{
    public int returns(String value){
        return ((Integer) value);
    }
}