JDK12 新特性

1.初识JDK12 新特性

自 2 月 7 日开始,Java/JDK 12 就进入了 RC 阶段。按照发布周期,美国当地时间 3 月 19 日,也就是北京时间 20 号 JDK12 正式发布了!

发行网站: http://openjdk.java.net/projects/jdk/12/

详情:

 

2.JDK12 更新列表

Features:总共有8个新的JEP(JDK Enhancement Proposals,JDK 增强提案)。
http://openjdk.java.net/projects/jdk/12/

 

3.安装JDK12

注意:一些老的IDEA版本是不支持JDK12的,我之前使用的2018.2.就不支持,目前使用的是2019.2.4.可以支持。

可以从Project Structure 查看当前IDEA 的版本支持的最新JDK版本,选择12或者更高的版本。

idea下载地址:https://www.jetbrains.com/idea/download/other.html

这个是否支持预览版的jdk12版本,跟idea的版本有关,我使用2019.2.才有这个选项,最新版的2021都没有这个选项,

只有12 - No new language features(不支持新特性),没有 12(preview) -switch expressions(支持预览版),

我猜测,有些idea的版本,不支持很久之前发行的jdk版本的预览版,只支持最接近发行的jdk版本的预览版,比如idea2019.2,

只支持jdk12,13的预览版,idea2021.2.,只支持jdk15,16的预览版。

 

4.JDK12 新特性详解

4.1.switch表达式

JEP 325:Switch Expressions(Preview) : switch表达式

Java的switch语句是一个变化较大的语法(可能是因为Java的switch语句一直不够强大、熟悉swift或者js语言的同学可与swift的switch语句对比一下,就会发现Java的switch相对较弱),

因为Java的很多版本都在不断地改进switch语句:

JDK 12扩展了switch语句,使其可以用作语句或者表达式,并且传统的和扩展的简化版switch都可以使用。JDK 12对于switch的增强主要在于简化书写形式,提升功能点。

下面简单回顾一下switch的进化阶段:

  • JDK1.5之前,switch循环只支持byte short char int四种数据类型
  • 从Java 5+开始,Java的switch语句可使用枚举了。以及byte short char int的包装类。
  • 从Java 7+开始,Java的switch语句支持使用String类型的变量和表达式了。
  • 从Java 11+开始,Java的switch语句会自动对省略break导致的贯穿提示警告(以前需要使用-X:fallthrough选项才能显示出来) 。
  • 但从JDK12开始,Java的switch语句有了很大程度的增强。

 

JDK 12以前的switch程序

public class Demo01{
    public static void main(String[] args){
        // 声明变量score,并为其赋值为'C'
        char score = 'C';
        // 执行switch分支语句
        switch (score) {
            case 'A':
                System.out.println("优秀");
                break;
            case 'B':
                System.out.println("良好");
                break;
            case 'C':
                System.out.println("中");
                break;
            case 'D':
                System.out.println("及格");
                break;
            case 'E':
                System.out.println("不及格");
                break;
            default:
                System.out.println("数据非法!");
        }
    }
}

这是经典的Java 11以前的switch写法 ,这里不能忘记写break,否则switch就会贯穿、导致程序出现错误(JDK 11会提示警告)。

 

JDK12 不需要break了

在JDK 12之前如果switch忘记写break将导致贯穿,在JDK 12中对switch的这一贯穿性做了改进。你只要将case后面的冒号改成箭头,那么你即使不写break也不会贯穿了,因此上面程序可改写如下形式:

public class Demo02{
   public static void main(String[] args){
        // 声明变量score,并为其赋值为'C'
        char score = 'C';
        // 执行switch分支语句
        switch (score){
            case 'A' -> System.out.println("优秀");
            case 'B' -> System.out.println("良好");
            case 'C' -> System.out.println("中");
            case 'D' -> System.out.println("及格");
            case 'E' -> System.out.println("不及格");
            default -> System.out.println("成绩数据非法!");
        }
    }
}

 

JDK12 的switch表达式

Java12 的switch甚至可作为表达式了——不再是单独的语句。例如如下程序:

public class Demo03{
   public static void main(String[] args) {
        // 声明变量score,并为其赋值为'C'
        char score = 'C';
        // 执行switch分支语句
        String s = switch (score)
        {
            case 'A' -> "优秀";
            case 'B' -> "良好";
            case 'C' -> "中";
            case 'D' -> "及格";
            case 'F' -> "不及格";
            default -> "成绩输入错误";
        };
        System.out.println(s);
    }
}

