前言

今天突然看到一个这样的问题,说是为什么将class文件的东西直接复制到txt文档中,再改成class文件就不能运行了。觉得是一个很有意思的问题,便自己研究了一下,下面是我的一些发现。

准备工作

写一个java类用来测试

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

Java文件变为灰色的了 为什么java文件转class文件_jvm


编译完成之后,用记事本打开的效果

Java文件变为灰色的了 为什么java文件转class文件_版本号_02


注意到这个class文件的大小是414字节

将用记事本打开的Test.class文件另存为Test.class(放在另一个目录下),选择编码为ANSI。接下来讲讲为什么这里不设置成别的编码:

魔数

在讲为什么设置编码为ANSI之前,先讲讲魔数的概念。我的理解是这样的,有些文件为了标明一些信息,比如自己是什么类型的文件等等,会在文件开头添加几个字节,一般把这几个字节的数值称为魔数。

ANSI在Windows中一般是指当前locale的默认编码,简体中文版的Windows中就是GBK。之前另存为选择ANSI编码,而不选择其他编码的原因,就是因为选择ANSI由于是用当前系统默认的编码,这样的文本文档并不会在文件开头加上魔数,来标明该文档所应用的编码。可以尝试另存为utf8,会发现这样的文本文档会比最开始的class文件多出两个字节,这就是utf8的魔数。

另存为ANSI编码,能保证文件和最开始的class文件字节数完全一样。由于Java的字节码文件在文件开头的四个字节是它自己的魔数,所以至少要保证我们从复制下来不会由于编码问题导致新增的字节(魔数),使得原来的Java字节码文件的魔数失效,jvm无法识别该class文件。

开始运行

当我用java指令去运行刚刚复制粘贴得到的Test.class文件时,发现还是报错了,如图:

Java文件变为灰色的了 为什么java文件转class文件_Java文件变为灰色的了_03

这里提示我们当前的jre版本不能运行该class文件

经过各种搜索知道,Java的字节码文件开始四个字节标识这是一个class文件,即刚刚介绍的魔数,之后两个字节标识副版本号,再之后两个字节标识主版本号。

版本号

Java在用javac指令编译java文件后,会指出当前编译的版本,如果之后运行class文件的jvm版本低于这个,那么将抛出错误(比如上面),无法运行该class文件。(Java字节码文件的副版本号好像一直都是0000,而且感觉为什么一个版本号需要四个字节来标识,有些浪费,如果有大佬知道的话,还请赐教)

回到这个错误。这是一个可以打开二进制文件的网站。将javac编译出来的class文件和复制粘贴出来的class文件分别上传上去,对比差别。

Java文件变为灰色的了 为什么java文件转class文件_jvm_04


javac编译出来的class文件

Java文件变为灰色的了 为什么java文件转class文件_jvm_05


复制粘贴产生的class文件

注意到开头四个字节——CAFEBABE,这就是Java字节码的魔数,刚刚用记事本打开的乱码,最开始的两个汉字漱壕,我们知道这个是用GBK编码打开的,如果去搜索这两个汉字的GBK编码会发现,就是CAFEBABE。之后四个字节标识版本号,这里两个文件出现了不同。

为什么会不同

发生变化的都是数值为00(八进制)的字节,GBK是兼容ASCII的,在ASCII中是按单字节解析的,也就是三个00分别对应三个ASCII码值,查ASCII码表可知,00是控制字符,不能被打印。

Java文件变为灰色的了 为什么java文件转class文件_Java文件变为灰色的了_06


回到之前用记事本打开的图,可以发现在乱码漱壕后,有三个空格,紧接着是数字4,34(八进制)就是ASCII表中的数字4,从而也可以推测出Windows记事本在打开原字节码文件时将这些控制字符变成了空格。

再观察复制粘贴产生的class文件的版本号是20202034,翻译成十进制刚好就是8244.8224(主版本号在前,副版本号在后),即刚刚黑框框报错的信息中,显示的当前的class文件的版本。我的jre版本是1.8,对应的版本号是52.0,自然就报错了。

拓展

可以想象,就算这里的版本号不报错,之后也运行不起来,因为原来的字节码文件中还有很多00这样的控制字符,在转换为文本文档的过程中,他们就都丢失了。

总结

通过以上的实验可以发现确实直接的复制粘贴到文本文档中确实会导致class文件无法运行,而且也无法通过这种方式得到一个可以运行的class后缀的字节码文件,除非有什么编码中00是一个可以显示的字符