java版本详解

时间:2022-03-13 00:57:55

java版本

在Java开发中,许多童鞋经常被各种版本的JDK搞得晕头转向,本节我们就来详细讲解Java程序编译后的class文件版本问题。

我们通常说的Java 8,Java 11,Java 17,是指JDK的版本,也就是JVM的版本,更确切地说,就是​​java.exe​​这个程序的版本:

$ java -version
java version "17" 2021-09-14 LTS

而每个版本的JVM,它能执行的class文件版本也不同。例如,Java 11对应的class文件版本是55,而Java 17对应的class文件版本是61。

如果用Java 11编译一个Java程序,输出的class文件版本默认就是55,这个class既可以在Java 11上运行,也可以在Java 17上运行,因为Java 17支持的class文件版本是61,表示“最多支持到版本61”。

如果用Java 17编译一个Java程序,输出的class文件版本默认就是61,它可以在Java 17、Java 18上运行,但不可能在Java 11上运行,因为Java 11支持的class版本最多到55。如果使用低于Java 17的JVM运行,会得到一个​​UnsupportedClassVersionError​​,错误信息类似:

java.lang.UnsupportedClassVersionError: Xxx has been compiled by a more recent version of the Java Runtime...

只要看到​​UnsupportedClassVersionError​​就表示当前要加载的class文件版本超过了JVM的能力,必须使用更高版本的JVM才能运行。

打个比方,用Word 2013保存一个Word文件,这个文件也可以在Word 2016上打开。但反过来,用Word 2016保存一个Word文件,就无法使用Word 2013打开。

但是,且慢,用Word 2016也可以保存一个格式为Word 2013的文件,这样保存的Word文件就可以用低版本的Word 2013打开,但前提是保存时必须明确指定文件格式兼容Word 2013。

类似的,对应到JVM的class文件,我们也可以用Java 17编译一个Java程序,指定输出的class版本要兼容Java 11(即class版本55),这样编译生成的class文件就可以在Java >=11的环境中运行。

指定编译输出有两种方式,一种是在​​javac​​命令行中用参数​​--release​​设置:

$ javac --release 11 Main.java

参数​​--release 11​​表示源码兼容Java 11,编译的class输出版本为Java 11兼容,即class版本55。

第二种方式是用参数​​--source​​指定源码版本,用参数​​--target​​指定输出class版本:

$ javac --source 9 --target 11 Main.java

上述命令如果使用Java 17的JDK编译,它会把源码视为Java 9兼容版本,并输出class为Java 11兼容版本。注意​​--release​​参数和​​--source --target​​参数只能二选一,不能同时设置。

然而,指定版本如果低于当前的JDK版本,会有一些潜在的问题。例如,我们用Java 17编译​​Hello.java​​,参数设置​​--source 9​​和​​--target 11​​:

public class Hello {
public static void hello(String name) {
System.out.println("hello".indent(4));
}
}

用低于Java 11的JVM运行​​Hello​​会得到一个​​LinkageError​​,因为无法加载​​Hello.class​​文件,而用Java 11运行​​Hello​​会得到一个​​NoSuchMethodError​​,因为​​String.indent()​​方法是从Java 12才添加进来的,Java 11的​​String​​版本根本没有​​indent()​​方法。

 注:如果使用--release 11则会在编译时检查该方法是否在Java 11中存在。

因此,如果运行时的JVM版本是Java 11,则编译时也最好使用Java 11,而不是用高版本的JDK编译输出低版本的class。

如果使用​​javac​​编译时不指定任何版本参数,那么相当于使用​​--release 当前版本​​编译,即源码版本和输出版本均为当前版本。

在开发阶段,多个版本的JDK可以同时安装,当前使用的JDK版本可由​​JAVA_HOME​​环境变量切换。

源码版本

在编写源代码的时候,我们通常会预设一个源码的版本。在编译的时候,如果用​​--source​​或​​--release​​指定源码版本,则使用指定的源码版本检查语法。

例如,使用了lambda表达式的源码版本至少要为8才能编译,使用了​​var​​关键字的源码版本至少要为10才能编译,使用​​switch​​表达式的源码版本至少要为12才能编译,且12和13版本需要启用​​--enable-preview​​参数。