【Java】面向对象基础 之 class版本

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

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

shell 复制代码
$ 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,错误信息类似:

shell 复制代码
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设置:

shell 复制代码
$ javac --release 11 Main.java

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

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

shell 复制代码
$ 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:

java 复制代码
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参数。

小结

高版本的JDK可编译输出低版本兼容的class文件,但需注意,低版本的JDK可能不存在高版本JDK添加的类和方法,导致运行时报错。

运行时使用哪个JDK版本,编译时就尽量使用同一版本编译源码。

相关推荐
2501_944521596 小时前
Ramda 跨端三方库仓颉适配全流程
开发语言·javascript·flutter·php
行思理6 小时前
go语言学习难度及适合学习的人群
开发语言·学习·golang
wangchunyu1148 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
paopaokaka_luck9 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
Thomas.Sir9 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
程序 代码狂人9 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
2501_9445215910 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
meiying883810 小时前
快递单号批量查询Python脚本:从手动到自动化的完整实现
开发语言·python·自动化
@大迁世界11 小时前
告别 Go,拥抱 Rust
开发语言·golang·rust
SEO_juper11 小时前
Node.js 重定向链检测工具:301 跳了几跳、有没有成环,一次看清
xml·开发语言·python·node.js·json·bash