【问题标题】:Any type casting done by javac?javac完成的任何类型转换?
【发布时间】:2012-07-24 13:33:13
【问题描述】:

据我了解,如果可以在编译期间进行类型检查,则类型转换将在编译期间完成,不会产生任何运行时开销。

例如

public Child getChild() {
  Parent o = new Child();
  return (Child) o;
}

类型转换是在编译期间还是在运行时完成的?

是否有任何一般规则来确定类型转换是由 javac 编译器完成还是由 VM 完成?

【问题讨论】:

  • 好问题...但这对编码有影响吗?还是你只是为了自己的好奇心?
  • @Tenner 这两种方式都重要吗?
  • 在这个例子中,我希望在编译期间而不是在运行期间进行类型转换。但是,对于 return 语句,您不需要将 o 强制转换为 Child,因为 Java 知道它是基于 Object 图的 Child 类型。
  • @Jay 你建议 OP 引入编译器错误。
  • @Marko,如果 Parent 扩展了 Child,我认为这不是一个好主意,我很抱歉。

标签: java casting javac


【解决方案1】:

其实这种情况有三种可能:

  1. javac 编译器可以执行优化。
  2. JIT 编译器可以执行优化。
  3. JIT 编译器的本机代码可以包含执行运行时类型检查的代码。

我希望它是选项 1. 或 2。但这可能是特定于平台的。


事实上,在我的系统上,字节码并没有优化。如果要进行任何优化,它将由 JIT 编译器来完成。 (这符合我所听到的……大多数 Java 字节码编译器在生成字节码之前几乎没有做任何优化。)

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

public Child getChild();
  Code:
   0:   new #16; //class Child
   3:   dup
   4:   invokespecial   #18; //Method Child."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   checkcast   #16; //class Child
   12:  areturn

}

【讨论】:

  • @StephenC 我看到了同样的结果。如果我将getChild() 设置为私有方法,则似乎发生了优化。 javap -c 日志中没有更多 getChild() 块(请参阅我的答案)。我真的不明白为什么它会有所作为:/
  • @StephenC 谢谢。我完全同意你列出的 3 种可能性。我的期望是 javac 或其他字节码编译器会删除一些类型转换,因为在执行期间懒惰地做这件事没有什么意义。此外,我想知道是否有任何编码模式可以触发字节码编译器的优化。但是好像javac不会做这种优化。
  • “但似乎 javac 不会做这种优化。” - 底线是优化在哪里完成并不重要,只要是完成了。
【解决方案2】:

当正在运行的程序尝试将对象引用转换为另一种类型时,虚拟机必须检查被转换为的类型是被引用对象的实际类还是它的超类型之一。当程序执行 instanceof 操作时,它必须执行相同类型的检查。

在任何一种情况下,虚拟机都必须查看被引用对象的类数据。当程序调用实例方法时,虚拟机必须执行动态绑定:它必须选择调用的方法不是基于引用的类型,而是基于对象的类。为此,它必须再次访问仅给定对象引用的类数据。

编辑:

Java 编译器不负责检查转换是否正确,就像某些绑定只在运行时发生一样。 Java 虚拟机在运行时进行检查以确定实际引用对象是否是新类型的合法对象。如果没有,就会出现运行时异常:ClassCastException。

【讨论】:

  • javac 可以在没有向下转换的情况下编译该代码,因此运行时永远不会知道涉及到 Parent 类型。
【解决方案3】:

我猜它在两个阶段都完成了。在编译时,编译器会强制你进行正确的转换,以确保你没有混淆类型,就像在任何 strongly typed language 中一样。

但是,如果您将 Object 作为参数转换为 String(这将适用于实际上是 instanceof String 的对象),JVM 仍然必须确保实现Object 的类真正扩展或者是 String,如果不是,您将获得 ClassCastException

