【问题标题】:ClassCastException is System.out.printlnClassCastException 是 System.out.println
【发布时间】:2017-01-11 17:05:05
【问题描述】:

如果我在 System.out.println() 中直接使用 t.getTheString(implClass.class) 的返回对象,下面的代码将引发 java.lang.ClassCastException。虽然当我将对象分配给某个引用时,它工作正常,为什么?

interface testInterface{

    public String testMethod();
}
class testClass{

    public <T extends testInterface> T getTheString(Class< ? extends testInterface> classType){
        try {
            testInterface obj = classType.newInstance();
            return (T)obj;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

class implClass implements testInterface{

    @Override
    public String testMethod() {

        return "Sample String";
    }

    @Override
    public String toString(){
        return super.toString() + " Overridden toString()";

    }

}
public class genericTest {

    public static void main(String args[]){
        testClass t = new testClass();
        testInterface ti = t.getTheString(implClass.class);
        implClass i = t.getTheString(implClass.class);
        //System.out.println(i);   //<= Works fine
        //System.out.println(ti);  //<= Works fine
        System.out.println(t.getTheString(implClass.class));  // Exception in thread "main" java.lang.ClassCastException: implClass cannot be cast to java.lang.String

    }

}

编辑:很多人对这个问题感到困惑。上界的使用只是为了创建这个例子。我知道这是不正确的。 怀疑是,如果你反编译上面的代码,你会看到编译器将 t.getTheString(implClass.class) 结果类型转换为 String,因为它可能不确定类型。我的问题是,如果它被类型转换为 Object 会不会更好。在这种情况下,它将调用 toString() 方法并且代码可以正常工作。 这是 Java 编译器的问题还是我在这里遗漏了什么?

【问题讨论】:

  • 有趣;并且没有初步的想法;但除了实际问题:Java 中的类名以大写字母开头;偏离该标准会使您的代码更难为所有人阅读。此外:请使用预览功能并花时间正确格式化/缩进您的源代码所有

标签: java exception


【解决方案1】:

其他答案中已经提到了问题的原因-您的classType参数可以是实现testInterface的任何类型,不一定是T

这里有一个解决方案。

目前的问题是classType没有被约束,没有足够的上下文,T不能被正确推断,导致obj被强制转换为错误的类型。所以如果我们把方法改成这样:

public <T extends testInterface> T getTheString(Class<T> classType){

T从参数每次都能正确推断出来!

另一种解决方案是向编译器提供您想要的类型:

System.out.println((testInterface)t.getTheString(implClass.class));

从您的评论到另一个答案:

如果编译器不确定类型为什么不将其类型转换为 Object 而不是字符串

只有两个println 的重载可供编译器选择调用,因为其他的都是原始类型。它是println(Object)println(String)。根据 JLS(第 15.12.2 节):

此步骤使用方法的名称和参数表达式的类型来定位可访问和适用的方法。可能有多个这样的方法,在这种情况下选择最具体的一个。

请参阅this post 了解更多信息。

由于 String 比 Object 更具体,所以选择后者。

【讨论】:

    【解决方案2】:

    泛型在运行时被删除,所以当 JVM 需要选择 System.out.println() 的哪个变体来调用它时,它不知道它是什么 impl 并假设传入类是 String 并采用 System.out.println(String string) 显然您的 implClass 实例不是 String

    这一切都来自您使用泛型,而不是在类级别声明它们。更改 testClass 声明以声明您的通用 T class testClass<T> { 一旦你这样做了,编译器应该在尝试将 testInterface 转换为 implClass 时开始抱怨缺少演员表 implClass i = t.getTheString(implClass.class); 一旦你像这样更正它: implClass i = (implClass) t.getTheString(implClass.class);

    System.out.println() 将按预期开始工作。

    如果您想知道为什么编译器会选择错误的方法,请考虑以下问题: - 类型 T 没有在类级别声明,所以当它在编译过程中被擦除时,它变成“任何东西”, - 当编译器稍后试图找出要调用的println() 方法的匹配变体时,它首先匹配,因为类型已变为“任何东西”,第一个方法变体将匹配 - 在 Oracle JDK impl 的 PrintStream 中,第一个 impl 恰好是 String 作为参数的那个(检查源代码;)

    【讨论】:

      【解决方案3】:

      对我来说很好;重命名并作为 Main.java 运行:

      > javac Main.java 
      Note: Main.java uses unchecked or unsafe operations.
      Note: Recompile with -Xlint:unchecked for details.
      > java Main
      implClass@15dc8287 Overridden toString()
      

      然后:

      > javac -Xlint:unchecked Main.java 
      Main.java:10: warning: [unchecked] unchecked cast
                  return (T)obj;
                        ^
      required: T
      found:    testInterface
      where T is a type-variable:
        T extends testInterface declared in method <T>getTheString(Class<? extends testInterface>)
      

      这里的问题:在您的方法 getTheString() 中,您强制转换为 T;但是不能保证传入的类与那个 T! 有任何关系!实现该接口的类不一定也是 T。

      对于您看到的例外情况;如前所述 - 不可重现(您这边的一些设置问题)?

      【讨论】:

      • 正如 Scovetta 指出的,如果你反编译这段代码,System.out.println((String)testClass2.getTheString(implClass.class));编译器尝试将其类型转换为 String 。它应该被类型转换为 Object 。
      【解决方案4】:

      如果您取消注释这两个“它可以工作”行并通过反编译器运行 genericTest.class(我使用了http://www.javadecompilers.com/),您会看到:

      public static void main(String[] arrstring) {
          testClass testClass2 = new testClass();
          Object t = testClass2.getTheString(implClass.class);
          implClass implClass2 = (implClass)testClass2.getTheString(implClass.class);
          System.out.println((Object)implClass2);
          System.out.println(t);
          System.out.println((String)testClass2.getTheString(implClass.class));
      }
      

      println 的前两次调用调用接受对象的方法,该方法又调用toString() 并打印您期望的结果。在第三个println 中,编译器期望结果确实是一个字符串,并尝试进行强制转换,这是您从中获取异常的地方。

      【讨论】:

      • 这正是我的问题。如果编译器不确定类型为什么不将其类型转换为 Object 而不是 String
      猜你喜欢
      • 2014-08-18
      • 1970-01-01
      • 1970-01-01
      • 2013-06-03
      • 1970-01-01
      • 1970-01-01
      • 2017-05-25
      • 2012-05-16
      • 1970-01-01
      相关资源
      最近更新 更多