任何人都可以解释相同的Java源代码如何最终编译成二进制不同的类文件?
问题来自以下情况:
我们有一个相当大的应用程序(800多个类)已经分支,重组然后重新集成回主干。在重新集成之前,我们将主干合并到分支中,这是标准过程。
最终结果是一组带有分支源的目录和一组带有主干源的目录。使用Beyond Compare,我们能够确定两组源相同。但是,在编译(使用IntelliJ v11中托管的maven的相同的JDK )时,我们注意到大约十几个类文件是不同的。
当我们为每对明显不同的类文件反编译源代码时,我们最终得到了相同的java源代码,因此就最终结果而言,它似乎并不重要。但为什么只有少数文件不同?
感谢。
补充思想:
如果maven / javac以不同的顺序编译文件,可能会影响最终结果吗?
答案 0 :(得分:6)
假设JDK版本,构建工具版本和构建/编译选项相同,我仍然可以想到5 可能的差异来源:
时间戳 - 类文件可能 1 包含编译时间戳。除非您在完全相同的时间运行编译,否则同一文件的不同编辑将导致不同的时间戳。
源文件名路径 - 每个类文件都包含源文件的路径名。如果编译具有不同路径名的两个树,则类文件将包含不同的源路径名。
导入的编译时常量的值 - 当类A
使用在另一个类B
中定义的编译时常量时(请参阅JLS以获取“编译时常量”的定义) ),常量的值被合并到A
的类文件中。因此,如果您针对不同版本的A
编译B
(使用不同的常量值),则A
的代码可能会有所不同。
外部类/方法签名的差异;例如如果您在其中一个POM文件中更改了依赖项版本。
构建类路径的差异可能导致找到导入类的顺序不同,这可能导致类文件的常量池中条目顺序的非显着差异。这可能是由于以下事情而发生的:
请注意,您通常不会看到FS目录中文件的实际顺序,因为ls
和dir
等工具默认在显示条目之前对其进行排序。
1 - 这取决于编译器。此外,无法保证javap
将显示时间戳...如果它们存在。
我应该补充一点,确定差异原因的第一步是弄清楚它们究竟是什么。您可能需要(需要)以这种方式执行此操作 - 通过手动解码一对类文件来识别它们实际存在差异的位置......以及这些差异实际意味着什么。
答案 1 :(得分:2)
使用超比较进行比较时,将根据文件内容进行比较。但是在构建过程中,只检查源文件的时间戳是否有变化。因此,您的源文件的最后修改日期更改将重新编译。
答案 2 :(得分:1)
不同的JDK产生不同的二进制类(优化,但也有类版本号)。还有编译选项(JDK可以用旧格式编译,或者它可以添加调试信息)。
答案 3 :(得分:1)
不同版本的Java可以添加不同的元数据,这些元数据通常会被反编译器忽略。
我建议您尝试使用javap -c -v
获取文件中的更多详细信息。如果这没有帮助,您可以使用查看每个字节的ASMifierClassVisitor。
答案 4 :(得分:1)
同样的JDK也可以有不同的输出,具体取决于你的编译方式。 您可以使用或不使用调试信息进行编译,您可以编译为在旧版本中运行,每个选项都将导致其他类。