【讨论】:

    【解决方案4】:

    当我编译时

    public class Test {
        public Child getChildVersion1() {
            Parent o = new Child();
            return (Child) o;
        }
        public Child getChildVersion2() {
            return new Child();
        }
    }
    

    并在 Java 7(Windows 7 64 位)上使用 javap -c Test 反编译该代码,它给了我这个结果

    Compiled from "Test.java"
    public class Test {
      public Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public Child getChildVersion1();
        Code:
           0: new           #2                  // class Child
           3: dup
           4: invokespecial #3                  // Method Child."<init>":()V
           7: astore_1
           8: aload_1
           9: checkcast     #2                  // class Child
          12: areturn
    
      public Child getChildVersion2();
        Code:
           0: new           #2                  // class Child
           3: dup
           4: invokespecial #3                  // Method Child."<init>":()V
           7: areturn
    }
    

    所以编译器没有将方法 getChildVersion1 优化为像 getChildVersion2 这样的接缝,所以除了在编译时检查类型时间还有在运行时检查9: checkcast #2)。但正如 Stephen C mentioned 一样,它可能与平台(操作系统、Java 版本)有关。

    【讨论】:

      【解决方案5】:

      对于不需要在运行时进行测试的转换,编译器可能会进行一些优化以避免在运行时进行强制转换。

      我建议阅读JLS Chapter 5. Conversions and Promotions 以了解更多关于需要在运行时测试的转换类型。

      示例 5.0-1。编译时和运行时的转换

      A conversion from type Object to type Thread requires a run-time check to make sure that the run-time value is actually an instance of class Thread or one of its subclasses; if it is not, an exception is thrown.
      
      A conversion from type Thread to type Object requires no run-time action; Thread is a subclass of Object, so any reference produced by an expression of type Thread is a valid reference value of type Object.
      
      A conversion from type int to type long requires run-time sign-extension of a 32-bit integer value to the 64-bit long representation. No information is lost.
      
      A conversion from type double to type long requires a nontrivial translation from a 64-bit floating-point value to the 64-bit integer representation. Depending on the actual run-time value, information may be lost.
      

      5.1.6. Narrowing Reference Conversion

      此类转换需要在运行时进行测试,以确定实际引用值是否是新类型的合法值。如果不是,则抛出 ClassCastException。

      5.1.8. Unboxing Conversion ;转换在运行时进行。

      另见:5.5.3. Checked Casts at Run-time

      例如,要确定转换发生的时间并不容易:

      public class Main {
      
          private static class Child extends Parent{
      
              public Child() {
              }
          }
      
          private static class Parent {
      
              public Parent() {
              }
          }
      
          private static Child getChild() {
              Parent o = new Child();
              return (Child) o;
          }
      
      
          public static void main(final String[] args) {
              Child c = getChild();
          }
      }
      

      javap -c Main给出的结果是:

      public class Main extends java.lang.Object{
      public Main();
      Code:
      0:  aload_0
      1:  invokespecial   #1; //Method java/lang/Object."<init>":()V
      4:  return
      
      public static void main(java.lang.String[]);
      Code:
      0:  invokestatic    #4; //Method getChild:()LMain$Child;
      3:  astore_1
      4:  return
      
      }
      

      如果将方法声明更改为public static Child getChild(),结果是:

      public class Main extends java.lang.Object{
      public Main();
      Code:
      0:  aload_0
      1:  invokespecial   #1; //Method java/lang/Object."<init>":()V
      4:  return
      
      public static Main$Child getChild();
      Code:
      0:  new #2; //class Main$Child
      3:  dup
      4:  invokespecial   #3; //Method Main$Child."<init>":()V
      7:  astore_0
      8:  aload_0
      9:  checkcast   #2; //class Main$Child
      12: areturn
      
      public static void main(java.lang.String[]);
      Code:
      0:  invokestatic    #4; //Method getChild:()LMain$Child;
      3:  astore_1
      4:  return
      
      }
      

      您会发现,仅更改访问器就会对可能的优化产生很大影响。

      【讨论】:

      • 我查过了。 'private static getChild()' 仍然存在于类中。只是由于某种原因(或错误) javap 不输出它。字节码也没有区别。
      猜你喜欢
      • 2012-10-02
      • 1970-01-01
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多