【问题标题】:Ambiguous behaviour in casting铸造中的模棱两可的行为
【发布时间】:2019-09-09 11:25:23
【问题描述】:

我正在教学生老式的泛型,但遇到了一个看不见的!我在演讲时的行为! :(

我有一个简单的课程

public class ObjectUtility {

  public static void main(String[] args) {
    System.out.println(castToType(10,new HashMap<Integer,Integer>()));
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

这给出了 10 的输出,没有任何错误!!!我期待这会给我一个 ClassCastException,出现一些错误,例如 Integer cannot be cast to HashMap。

Curious and Furious,我在返回值上尝试了getClass(),如下所示

System.out.println(castToType(10,new HashMap<Integer,Integer>()).getClass());

正如我预期的那样抛出 ClassCastException。

另外,当我将同一个语句一分为二时,类似于

Object o = castToType(10,new HashMap<Integer,Integer>());
System.out.println(o.getClass());

它没有抛出任何错误并打印class java.lang.Integer

全部用

执行
openjdk version "1.7.0_181"
OpenJDK Runtime Environment (Zulu 7.23.0.1-macosx) (build 1.7.0_181-b01)
OpenJDK 64-Bit Server VM (Zulu 7.23.0.1-macosx) (build 24.181-b01, mixed mode)

有人可以为我指出为什么会发生这种行为的正确方向吗?

【问题讨论】:

  • 正如@Michael 在他的回答中所说。要更好地了解正在发生的事情,请查看type erasure
  • 当然@kenny_k,但我仍然不清楚我期待的答案的第二部分
  • 启用所有编译器警告会对此有所了解。

标签: java generics casting


【解决方案1】:

T 在运行时不存在。它解析为约束的下限。在这种情况下,没有,所以它解析为Object。一切都可以转换为Object,所以没有类转换异常。

如果你确实要将约束更改为这个

private static <V,T extends Map<?,?>> T castToType(V value, T type){
    return (T) value;
}

然后对T 的强制转换变为对下限Map 的强制转换,显然Integer 不是,并且您会得到您期望的类强制转换异常。


另外,当我将同一个语句一分为二时,类似于

Object o = castToType(10,new HashMap<Integer,Integer>());
System.out.println(o.getClass());

它没有抛出任何错误

castToType(10,new HashMap<Integer,Integer>()).getClass() 

这会引发一个类转换异常,因为它静态链接到方法 HashMap::getClassnot Object::getClass),因为签名表示期望 HashMap 作为返回值。这需要对HashMap 进行隐式强制转换,但会失败,因为castToType 在运行时返回Integer

当你第一次使用它时

Object o = castToType(10,new HashMap<Integer,Integer>());

您现在静态链接到 Object::getClass,无论实际返回什么都可以。

“未拆分”版本相当于这个

final HashMap<Integer, Integer> map = castToType(10, new HashMap<>());
System.out.println(map.getClass());

希望能证明差异

【讨论】:

  • 酷,是的。听起来很适合第一个场景。你能否解释一下为什么System.out.println(castToType(10,new HashMap&lt;Integer,Integer&gt;()).getClass()); 会抛出异常,而如果我把它分成两个不同的语句,我什么也得不到?
  • 非常感谢!!对于初学者甚至专业人士来说,这肯定是一个难题!感谢您用漂亮而简单的词来表达这一点!
  • @MohamedAneesA 当然,没问题。我怀疑我的解释会更糟糕,不管有多少双学生的眼睛期待地盯着我;)
  • 是的!不知何故,我有时把脸藏在讲台下!我明天会解释他们,并记得引用这个答案!再次感谢:)
【解决方案2】:

您可以使用 javap 工具查看差异。

默认情况下,编译过程会进行代码优化,将通用类型更改为原始类型

第一个代码:

public class ObjectUtility {

  public static void main(String[] args) {
    System.out.println(castToType(10,new java.util.HashMap<Integer,Integer>()));
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

真正的伪代码:

Compiled from "ObjectUtility.java"
public class ObjectUtility {
  public ObjectUtility();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: bipush        10
       5: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       8: new           #4                  // class java/util/HashMap
      11: dup
      12: invokespecial #5                  // Method java/util/HashMap."<init>":()V
      15: invokestatic  #6                  // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      18: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      21: return
    LineNumberTable:
      line 4: 0
      line 5: 21

  private static <V, T> T castToType(V, T);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    Code:
       0: aload_0
       1: areturn
    LineNumberTable:
      line 8: 0
}

Generic 类型的调用被更改为 Object 并且 Integer.valueOf 被添加到 System out print 中。

第二个代码:

public class ObjectUtility {

  public static void main(String[] args) {
    System.out.println(castToType(10,new java.util.HashMap<Integer,Integer>()).getClass());
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

真正的伪代码:

Compiled from "ObjectUtility.java"
public class ObjectUtility {
  public ObjectUtility();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: bipush        10
       5: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       8: new           #4                  // class java/util/HashMap
      11: dup
      12: invokespecial #5                  // Method java/util/HashMap."<init>":()V
      15: invokestatic  #6                  // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      18: checkcast     #4                  // class java/util/HashMap
      21: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      27: return
    LineNumberTable:
      line 4: 0
      line 5: 27

  private static <V, T> T castToType(V, T);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    Code:
       0: aload_0
       1: areturn
    LineNumberTable:
      line 8: 0
}

checkcast 在 HashMap 上调用,但签名更改为 Object,returnt 是 int 的值,没有在 castToType 内进行强制转换。 “int”原始类型导致无效的强制转换

第三密码:

public class ObjectUtility {

  public static void main(String[] args) {
    Object o = castToType(10,new java.util.HashMap<Integer,Integer>());
    System.out.println(o.getClass());
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

真正的伪代码:

Compiled from "ObjectUtility.java"
public class ObjectUtility {
  public ObjectUtility();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: bipush        10
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: new           #3                  // class java/util/HashMap
       8: dup
       9: invokespecial #4                  // Method java/util/HashMap."<init>":()V
      12: invokestatic  #5                  // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      15: astore_1
      16: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: aload_1
      20: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      23: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      26: return
    LineNumberTable:
      line 4: 0
      line 5: 16
      line 6: 26

  private static <V, T> T castToType(V, T);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    Code:
       0: aload_0
       1: areturn
    LineNumberTable:
      line 9: 0
}

在这种情况下,方法类似于第一种。 castToType 原样返回第一个参数。

如您所见,java 编译器进行了一些“性能”更改,这些更改可能会在某些情况下产生影响。泛型是源代码的“发明”,在任何情况下最终都会转换为所需的真实类型。

【讨论】:

  • 不过还是要努力。我懒得检查字节码
  • @Dubas 感谢您的努力!我想这两个答案都一样正确......我必须了解更多关于如何解释javap 输出
  • @MohamedAneesA Cheatsheet
  • @Michael 谢谢!!会通过的!
  • @迈克尔。伪代码是对真实代码或字节码的任何类型的解释,使用“javap”工具生成一个“伪代码”,该“伪代码”接近类中的字节码,但更“人类可读”。但是用于解释未来编码的自然语言也可以称为“伪代码”,它不是真正的源代码,但可以描述过程。
猜你喜欢
  • 2020-06-03
  • 2014-05-17
  • 1970-01-01
  • 2012-06-09
  • 2011-01-09
  • 2017-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多