【问题标题】:How does the Java cast operator work?Java 强制转换运算符是如何工作的?
【发布时间】:2013-12-30 18:41:31
【问题描述】:

我正在尝试调试涉及 Java 中的 ClassCastException 的问题。为了解决这个问题,我需要知道当我从 Object 转换为特定类型时发生了什么。 谁能向我解释一下 Java cast 运算符在 Java 级别和 JVM 级别是如何工作的?

【问题讨论】:

  • 你能举一个发生这种情况的例子吗?比如具体的类型是什么类,对象是什么类?
  • 亚当,我可能会问一个后续问题。本质上,我正在从“com.generic.MyType”类型的 Spring 创建一个 bean,但它无法转换为 (MyType)。我认为这可能是 Spring 问题而不是 Java 问题,但我正在努力做到彻底。
  • @nemo:我知道这很明显,但是在源代码中,您是否在顶部将 MyType 导入为 com.generic.MyType?或者您是否导入了多个包(import org.generic.*; import org.other.*;),其中可能包含多个名为 MyType 的类,而编译器可能选择了错误的类?
  • @Adam,实际上我没有为 cast 类型导入,如果我导入它,Netbeans 说它是未使用的。但是,该错误特别不能转换为 com.generic.MyType。如果我无法取得任何进展,我可能会在今天晚些时候发布带有一些真实细节的问题,我会在这里给你一个链接。
  • @nemo:请做;我们或许可以通过这种方式为您提供更多帮助。

标签: java casting jvm


【解决方案1】:

JLS 够好吗?

强制转换应用于强制转换运算符的操作数(第 15.16 节):操作数表达式的类型必须转换为强制强制转换运算符显式命名的类型。强制转换上下文允许使用:

  • 身份转换 (§5.1.1)
  • 一个扩大的基元转换(§5.1.2)
  • 一个缩小的原始转换(§5.1.3)
  • 一个扩大的参考转换(§5.1.5)可选地后跟一个未经检查的转换(§5.1.9)
  • 缩小参考转换 (§5.1.6) 可选地后跟未经检查的转换
  • 拳击转换 (§5.1.7)
  • 拆箱转换 (§5.1.8)。

其实,也许this part 更相关:

编译时引用类型S的值到编译时引用类型T的强制转换的编译时合法性详细规则如下:

  • 如果 S 是一个类类型:
    • 如果 T 是 一个类类型,然后是 |S| <:>T|,或|T| S|;否则是编译时 发生错误。此外,如果有 存在一个超类型 X T 和一个超类型 Y S,使得 XY 可证明是不同的 参数化类型(§4.5), 并且 X 的擦除和 Y 都是一样的,编译时 发生错误。
    • 如果 T 是接口类型:
      • 如果 S 不是final 类(§8.1.1), 那么,如果存在超类型 TX 和一个超类型 SY,使得两者 XY 是可证明的 不同的参数化类型,并且 XY 的擦除 是一样的,编译时错误 发生。否则,演员表总是 在编译时合法(因为即使 S 没有实现 TS 的子类可能)。
      • 如果 Sfinal 类(§8.1.1), 那么S必须实现T, 或发生编译时错误。

    • 如果 T 是一个类型变量,那么这个 递归应用算法, 使用 T 的上限 T 的位置。
    • 如果 T 是 数组类型,则 S 必须是 班级Object,或 发生编译时错误。
  • 如果 S 是 接口类型:
    • 如果 T 是 数组类型,则 T 必须 实现 S 或编译时 发生错误。
    • 如果 T 是不属于 final (§8.1.1), 那么如果存在超类型 TX 和一个超类型 SY,使得两者 XY 是可证明的 不同的参数化类型,并且 XY 的擦除 是一样的,编译时错误 发生。否则,演员表总是 在编译时合法(因为即使 T 没有实现 ST 的子类可能)。
    • 如果 Tfinal 的类型, 然后:
      • 如果 S 不是参数化的 类型或原始类型,则 T 必须 实现S,演员表是 静态已知是正确的,或 发生编译时错误。
      • 否则, S 是参数化的 调用某些类型的类型 泛型类型声明 G,或 对应于泛型的原始类型 类型声明 G。然后那里 必须存在一个超类型 X T,使得 XG 的调用,或 发生编译时错误。 此外,如果 SX 可证明是不同的参数化 类型然后编译时错误 发生。
  • 如果 S 是 一个类型变量,然后这个算法 递归地应用,使用 S 的上限代替 S
  • 如果 S 是数组类型SC[], 也就是说,一组组件 键入 SC:
    • 如果 T 是 类类型,那么如果 T 不是 Object,然后是 发生编译时错误(因为 Object 是唯一的类 可以分配数组的类型)。
    • 如果 T 是接口类型,然后是 除非发生编译时错误 T 是类型 java.io.Serializable 或 类型Cloneable, 仅由数组实现的接口。
    • 如果 T 是一个类型变量,那么:
      • 如果上 T 的边界是 Object 或类型 java.io.SerializableCloneable 类型,或 S 可以的类型变量 合法地被递归地强制转换为 应用这些规则,那么演员表是 合法(尽管未经检查)。
      • 如果上 T 的边界是数组类型 TC[],然后是编译时错误 除非类型 SC[] 可以 通过递归转换为 TC[] 这些编译时的应用 投射规则。
      • 否则,一个 发生编译时错误。
    • 如果 T 是 一个数组类型TC[],即一个 TC 类型的组件数组, 然后发生编译时错误 除非以下情况之一为真:
    • TCSC 是 相同的原始类型。
    • TCSC 是引用类型和类型 SC 可以通过以下方式转换为 TC 这些的递归应用 转换的编译时规则。

