从Java8到Java17

背景

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中列出了各个版本的官方维护时间。

 从Java8到Java17

图 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中我们可以得出一些有用的信息,当然了,如果是付费企业可以忽略,付费企业是上帝。

  1. Java8将在2022年3月份停止维护,如果这之后遇到问题,有两种办法,第一,自己想办法解决,这对一些技术比较强的IT企业可能并不是什么难题,但是技术一般的企就不一定能搞得定了。第二,花钱买服务,找官方支持。相信国内99%以上的企业用的都是在免费使用Java。
  2. Java11也并不是一个安全和稳妥的升级目标版本,在2023年7月份,Oracle官方对Java11的支持也将结束,此时,替从Java8迁移到Java11的用户感到有些不值,下次升级一定要看准了再升,当然了,如果是新应用直接使用Java11,那么后面基本可以无感升级到后续版本。
  3. 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中从上到下的顺序详细进行介绍。

 

 从Java8到Java17

图 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只是一个开始,你准备好了吗?

 

 

上一篇:推荐系统之冷启动问题


下一篇:Centos安装和卸载Jenkins