背景
Java8的前世今生
作为一名程序员,笔者从大学开始接触Java这门编程语言,还记得当时的版本是1.4,不过这个版本的寿命还挺长,目前在一些金融行业的存量系统中依然还有1.4版本的影子。从C/C++到Java,Java给我的第一印象,就是这门语言的语法非常之啰嗦,比如内部类,像下面这段排序的代码,在C语言里面可以很方便地通过函数指针来解决,C++可以通过运算符重载来解决,唯独Java语言的写法最为冗长,这可能跟在Java的世界里一切皆是对象的这一设计理念相关。
Collections.sort(list, new Comparator() {
@Override public int compare(Fruit o1, Fruit o2) {
return o1.getWeight() >= o2.getWeight() ? 1 : -1; }
});
大学毕业参加工作后,开始用到JDK1.6,带来的最大感受是终于支持泛型了,操作集合的时候终于不用各种类型转换了,即便他的实现还是个假的泛型,但是带来的方便也是非常明显的。转眼到了2014年3月份,今天的第一个主角Java8正式GA,这是一个里程碑式的版本,带来了巨量的升级,其中包括对函数式编程的支持,像上述的冗长的排序代码,通过使用一行代码即可搞定;除此之外,还包括对Streams支持,方便了开发者写出更加简洁、高效的代码;然后,在Java8中还引入了全新的日期操作API,极大地方便了开发人员对日期和时间的操作,关键是他还是线程安全的,原来那套旧的日期和时间操作的API极度难用,而且容易出错。最后,还引入了Optional这一特性,极大地方便了对NPE问题的处理。这些新的特性的加入,为Java这门语言注入了新的生机,大量的开发人员从中受益,从这也可以看出Oracle官方对这门语言还是非常地重视,也投入了大量的资源持续进行优化升级,所以大家可以继续依赖这门语言。
list.sort(Comparator.comparing(Fruit::getWeight));
Oracle的商业支持
图1是2020年统计的Java各版本的使用情况,Java8的占比还是非常之大的,足以说明这个版本取得了非常大的成功,也不得不承认Java8是一个划时代的版本,从另一方面也说明了Java8之前的版本确实也太差了。竞争就是这样,不进步就被淘汰。从Java8之后,Java的更新也开始进入了一个快车道,每半年就会发布一次数字更新,但很多都是临时版本,长期维护的版本则由Java自行宣布,当前8,11,17属于是LTS(长期维护版本),表1中列出了各个版本的官方维护时间。
图 1 2020年编程语言使用排名
Release |
GA Date |
Premier Support Until |
Extended Support Until |
Sustaining Support |
7 (LTS) |
July 2011 |
July 2019 |
July 2022 |
Indefinite |
8 (LTS) |
March 2014 |
March 2022 |
December 2030 |
Indefinite |
9 (nonLTS) |
September 2017 |
March 2018 |
Not Available |
Indefinite |
10 (nonLTS) |
March 2018 |
September 2018 |
Not Available |
Indefinite |
11 (LTS) |
September 2018 |
September 2023 |
September 2026 |
Indefinite |
12 (nonLTS) |
March 2019 |
September 2019 |
Not Available |
Indefinite |
13 (nonLTS) |
September 2019 |
March 2020 |
Not Available |
Indefinite |
14 (nonLTS) |
March 2020 |
September 2020 |
Not Available |
Indefinite |
15 (nonLTS) |
September 2020 |
March 2021 |
Not Available |
Indefinite |
16 (non-LTS) |
March 2021 |
September 2021 |
Not Available |
Indefinite |
17 (LTS) |
September 2021 |
September 2026 |
September 2029 |
Indefinite |
18 (non-LTS) |
March 2022 |
September 2022 |
Not Available |
Indefinite |
19 (non-LTS) |
September 2022 |
March 2023 |
Not Available |
Indefinite |
20 (non-LTS) |
March 2023 |
September 2023 |
Not Available |
Indefinite |
21 (LTS) |
September 2023 |
**September 2028 ** |
September 2031 |
Indefinite |
表 1 Oracle对Java版本支持路线图
从表1中我们可以得出一些有用的信息,当然了,如果是付费企业可以忽略,付费企业是上帝。
- Java8将在2022年3月份停止维护,如果这之后遇到问题,有两种办法,第一,自己想办法解决,这对一些技术比较强的IT企业可能并不是什么难题,但是技术一般的企就不一定能搞得定了。第二,花钱买服务,找官方支持。相信国内99%以上的企业用的都是在免费使用Java。
- Java11也并不是一个安全和稳妥的升级目标版本,在2023年7月份,Oracle官方对Java11的支持也将结束,此时,替从Java8迁移到Java11的用户感到有些不值,下次升级一定要看准了再升,当然了,如果是新应用直接使用Java11,那么后面基本可以无感升级到后续版本。
- Java8将在2022年3月停止维护,那么下一次值得升级和使用的版本就是Java17了,这个版本在2021年11月正式GA,官方将公开支持到2026年11月份,而且也是一个LTS版本,更为关键的是,这个版本可以免费商用,此时此刻全场欢呼有没有。
持续充电
作为一名程序员,保持持续充电是非常有必要,在Java8即将停止得到官方免费升级支持的时候,相信会有越来越多的项目将基于Java17进行构建和迁移,包括于2022年1月20号,大名鼎鼎的SpringBoot3.0释放第一个里程碑版本M1,该项目就是基于Java17构建的,是的,你没看错,这个项目就是使用Java17进行构建,依赖的相关组件很快将全部会进行升级,届时如果想使用一些新的特性,在Java8下将无法使用,因为根本无法通过编译,相信这将会是一个痛苦的开始。因此,在此时此刻,突然有一种冲动,突然想在这一块给自己充充电,停留在Java8的时代确实也有些年头了,担心再不学习和充电,后面新的开源代码还有项目中的新的源代码将看不懂了,届时可能被这个时代给抛弃了。IT就是这样,知识的更迭速度非常非常快,持续充电非常必要。
收益
升级和使用新版本的Java带来的收益还是比较可观的,这里以升级Java17为例,首先可以借助新版本中新的语言特性和新的Java标准SDK,编写出更易于阅读和维护的代码,原来需要10行代码才能实现的功能,现在或许只需要一行代码就可以搞定了。在性能上,Java17还带来了不小的提升,具体可以参考相关测试链接,也就是说仅仅通过升级Java版本,就可以带来一次性能上的提升,想想还是挺划算的。新版本中还增加了一些新的库函数,比如更加好用的Http客户端API,终于可以摆脱Apache的HttpClient这个开源组件了。还有Record记录类这一新特性的加入,给经常写PO、DO、VO的开发人员也带来了福音。其他特性这里不再赘述,后面主要篇幅将详细的介绍从Java8到Java17这中间的一些变化和新的特性,通过学习和了解这些内容,能很快地从Java8转到Java17,当然了,对于有些新的特性,还是亲自动手去玩一遍,可能印象会更加深刻,效果也会更加好一些。
特性介绍
从Java8到Java17这中间增加的特性非常多,有些特性增加完后很快又被废弃,有些经过好几个版本的更迭后才被正式启用。图2是笔者总结归纳的从Java8到Java17增加的一些重要的新特性,有些特性可能跟我们关系不大,或者根本接触不到,或者一些几乎没用到过的一些的删除,这些点没有在这里列举。这里仅仅列举了对我们开发和使用关系非常密切的,能方便开发者且有明显感知得到的特性,如果对其他特性感兴趣,请访问Java官方链接。下面按照图2中从上到下的顺序详细进行介绍。
图 2 Java8到Java17重要新特性汇总
语法新特性
文本块
这个更新非常有用的,在没有这个特性之前,编写一段长文本是非常痛苦的,虽然有像IDEA这样的集成编辑工具自动处理,但是最后呈现出来的效果也是很丑陋的,里面一堆拼接符号。现在在这个字符串块内随便写,包括但不限于JSON、HTML和SQL等等看起来也非常清爽,给这个新特性五颗星,以后只需要关注字符串本身了,而不需要开发者关心拼接操作了。
/**
* 文本块对比
*
* @author fanyong
* @date 2022/2/20
* @since 1.0.0
*/
public class TextBlock {
/**
* 使用JDK8返回HTML文本
* @return 返回HTML文本
*/
public static final String getHtmlTextInJDK8() {
return "<html>\n" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
"</html>";
}
/**
* 使用JDK17返回HTML文本
* @return 返回HTML文本
*/
public static final String getHtmlTextInJDK17() {
return """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
}
}
NullPointerException增强
这一功能非常的强大而又实用,相信每一位Javaer等这个功能实在等得太久了。每一位Java程序员一定都查过NPE问题,也就是大名鼎鼎的空指针问题,而且对这类问题一定也非常痛恨,为什么呢?因为报的错误信息里面没有反馈是哪个对象的问题,就只会摆抛出来一个NullPointerException和一堆用处不大的堆栈信息,然后勤劳的程序员们就开始去定位问题去了,一个小时,两个小时过去了,问题可能还未定位出来。尤其是在遇到喜欢炫技开发者们,在一行代码上级联调用,鬼知道哪个对象为空导致报错了,只能从前往后推。如果是在测试环境,很多同学可能会想到使用远程debug来查明是哪个对象为空了。但为了查明是哪个对象是为空对象都需要这样大费周章,这到底是哪个地方的问题?为了规避出现这样的问题,阿里的编码规范中甚至出现了不允许级联调用,但这也只是治标不治本,在JDK17中,这个问题总算得到彻底解决了。如下代码所示,两个测试用例都会抛出NPE异常。
/**
* NPE增强
*
* @author fanyong
* @date 2022/2/20
* @since 1.0.0
*/
public class NullPointerExceptionEnhancer {
public static void main(String[] args) {
test1();
test2();
}
private static final void test1() {
try {
var a = new ArrayList<Integer>();
a.add(null);
System.out.println(a.get(0).longValue());
} catch (Exception e) {
e.printStackTrace();
}
}
private static final void test2() {
Object obj = null;
obj.equals(null);
}
}
运行后得到的报错如下,报错信息非常清晰地指出来了是哪个对象为空导致出现的NPE问题,无论级联调用得有多深,都能很清晰地指示出来,非常好用,非常强大,给这个功能五颗星,程序员以后终于可以早些下班了。
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.longValue()" because the return value of "java.util.ArrayList.get(int)" is null
at NullPointerExceptionEnhancer.test1(NullPointerExceptionEnhancer.java:20)
at NullPointerExceptionEnhancer.main(NullPointerExceptionEnhancer.java:12)
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Object.equals(Object)" because "obj" is null
at NullPointerExceptionEnhancer.test2(NullPointerExceptionEnhancer.java:28)
at NullPointerExceptionEnhancer.main(NullPointerExceptionEnhancer.java:13)
Records
在Java中,POJO是被大家所熟知的,包括DO、PO、VO、DTO等,都是普通的POJO对象,一个POJO对象包括一系列的类成员变量和相应的Getter和Setter函数,清一色的模板代码,虽然可以通过工具或者IDE直接生成,但是修改维护起来还是非常麻烦的,为了解决这个问题甚至还出现了像lombok这样的插件,在编译期间自动生成相应的Getter和Setter,甚至像hashcode、equals、构造函数、builder构造器模式模板代码都给自动生成了,使用起来也算是相当方便。但是这毕竟也只是个插件,而且一旦使用这个插件,那么项目团队里面所有人都得使用,IDE上面还都得安装相应的插件,有一种强迫的意思,实属不太友好。就在这个时候,Java来了一个标准解决方案,即Records这一语言特性,示例代码如下所示。record这一特性的引入,非常干净利落地解决了上述部分问题。但是如果要实现相应的hashcode和equals方法,那么还是得手工去写了,但好在有IDE自动生成的功能。
/**
* record
*
* @author fanyong
* @date 2022/2/20
* @since 1.0.0
*/
public record EmployeeRecord(Long id,
String firstName,
String lastName,
String email,
int age) {
public EmployeeRecord {
System.out.println("调用构造函数");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeRecord that = (EmployeeRecord) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public static void main(String[] args) {
EmployeeRecord er = new EmployeeRecord(1L, "Lokesh", "Gupta",
"howtodoinjava@gmail.com", 38);
System.out.println(er);
}
}
全新的switch表达式和模式匹配
有人可能问了,Java语言不早已支持switch了嘛,有什么好提的?讲真,这次的提升还有必要好好地来聊一聊了。在JDK12的时候就引入了switch表达式,注意这里是表达式,而不是语句,原来的switch是语句。如果不清楚两者的区别的话,最好先去了解一下。主要的差别就是就是表达式有返回值,而语句则没有。再配合模式匹配,以及yield和“->”符号的加入,全新的switch用起来爽到飞起来,具体请看对比示例。笔者在玩了几把新式的写法后,最大的感受就是简洁明了,而且还不易出错,如果习惯了新式的写法,那么再也不用担心少写break语句而导致出现bug了,相信绝大多数Java程序员都犯过漏写break语句而导致出现一些稀奇古怪的问题吧,新技能get起来。此外,模式匹配的引入,也确实给程序员带来了很大的便利。最后,case后面居然支持null值了,小小的惊喜。
/**
* switch表达式
*
* @author fanyong
* @date 2022/2/22
* @since 1.0.0
*/
public class SwitchExpressiong {
private static enum Week {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
/**
* 在JDK8中获取switch返回值方式
* @param week
* @return
*/
public int getCodeInJDK8(Week week) {
int i = 0;
switch (week) {
case MONDAY,TUESDAY:
i = 1;
break;
case WEDNESDAY:
i = 3;
break;
case THURSDAY:
i = 4;
break;
case FRIDAY:
i = 5;
break;
case SATURDAY:
i = 6;
break;
case SUNDAY:
i = 7;
break;
default:
i = 0;
break;
}
return i;
}
/**
* 在JDK17中获取switch返回值
* @param week
* @return
*/
public int getCodeInJDK17(Week week) {
// 1, 现在的switch变成了表达式,可以返回值了,而且支持yield和->符号来返回值
// 2, 再也不用担心漏写了break,而导致出问题了
// 3, case后面支持写多个条件
return switch (week) {
case null -> -1;
case MONDAY, TUESDAY -> 1;
case WEDNESDAY -> 3;
case THURSDAY -> { yield 4; }
case FRIDAY -> 5;
case SATURDAY -> 6;
case SUNDAY -> 7;
default -> 0;
};
}
/**
* 测试模式匹配
* @param value 任意类型的值
*/
public void testPatternMatcher(Object value) {
// 转换并申请了一个新的变量,极大地方便了代码的编写
if (value instanceof String v) {
System.out.println("遇到一个String类型" + v.toUpperCase());
} else if (value instanceof Integer v) {
System.out.println("遇到一个整型类型" + v.longValue());
}
// 旧式写法
if (value instanceof String) {
String v = (String) value;
System.out.println("遇到一个String类型" + v.toUpperCase());
} else if (value instanceof Integer) {
Integer v = (Integer) value;
System.out.println("遇到一个整型类型" + v.longValue());
}
// swtich表达式也支持模式匹配,现在的代码看起来是不是非常地清爽干净了
switch (value) {
case String v -> System.out.println("遇到一个String类型" + v.toUpperCase());
case Integer v -> System.out.println("遇到一个整型类型" + v.longValue());
default -> System.out.println("未知类型:" + value);
}
}
}
私有接口方法
从Java8开始,允许在interface里面添加默认方法,其实当时就有些小困惑,如果一个default方法体很大怎么办,拆到另外的类去写吗?实在有些不太合理,所以在Java17里面,如果一个default方法体很大,那么可以通过新增接口私有方法来进行一个合理的拆分了,为这个小改进点个赞。
/**
* 接口私有方法
*
* @author fanyong
* @date 2022/2/22
* @since 1.0.0
*/
public interface PrivateInterfaceMethod {
/**
* 接口默认方法
*/
default void defaultMethod() {
privateMethod();
}
// 接口私有方法,在Java8里面是不被允许的,不信你试试
private void privateMethod() {
}
}
局部变量类型推断
在Java8的时候,其实就已经支持了类型推断了,在对Stream进行操作的时候,比如下面这段代码,在执行filter的时候里面的lambda表达式中的参数都没有声明类型,这里其实就用到了类型推断。
int maxWeight = blocks.stream().filter(b -> b.getColor() == BLUE).mapToInt(Block::getWeight) .max();
在Java10的时候又引入了另外一个特性,叫局部变量类型推断这个特性。正如这个特性的名称,这个特性只能用于局部变量,且类型是确定的,无二义性的,下面的示例代码中给出了哪些地方不能用局部变量类型推断,也就是var关键词在哪些场景下不允许使用。
/**
* 接口私有方法
*
* @author fanyong
* @date 2022/2/23
* @since 1.0.0
*/
public class VarIncorrectUsage {
// var user = “fanyong”; // 类成员变量不允许使用
//public VarIncorrectUsage(var param) { // 构造函数的参数定义不允许使用
//}
/*try {
} catch(var exception) { // catch里面不允许使用
} */
/*public var returnMethod() { // 返回类型不允许使用
return null;
}*/
/*public Integer parameterMethod( var input ) { //函数或者方法参数不允许使用
return null;
}*/
}
密封类
通过sealed修饰符来描述某个类为密封类,同时使用permits关键字来制定可以继承或实现该类的类型有哪些。注意sealed可以修饰的是类(class)或者接口(interface),所以permits关键字的位置应该在extends或者implements之后。这个特性跟final是有区别的,final修饰的类表示该类不允许被继承,不允许修饰interface和抽象类,此外,被密封的类必须指定能被继承的子类,且子类也必须被标为sealed或者non-sealed或者final。这个特性可以防止类被误继承,把继承的范围固定在某个固定可控的范围类。
以下为描述一个接口为密封类的写法实例:
interface AnimalService {
void doSomething();
}
sealed interface DogService extends AnimalService permits DogServiceImpl {
}
non-sealed class DogServiceImpl implements DogService {
@Override
public void doSomething() {
// do something
}
}
JavaSDK更新
全新的HttpClient
这个API首次出现在9之中,不过当时并非是一个稳定版本,在Java11中正式得到发布,所以在Java17里面可以放心地进行使用。原来的JDK自带的Http客户端真的非常难用,这也就给了很多像okhttp、restTemplate、Apache的HttpClient和feign这样的第三方库极大的发挥空间,几乎就没有人愿意去用原生的http客户端的。但现在不一样了,感觉像是新时代的API了。FluentAPI风格,处处充满了现代风格,用起来也非常地方便,再也不用去依赖第三方的包了,就两个字,清爽。
// 同步请求
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
.authenticator(Authenticator.getDefault())
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
// 异步请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://foo.com/"))
.timeout(Duration.ofMinutes(2))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
集合类的工厂方法
在Java8的年代,即便创建一个很小的集合,或者固定元素的集合,那么也必须经历如下这几步。新建一个集合类,然后调用相应的函数增添元素,而且该集合还是一个可变的集合。此外,像List、Set和Map接口都增加了一个copyOf的default方法,用于做集合元素的深拷贝,这个方法也非常的实用。
);
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c"
这,其实很不像一个现代化语言。所以现在可以用简单的写法:
Set<String> set = Set.of("a", "b", "c");
包括List,Set,Map都可以使用of来直接创建集合。
Flow API
这个东西的前身叫rxjava,或者叫reactive java,后来又出现了一个叫project reactor。事件驱动已经不能简单的用来概括响应式编程了,为什么要增加这样的API呢,因为那段时间各个大厂纷纷发力异步编程,弄出很多种所谓的响应式框架,各自都宣称自己的性能好。后来被某个开源联盟联合起来一起出了一个响应式的规范,主要包括下面四大块。
- Subscriber:订阅者
- Publisher:生产者
- Subscription:订阅关系
- Processor:订阅者和生产者之间的N个处理步骤
响应式编程并不能提升多少性能,而是使程序更加稳定和获得更好的扩展性。
Stream API的增强
增加takeWhile, dropWhile, ofNullable, iterate以及toList的API,越来越像一些函数式语言了。用法举例如下。
// takeWhile 顺序返回符合条件的值,直到条件不符合时即终止继续判断,
// 此外toList方法的加入,也大大减少了节省了代码量,免去了调用collect(Collectors::toList)方法了
List<Integer> list = Stream.of(2,2,3,4,5,6,7,8,9,10)
.takeWhile(i->(i%2==0)).toList(); // 返回2, 2
// dropWhile 顺序去掉符合条件的值,直到条件不符合时即终止继续判断
List<Integer> list1 = Stream.of(2,2,3,4,5,6,7,8,9,10)
.dropWhile(i->(i%2==0)).toList(); //返回3, 4, 5, 6, 7, 8, 9, 10
// ofNullable,支持传入空流,若没有这个且传入一个空流,那么将会抛NPE
var nullStreamCount = Stream.ofNullable(null).count(); //返回0
// 以下两行都将输出0到9
Stream.iterate(0, n -> n < 10, n -> n + 1).forEach(x -> System.out.println(x));
Stream.iterate(0, n -> n + 1).limit(10).forEach(x -> System.out.println(x));
新的String方法
- repeat:重复生成字符串
- isBlank:不用在引入第三方库就可以实现字符串判空了
String aa = " ";
System.out.println(aa.isEmpty()); //false
System.out.println(aa.isBlank()); //true
String bb = "";
System.out.println(bb.isEmpty()); //true
System.out.println(bb.isBlank()); //true
- strip:去除字符串两边的空格,支持全角和半角,之前的trim只支持半角
- lines:能根据一段字符串中的终止符提取出行为单位的流
- indent:给字符串做缩进,接受一个int型的输入
- transform:接受一个转换函数,实现字符串的转换
InputStream和Reader之transferTo方法
在没有transferTo方法前,若要将一个I/O输入转成一个I/O输出,那么会怎么做呢?首先,从I/O输入中读一段数据到某个buffer中,然后,将这个buffer中的数据写到I/O输出中,循环此动作,直到数据完全读完并写入到I/O输出,这中间还有考虑很多边界的问题,完成这个动作还是比较繁琐的。在有了transferTo之后,可以通过一个简单的方法调用,就可以把这一堆琐事全部搞定了,如下代码所示,是不是非常方便了。
public static void testReaderTransferTo() throws IOException {
StringReader sr = new StringReader("Hello");
StringWriter sw = new StringWriter();
sr.transferTo(sw);
sw.append(" world!");
System.out.println(sw.getBuffer()); //打印Hello world!
}
public static void testInputStreamTransferTo() throws IOException {
InputStream ist = new ByteArrayInputStream("hello world".getBytes(StandardCharsets.UTF_8));
ist.transferTo(System.out); //打印Hello world!
}
新工具
jshell
在新的JDK版本中,支持直接在命令行下执行java程序,类似于python的交互式REPL。简而言之,使用 JShell,你可以输入代码片段并马上看到运行结果,然后就可以根据需要作出调整,这样在验证一些简单的代码的时候,就可以通过jshell得到快速地验证,非常方便。
java命令直接执行java文件
在现在可以直接通过执行“java xxx.java”,即可运行该java文件,无须先执行javac,然后再执行java,是不是又简单了一步。
其他
模块化
大家都知道Java的package,需要的时候引入(import)一下对应的类,module在package的上面一层,一个模块都会有一个module-info.java的描述文件,来描述当前模块需要引入哪些package和对哪些package可见。
module com.foo.bar {
requires org.baz.qux;
exports com.foo.bar.alpha;
exports com.foo.bar.beta;
}
这么做的好处,首先对于jdk本身来说我不需要的package我就不要了引入了,不像现在会默认塞一个rt.jar进来,这个东西有60M多,但大部分的内容都没用到。然后没有在module-info.java中声明的exports,即使是public的类在外部也无法被使用,这就进一步增强了安全性。
jdk身先士卒首先将rt.java拆成了很多个小的jmod文件,但好像也仅限于此,热门框架跟进的不积极。基于现有的Spring boot+maven的管理模式,大到依赖管理小到bean的管理都井井有条。如果各大框架进一步拆解自己的变成一个个模块,那可能确实打出来的jar会小一些,但可惜多数是没有响应的。
模块和maven不是一个东西
这个依赖管理让很多人一眼就想到了maven,但两者解决的不是一个问题。maven只能管到jar包的依赖,而module会深入到类中去做更精细的依赖管理和权限控制。而maven本身还有更重要的活去干:编译、测试、打包、发布等。
鉴于确实没多少人用,我也很难下结论说他好还是不好,只是现有的项目想迁移成本是极高的,新的项目不用module也可以实现大部分的功能,只是jar会比较大一些,不过在容器本身的体积面前确实也不算什么。
ZGC
在ParallelOldGC、CMS、G1之后,JDK11带来的全新的ZGC,全称为The Z Garbage Collector,听这名字取得就挺牛逼的。官方声称垃圾回收的停顿时间不超过10ms且最大支持16TB的堆,并且停顿时间不会随着堆的增大而增加。那到底为我们解决了什么问题呢?Oracle官方介绍它是一个Scalable、Low Latency的垃圾回收器。所以它的目的是降低停顿时间,由此会导致吞吐量有所降低。但是吞吐量降低问题不大,横向扩展几台服务器就能解决问题。对于ZGC,目前官方已经建议可以用于实际生产应用中,这一定是未来一个通用的垃圾回收器,要想了解更多,请参考官方链接。
改进的JavaDoc
新版本Java中,生成成的Java文档已经支持HTML5,且支持全文搜索功能。
总结
Java8的生命的尾声或许即将来临,或许在存量的系统中继续发光发亮,但无论是哪一种,Java17的使用量将会逐年攀升,越来越多的开源项目也将会升级到Java17,SpringBoot3.0只是一个开始,你准备好了吗?