编译后,导入类中的哪些信息存储在编译的类中?

时间:2014-10-16 19:29:31

标签: java compilation

想象一下,我们有一些类A.java。它以某种方式使用B.java,即导入它,调用它的方法,使用它的属性等。我们编译文件。

现在我想更深入地了解我们得到的东西。

  1. A.class是否存储了有关B内部的一些信息?什么信息 - 只是名称,被调用的方法,使用的最终变量? 如果在编译之后A.class中没有关于B的那么多信息 - 为什么我们不能编译A而没有B.class?

  2. 如果我们将B.class中的B.class替换为另一个B.class - 当它工作时和不工作时?

  3. 如果A.class使用新的B.class运行正常,A.class是否可以使用OLD B.class中的一些信息,这些信息在编译期间被合并到A.class中?即我们的项目中可能最终有一个B.class的混合逻辑吗?

  4. 基于上述问题的答案:我们能否以某种方式编译依赖于其他类的类而不知道它们在编译时的确切实现,并仅在运行时提供依赖性?

2 个答案:

答案 0 :(得分:3)

“import”关键字实际上不会导入任何内容。基本上它只是一种通过类名引用类的完整路径的方法。所以当我们说

import java.lang.String;

然后我们可以在代码中通过名称“String”来引用该类。在编译时,我们将类“String”的任何地方替换为“java.lang.String”。就是这样。

这也是为什么如果您有多个具有相同名称但在不同包中的类,您最多只能导入其中一个。您必须通过其完全限定名称来引用另一个类。

问题:

<强> 1。 A.class是否存储了有关B内部的一些信息?只是名称,被调用的方法,使用的最终变量?如果在编译之后A.class中没有关于B的那么多信息 - 为什么我们不能编译A而没有B.class?

不是真的。在调用其他类时,A类仅使用完全限定的类名和方法签名。使用我们的String示例,调用

 mystring.charAt(0) 

将编译成字节代码,如下所示:

   aload_1 [myString]
   iconst_0
   invokevirtual java.lang.String.charAt(int)

除了可能的内联常量之外,其他类的内部状态都不存储在我们的A类中。因此,如果值可能在将来发生变化,请务必小心制作字段public final。 (见下面的解决方法)

编译A类时我们需要B.class,因此编译器可以确保B类具有我们想要使用的那些方法。

<强> 2。如果我们将B.class中的B.class替换为另一个B.class - 当它工作时,何时不工作? 如果完整的限定名称(包等)相同并且它具有我们正在尝试使用的正确方法签名,则新的B.class将起作用。新的B类可以添加新方法,甚至可以使用我们使用的方法的不同实现。但是,它不能修改我们使用的方法的方法签名。如果旧方法不再存在或其签名不同,我们将在编译时获得java.lang.LinkageError

第3。如果A.class使用新的B.class运行正常,A.class是否可以使用OLD B.class中的一些信息,这些信息在编译期间被合并到A.class中?即我们的项目中最终可能有B.class的混合逻辑吗?

唯一的问题可能是来自旧B的内联常量。这就是为什么Java Coding Guidelines项31(第115页)说“不要将公共final应用于其值可能在以后的版本中变化的常量”。 相反,制作一个getter方法: 例如:

class BadFoo{
    //bad, VERSION could be inlined
    //and later when we change the version, other classes will have the old value!
   public static final int VERSION =1;

}

class BetterFoo{
   private static int version =1;

   //outside classes must call method getVersion()
   //field version can not be inlined at compile time.
   //JIT may inline at runtime which is OK
   public static final int getVersion(){
      return version;
   }

}

<强> 4。基于上述问题的答案:我们能否以某种方式编译依赖于其他类的类而不知道它们在编译时的确切实现,并仅在运行时提供依赖性?

是,接口代码。

接口应包含您需要调用的所有方法。我们的A类应仅通过接口引用调用者。如果传入我们要调用的对象实例,那么A类不知道(或需要知道)实际类型是什么,并且在编译时不需要知道它。

这是Dependency Injection

的主要目的和优势之一

除了依赖注入之外,对接口进行编码也具有优势。例如,如果我们使用Map而不是HashMap,我们稍后可以更改代码以使用不同的Map实现,例如ConcurrentHashMap,只需更改代码中的一个位置即可。我们的其余代码只会工作,因为它只知道它是一个Map。

答案 1 :(得分:2)

这由Java语言规范的Chapter 13. Binary Compatibility解决。

由于这可能比您想要的更具体,我将简要总结一下:

如果班级A使用班级f的功能B,则A的班级档案必须包含对f的符号引用。加载A类但B未提供f时,加载类A失败并显示java.lang.LinkageError,这会阻止A被使用。

一个例外是编译时常量表达式,编译器必须内联,即符号引用必须在编译时由它们的值替换,并且即使已经更改了类B,类A仍将继续使用原始值。 / p>

另一个例外是添加或删除注释对程序的二进制表示的正确链接没有影响。

在编译时没有关于B的其他信息被合并到A中,关于B的所有其他信息都可以更改,而不会影响已编译的类A的使用