上面程序直接将switch表达式的值赋值给s变量,这样switch不再是一个语句,而是一个表达式。

 

JDK 12中switch的多值匹配

当你把switch中的case后的冒号改为箭头之后,此时switch就不会贯穿了,但在某些情况下,程序本来就希望贯穿。

比如我就希望两个case共用一个执行体!JDK 12的switch中的case也支持多值匹配,这样程序就变得更加简洁了。

public class Demo04{
   public static void main(String[] args) {
        // 声明变量score,并为其赋值为'C'
        char score = 'B';
        // 执行switch分支语句
        String s = switch (score) {
            case 'A', 'B' -> "上等";
            case 'C' -> "中等";
            case 'D', 'E' -> "下等";
            default -> "成绩数据输入非法!";
        };
        System.out.println(s);
    }
}

小结

从以上案例可以看出JDK 12对switch的功能做了很大的改进,代码也十分的简化,目前来看switch依然是不支持区间匹配的,未来是否可以支持,我们拭目以待

 

 

4.2.微基准测试套件

JEP 230: Microbenchmark Suite 微基准测试套件

 

JMH是什么

JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。

何谓Micro Benchmark(微基准测试)呢?简单的来说就是基于方法层面的基准测试,精度可以达到微秒级。

当你希望进一步优化方法执行性能的时候,就可以使用JMH对优化的结果进行量化的分析。

 

JMH比较典型的应用场景

  • 想定量地知道某个方法需要执行多长时间,以及执行时间和输入参数的相关性
  • 一个接口有两种不同实现(例如实现 A 使用了 FixedThreadPool,实现 B 使用了 ForkJoinPool),不知道哪种实现性能更好

我在写代码的时候经常有这种怀疑:写法A快还是写法B快,某个位置是用ArrayList还是LinkedList,HashMap还是TreeMap,HashMap的初始化size要不要指定,指定之后究竟比默认的DEFAULT_SIZE性能好多少。。。

 

JMH只适合细粒度的方法测试,并不适用于系统之间的链路测试!

JMH只适合细粒度的方法测试,并不适用于系统之间的链路测试!

JMH只适合细粒度的方法测试,并不适用于系统之间的链路测试!

 

JMH的使用案例

