有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java generictyped方法返回错误的类型

每隔一段时间,我就觉得自己根本不懂Java。。我最近发现了Java类型转换的这种奇怪行为:

public static void main(String[] args) {
    String res = get();
    System.out.println(res);
}

public static <T> T get() {
    Object longObj = Long.valueOf("0");
    T casted = (T) longObj;
    System.out.println("longObj=" + longObj.getClass());
    System.out.println("casted=" + casted.getClass()); // <-- Why the type of "casted" is Long instead of String???
    return casted;
}

输出为:

longObj=class java.lang.Long
casted=class java.lang.Long
Exception in thread "main" java.lang.ClassCastException: java.lang.Long 
cannot be cast to java.lang.String
    at com.apple.geo.coreloc.Test.main(Test.java:5)

我的困惑是:为什么变量casted的类型是Long?它不应该被强制转换为类型T,即String

以下是我认为应该是String的原因:

  • 在运行时,String res = get()提示get()方法返回字符串
  • T casted = (T) longObj;应该尝试将cast Long键入字符串(我希望这里出现异常,但没有…)

共 (4) 个答案

  1. # 1 楼答案

    java泛型与C++模板不同。对于Java泛型方法,该方法只有一个版本。在C++中,模板化方法的代码根据特定类型需要生成,以便每个不同的参数化类型获得该方法的自己版本。p>

    要获得预期的错误,必须告诉Java泛型类型,从而帮助Java解决问题

    class A {
      public static void main(String[] args) {
        String res = get(String.class);
        System.out.println(res);
      }
    
      public static <T> T get( Class<T> klass) {
    
        Object longObj = Long.valueOf("0");
        T casted = klass.cast(longObj);
        System.out.println("longObj=" + longObj.getClass());
        System.out.println("casted=" + casted.getClass());
        return casted;
      }
    }
    

    运行此命令会出现强制转换异常:

    Exception in thread "main" java.lang.ClassCastException: Cannot cast java.lang.Long to java.lang.String
        at java.lang.Class.cast(Class.java:3369)
        at A.get(A.java:10)
        at A.main(A.java:4)
    
  2. # 2 楼答案

    铸造不同于转化

    强制转换是一种声明,表明对象属于目标类型。它不会更改当前对象的类

    转换是将一种类型的对象重新创建为另一种类型的对象的过程

    这就是为什么只有当你已经知道你正在铸造的对象的类型时,才应该使用铸造。在所有其他情况下,应该将对象转换为目标类型的实例

  3. # 3 楼答案

    在Java中,泛型类型只在编译时工作,但由于所谓的“类型擦除”(参见herehere),它(几乎)没有关于特定目标泛型类型的信息

    如果反编译代码,您将看到:

    public static void main(String[] args) {
        String res = (String)get();
        System.out.println(res);
    }
    
    public static <T> T get() {
        Object longObj = Long.valueOf("123");
        System.out.println("longObj=" + longObj.getClass());
        System.out.println("casted=" + longObj.getClass());
        return longObj;
    }
    

    这里一切都很清楚:

    • main中,这只是强制转换操作
    • get中,带有“casted”值的变量被完全删除
  4. # 4 楼答案

    泛型是编译时检查。它们不是写糟糕演员阵容的保障

    你试着做(String) longIbj;,而longObj是一个很长的过程。这会给你一个运行时ClassCastException

    但是,因为它不是T get()上下文中的编译时错误,所以T get()被正确编译。由于“类型擦除”,T被提升为最高约束类型。您没有限制类型的上限,因此编译器处理的代码大致如下:

    public static Object get() {
        Object longObj = Long.valueOf("0");
        Object casted = (Object) longObj;
        System.out.println("longObj=" + longObj.getClass());
        System.out.println("casted=" + casted.getClass()); // <-- Why the type of "casted" is Long instead of String???
        return casted;
    }
    

    由于<T>是一个Object,填充longObjcasted的两行代码大致相同。在任何情况下,通过任何名称对对象引用的多态调用仍将解析为对象的.getClass()方法,该方法仍将返回java.lang.Long

    Java中的强制转换不会创建新对象。如果你想要一个不同的对象,你需要new一个不同的对象,并用相关信息填充它。java中的强制转换只能通过强制转换类型的方法名访问类。因此,在Long转换为Object时,您可能会丢失getLong(),但它仍然存在(如果您通过^{类型的变量调用它)