现在完全清楚了,不是吗? :D

换句话说,这是我在不了解您的问题的更多细节的情况下能做的最好的事情。

【讨论】:

  • 嗯...我去删除了第二部分的所有链接,但我不知道第一部分有什么问题。
  • 如果有人知道为什么链接表现得很奇怪,请继续修复它!
  • 也许它不喜欢锚标签?
  • 但是没有锚标签;我已经把它们全部剥离了。
【解决方案2】:

类转换神秘的一个可能原因是不仅类型必须匹配,而且它们必须由同一个类加载器加载。

您不仅应该能够转储类型层次结构,还应该能够转储每个类的类加载器的标识。

这类问题在应用程序代码和基础设施代码被故意隔离的 appserver 风格的环境中并不少见 - 例如,如果系统类意外包含在应用程序 JAR 中,您可以在 JVM 中拥有两个“相同”类的副本,并且生活变得混乱

【讨论】:

  • "你应该不仅可以转储类型层次结构,还可以转储每个类的类加载器的标识。"你是如何实现的?
  • 这是一个很好的建议,但是在对它们运行 .getClassLoader() 之后,这两个类似乎是由同一个类加载器加载的。
  • 对于其他可能正在排除故障的人,stackoverflow.com/questions/826319/… 有更多关于类加载器问题的信息。
【解决方案3】:

强制转换断言对象的运行时类型与给定的静态类型兼容,因此允许您在对象上调用该类型的方法。

这里的 obj 是一个 Integer 对象,但只能通过 Object 引用访问:

Object obj = new Integer(1);

铸造让您再次将其视为整数(或整数的某些超类):

System.out.println(((Integer) obj).intValue());

当给定的静态类型与对象的运行时类型不匹配时发生ClassCastException:

System.out.println(((Float) obj).intValue()); // runtime error

您可以使用 getClass() 和各种 Class 方法找到任何对象的运行时类型:

System.out.println(obj.getClass()); // prints "class java.lang.Integer"

【讨论】:

    【解决方案4】:

    在 Java 虚拟机规范中可以找到其他有用且权威的参考资料,特别是 §2.6.5, "Narrowing Reference Conversions",,尤其是 checkcast 指令的定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-17
      • 2020-03-03
      • 1970-01-01
      • 1970-01-01
      • 2019-05-15
      • 2015-07-09
      • 2023-03-20
      • 2012-01-04
      相关资源
      最近更新 更多