如果你使用 maven 来管理你的 Java 项目的话,引入 JMH 是一件很简单的事情——只需要在 pom.xml 里增加 JMH的依赖即可

    <properties>
        <jmh.version>1.14.1</jmh.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>${jmh.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>${jmh.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>12</release>
                    <compilerArgs>--enable-preview</compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

接下来再创建我们的第一个 Benchmark

package com.dayee;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
public class HelloWorld {

    static class Demo {
        int id;
        String name;
        public Demo(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    static List<Demo> demoList;
    static {
        demoList = new ArrayList();
        for (int i = 0; i < 10000; i ++) {
            demoList.add(new Demo(i, "test"));
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void testHashMapWithoutSize() {
        Map map = new HashMap();
        for (Demo demo : demoList) {
            map.put(demo.id, demo.name);
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void testHashMap() {
        Map map = new HashMap((int)(demoList.size() / 0.75f) + 1);
        for (Demo demo : demoList) {
            map.put(demo.id, demo.name);
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(HelloWorld.class.getSimpleName())
                .forks(1)
//                .warmupIterations(5)
//                .measurementIterations(5)
                .build();
        new Runner(opt).run();
    }
}

测试结果如下

 

上面的代码用中文翻译一下:分别定义两个基准测试的方法 testHashMapWithoutSize 和 testHashMap,这两个基准测试方法执行流程是:每个方法执行前都进行5次预热执行,每隔1秒进行一次预热操作,预热执行结束之后进行5次实际测量执行,每隔1秒进行一次实际执行,我们此次基准测试测量的是平均响应时长,单位是us(微秒)。

预热?为什么要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 benchmark 的结果更加接近真实情况就需要进行预热。

从上面的执行结果我们看出,针对一个Map的初始化参数的给定其实有很大影响,当我们给定了初始化参数执行执行的速度是没给定参数的2/3,这个优化速度还是比较明显的,所以以后大家在初始化Map的时候能给定参数最好都给定了,代码是处处优化的,积少成多。

通过上面的内容我们已经基本可以看出来JMH的写法雏形了,后面的介绍主要是一些注解的使用:

 

注解

@Warmup

@Warmup用来配置预热的内容,可用于类或者方法上,越靠近执行方法的地方越准确。一般配置warmup的参数有这些:

  • iterations:预热的次数。

  • time:每次预热的时间。

  • timeUnit:时间单位,默认是s。

  • batchSize:批处理大小,每次操作调用几次方法。(后面用到)

 

@Measurement

用来控制实际执行的内容,配置的选项本warmup一样。

 

@Benchmark
方法注解,是用来标记测试方法的,只有被这个注解标记的话,该方法才会参与基准测试,但是有一个基本的原则就是被@Benchmark标记的方法必须是public的。

 

@BenchmarkMode

@BenchmarkMode主要是表示测量的纬度,有以下这些纬度可供选择:

  • Mode.Throughput 吞吐量纬度

  • Mode.AverageTime 平均时间

  • Mode.SampleTime 抽样检测

  • Mode.SingleShotTime 检测一次调用

  • Mode.All 运用所有的检测模式 在方法级别指定@BenchmarkMode的时候可以一定指定多个纬度,例如:@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime}),代表同时在多个纬度对目标方法进行测量。

 

@OutputTimeUnit

@OutputTimeUnit代表测量的单位,比如秒级别,毫秒级别,微妙级别等等。一般都使用微妙和毫秒级别的稍微多一点。该注解可以用在方法级别和类级别,当用在类级别的时候会被更加精确的方法级别的注解覆盖,原则就是离目标更近的注解更容易生效。

 

@State

类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期,可以类比Spring Bean的Scope。

在很多时候我们需要维护一些状态内容,比如在多线程的时候我们会维护一个共享的状态,这个状态值可能会在每隔线程中都一样,也有可能是每个线程都有自己的状态,JMH为我们提供了状态的支持。该注解只能用来标注在类上,因为类作为一个属性的载体。@State的状态值主要有以下几种:

由于JMH允许多线程同时执行测试,不同的选项含义如下:

  • Scope.Thread:默认的State,每个测试线程分配一个实例;
  • Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
  • Scope.Group:每个线程组共享一个实例;

启动项

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(HelloWorld.class.getSimpleName())
                .forks(1)
                .warmupIterations(5)
               // .measurementIterations(5)
               // .build();
        new Runner(opt).run();
    }
  • include(SimpleBenchmark.class.getSimpleName())   代表我要测试的是哪个类的方法
  • exclude("xxx")  代表测试的时候需要排除xxxx方法
  • forks(2) 指的是做2轮测试,在一轮测试无法得出最满意的结果时,可以多测几轮以便得出更全面的测试结果,而每一轮都是先预热,再正式计量。
  • warmupIterations(5) 代表先预热5次
  • measurementIterations(5) 正式运行测试5次

方法 warmupIterations 与 measurementIterations ,同@Warmup ,@Measurement 功能相同

 

注意:只可以使用run模式,不可使用debug模式,否则报错。

 

JDK12中JMH说明

Java 12 中添加一套新的基本的微基准测试套件(microbenchmarks suite)

  • 此功能为JDK源代码添加了一套微基准测试套件,简化了现有微基准测试的运行和新基准测试的创建过程。
  • 使开发人员可以轻松运行现有的微基准测试并创建新的基准测试,其目标在于提供一个稳定且优化过的基准。它基于Java Microbenchmark Harness(JMH),可以轻松测试JDK性能,支持JMH更新

参考文档:Java 并发测试神器:基准测试神器-JMH


4.3. 默认生成类数据共享

JEP 341: Default CDS Archives 默认生成类数据共享

我们知道在同一个物理机上启动多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。

所以Java团队引入了类数据共享机制 (Class Data Sharing ,简称 CDS) 的概念,通过把一些核心类在每个JVM间共享,每个JVM只需要装载自己的应用类即可。

好处是:JVM启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了。

Java12新特性

  • JDK 12之前,想要利用CDS的用户,即使仅使用JDK中提供的默认类列表,也必须 java -Xshare:dump 作为额外的步骤来运行。
  • Java 12 针对 64 位平台下的 JDK 构建过程进行了增强改进,使其默认生成类数据共享(CDS)归档,以进一步达到改进应用程序的启动时间的目的。
  • 同时也取消了用户必须手动运行:java -Xshare:dump 才能使用CDS的功能。


4.4.Shenandoah GC低停顿时间的GC(预览)

JEP 189:Shenandoah:A Low-Pause-Time Garbage Collector(Experimental) 低暂停时间的GC

目标

添加一个名为Shenandoah的新垃圾收集(GC)算法,通过与正在运行的Java线程同时进行疏散工作来减少GC暂停时间,最终目标旨在针对 JVM 上的内存收回实现低停顿的需求。

使用Shenandoah的暂停时间与堆大小无关,这意味着无论堆是200MB还是200GB,您都将具有相同的一致暂停时间。

与 ZGC 类似,Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等

使用

-XX:+UnlockExperimentalVMOptions
在命令行中要求。作为实验性功能,Shenandoah构建系统会自动禁用不受支持的配置。

要启用/使用Shenandoah GC,需要以下JVM选项:
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

 

 

4.5.G1垃圾收集器功能增强

344:Abortable Mixed Collections for G1 可中止的G1 Mixed GC

如果G1垃圾收集器有可能超过预期的暂停时间,则可以使用终止选项。
该垃圾收集器设计的主要目标之一是满足用户设置的预期的 JVM 停顿时间,可以终止可选部分的回收已到达停顿时间的目标。

346:Promptly Return Unused Committed Memory from G1 G1及时返回未使用的已分配内存

如果应用程序活动非常低,G1应该在合理的时间段内释放未使用的Java堆内存。
G1可以使其能够在空闲时自动将 Java 堆内存返还给操作系统
 

 

4.6. JDK 12其他新特性


增加项:String新增方法

1. transform(Function):对字符串进行处理后返回。

var rs = "hello".transform(s -> s+"学习Java").transform(s -> s.toUpperCase());
System.out.println(rs);

2. indent :该方法允许我们调整String实例的缩进

System.out.println("=======================");
String result = "Java\nMySQL\nMyBatis".indent(3);
System.out.println(result);

 

新增项:Files新增mismatch方法

返回内容第一次不匹配的字符位置索引

Writer fw1 = new FileWriter("a.txt");
fw1.write("acc");
fw1.write("b");
fw1.write("c");
fw1.close();
Writer fw2 = new FileWriter("b.txt");
fw2.write("acc");
fw2.write("ddd");
fw2.write("ddd");
fw2.write("c");
fw2.close();
System.out.println(Files.mismatch(Path.of("a.txt"),Path.of("b.txt")));


核心库java.text支持压缩数字格式

NumberFormat添加了对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读形式表示的数字。例如,在en_US语言环境中,1000可以格式化为“1K”,1000000可以格式化为“1M”,具体取决于指定的样式
NumberFormat.Style。紧凑数字格式由LDML的Compact Number格式规范定义。要获取实例,请使用
NumberFormat紧凑数字格式所给出的工厂方法之一。

//例如:
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US,
NumberFormat.Style.SHORT);
String result = fmt.format(1000);
// 上面的例子导致“1K”。
var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA,
NumberFormat.Style.SHORT);
System.out.println(cnf.format(3_0000));
System.out.println(cnf.format(3_9200));
System.out.println(cnf.format(3_000_000));
System.out.println(cnf.format(3L << 30));
System.out.println(cnf.format(3L << 50));
System.out.println(cnf.format(3L << 60));

 

核心库java.lang中支持Unicode 11

JDK 12版本包括对Unicode 11.0.0的支持。在发布支持Unicode 10.0.0的JDK 11之后,Unicode 11.0.0引入了以下
JDK 12中包含的新功能:

1、684个新角色

  • 1.1、66个表情符号字符
  • 1.2、Copyleft符号
  • 1.3、评级系统的半星
  • 1.4、额外的占星符号
  • 1.5、象棋中国象棋符号

2、11个新区块

  • 2.1、格鲁吉亚扩展
  • 2.2、玛雅数字
  • 2.3、印度Siyaq数字
  • 2.4、国际象棋符号

3、7个新脚本

  • 3.1、Hanifi Rohingya
  • 3.2、Old Sogdian
  • 3.3、Sogdian
  • 3.4、Dogra
  • 3.5、Gunjala Gondi
  • 3.6、Makasar
  • 3.7、Medefaidrin

 

移除项

移除com.sun.awt.SecurityWarnin;
移除FileInputStream、FileOutputStream、- Java.util.ZipFile/Inflator/Deflator的finalize方法;
移除GTE CyberTrust Global Root;
移除javac的-source, -target对6及1.6的支持,同时移除--release选项;

 

废弃项

废弃的API列表见deprecated-list
废弃-XX:+/-MonitorInUseLists选项
废弃Default Keytool的-keyalg值
 

 

 

 

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值