Java9+

Java9-11新特性

Mount Bromo
Mount Bromo

经历四次跳票的 Java9 于 2017 年 9 月 21 日正式发布,Oracle 宣布今后按照每六个月一次的节奏进行更新。意味着从以特性驱动的发布周期转向以时间驱动的模式。并以三年为周期发布长期支持版本 Long Term Support(LTS)。

安装与切换

推荐安装 LTS 版本,即 Java8、11。点此快速进入 oracle Java 页面。

  • Mac 下查看已安装的 jdk 版本及其安装目录
# 输入命令参数区分大小写
/usr/libexec/java_home -V
# 结果默认排序为当前已安装jdk目录、Mac默认使用的jdk版本
  • IDEA 开发环境设置

在 IntelliJ 中 File 下选择 Project Structure 查看项目的 SDK。新下载版本需要在 new 选项里进行导入。完成后在左侧 Project Settings 中点击 Modules 项可以指定 Sources 与 Dependencies 需要识别 SDK 的版本。前者是核心,作用当前的 module 可以识别的语法;后者仅决定当前的 module 使用何种 JDK。

Java9 新特性

模块化系统 Jigsaw|Modularity

Java9 首先想到的就是 Jigsaw|Modularity 项目。众所周知 Java 从 95 至今已经发展超过 20 年,在相关生态在不断丰富的同时也越来越暴露出一些问题。

每次 JVM 启动的时候,至少会有 30~60MB 的内存加载,主要原因是 JVM 加载 rt.jar。不论其中的类是否被 classloader 加载,整个 jar 都会被 JVM 加载到内存当中去(而模块化可以根据模块的需要加载程序运行需要的 class)。当代码库逐步复杂,盘根错节的不同版本类库交叉依赖导致产生许多令人头疼的问题,这些阻碍了 Java 开发和运行效率的提升。系统没有对不同 JAR 文件之间的依赖关系添加明确概念,这导致每一个公共类都可以被类路径之下任何其它的公共类所访问,可能会导致无意中使用了并不想被公开访问的 API。

要设置模块,首先需要在包的根目录 src 文件夹下放置一个名为 module-info.java 的特殊文件,该文件称为模块描述符,包含构建和使用新模块所需的所有数据。用一个声明来构造模块,其主体要么是空的,要么由模块指令组成。

# 一个工程下不同的 module 之间实现类的导入
# module-info.java
module exportsContentModuleName{
    # 对外暴露内容在此体现
    exports com.xxx.bean; // 暴露相关包bean下内容
}
---
module requiresContentModuleName{
    requires exportsContentModuleName; # 直接导入暴露的模块名
}

jshell

诸如 python 与 scala 之流的语言早有交互式编程环境 REPL Read Evaluate Print Loop 运行语句和表达式,以在编译前获得对程序的反馈。jdk9 中新增 jshell 工具,同样可以帮助运行一些简单如 HelloWorld 的代码。

# 进入 jshell
$ jshell
|  Welcome to JShell -- Version 17.0.2
|  For an introduction type: /help intro
jshell> System.out.println("hello jeshell")
# 获取帮助|查看导入|退出
jshell> /help| /import| /exit

接口中声明私有方法

Java8 规定接口中除抽象方法外,还可定义静态与默认方法。由于静态和默认方法里可能存在的方法体会扩展接口的功能,此时的接口更像是一个抽象类。

Java9 中更甚,连方法的访问权限修饰符都可以声明为 private,那么实现类的接口里,私有方法将不会成为对外暴露 API 的一部分。其实现的意义在于,对于接口中的部分方法体可能存在重复的逻辑,那么可在私有方法中完成定义。

public interface MyInterface {
    // 下述前三方法省略权限修饰符都是 public
    void methodAbstract();
    static void methodStatic(){
        System.out.println("static method");
    }
    default void methodDefault(){
        System.out.println("default method");
        // 接口中自行调用私有方法
        methodPrivate();
    }
    private void methodPrivate(){
        System.out.println("private method");
    }
}
public class MyInterfaceImpl implements MyInterface{

    @Override
    public void methodAbstract() {

    }
    public static void main(String[] args){
        // 接口中的静态方法只能由接口调用
        MyInterface.methodStatic();
        // 接口中的实现类不能调用接口的静态方法
        // MyInterfaceImpl.methodStatic();
        MyInterfaceImpl impl = new MyInterfaceImpl();
        impl.methodDefault(); // 不重写情况下相当于间接的调用了接口的私有方法
        // 私有方法不能在接口外部调用
        // impl.methodPrivate();
    }
}

钻石操作符升级

在 Java9 中能与匿名实现类共同使用钻石操作符,若在 Java8 中则会报错。

public class Java9Test {
    @Test
    public void test2(){
        // JDK8不可识别;钻石操作符与匿名内部类在Java8不可共存
        Comparator<Object> com = new Comparator<>(){
            @Override
            public int compare(Object o1, Object o2) {
                return 0;
            }
        };
        // JDK7允许的类型推断
        ArrayList<String> list = new ArrayList<>();
    }
}

try语句

Java8 可以实现资源的自动关闭,要求执行后必须关闭所有的资源必须在 try 子句初始化,否则编译不通过。

public class Java9Test {
    // java8之前资源关闭操作
    @Test
    public void test3(){
        InputStreamReader reader = null;
        try{
            reader = new InputStreamReader(System.in);
            char[] cbuf = new char[20];
            int len;
            if((len = reader.read(cbuf)) != -1){
                String str = new String(cbuf,0,len);
                System.out.println(str);
            }
        } catch (IOExcerption e) {
            e.printStackTrace();
        } finally {
            if(reader != null){
                try{
                    reader.close();
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
    // java8资源关闭操作 => 自动关闭reader
    @Test
    public void test4(){
        try(InputStreamReader reader = new InputStreamReader(System.in)){
            char[] cbuf = new char[20];
            int len;
            if((len = reader.read(cbuf)) != -1){
                String str = new String(cbuf,0,len);
                System.out.println(str);
            }
        } catch (IOExcerption e){
            e.printStackTrace();
        }
    }
    // java9资源关闭操作需要自动关闭的资源的实例化放在try的一堆小括号外
    // 此时资源变量是常量
    @Test
    public void test5(){
        InputStreamReader reader = new InputStreamReader(System.in)
        try(reader){
            char[] cbuf = new char[20];
            int len;
            if((len = reader.read(cbuf)) != -1){
                String str = new String(cbuf,0,len);
                System.out.println(str);
            }
        } catch (IOExcerption e){
            e.printStackTrace();
        }
    }
}

String 底层存储结构的变更

The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

总结一下即是 string 不再用 char[] 来存储,而是改成 byte[] 加上编码标记来节约空间。StringBuffer 和 StringBuilder 也同样

// adopt-open-jdk-11 => java.base => java => lang
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
    ...
    }

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    // The value is used for character storage.
    byte[] value;
    // The id of the encoding used to encode the bytes in {@code value}.
    byte coder;
    ...
}

集合工厂方法

此前创建一个只读不可改变的集合,必须完成构造分配再添加元素,最后包装成一个不可修改的集合。

public class Java9Test {
    @Test
    public void test3(){
        // JDK8创建只读集合
        List<String> nameList = new ArrayList<>();
        nameList.add("zs");
        nameList = Collections.unmodifiableList(nameList);
        System.out.println(nameList);
        List<Integer> list = Arrays.asList(1, 2, 3);
        // java.lang.UnsupportedOperationException
        // list.add(4);
        System.out.println(list);
        // java9集合工厂创建只读集合
        List<Integer> llist = List.of(1, 2, 3);
        // UnsupportedOperationException
        // llist.add(4);
        System.out.println(llist);
        Map<String, Integer> map = Map.of("gz", 20, "hz", 22, "zs", 21);
        // UnsupportedOperationException
        // map.put("test",1);
        System.out.println(map);
        Map<String, Integer> kvMap = Map.ofEntries(Map.entry("gz", 20), Map.entry("hz", 22));
        // UnsupportedOperationException
        // kvMap.put("test",1);
        System.out.println(kvMap);
    }
}

InputStream 加强

InputStream 推出方法 transferTo 将数据直接传输到 OutputStream 处理原始数据流。

public class Java9Test {
    // java9新特性 InputStream 的 transferTo()
    @Test
    public void test4(){
        ClassLoader cl = this.getClass().getClassLoader();
        try(InputStream is = cl.getResourceAsStream("hello.txt");OutputStream os = new FileOutputStream("src/hi.txt")){
            is.transferTo(os); // 输入流数据直接获取到输出流
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

StreamAPI 的增强

Java9 中 Stream API 添加4个新的方法 takeWhile、dropWhile、ofNullable 与 iterate 新重载方法。对于容器 Optional 也提供转化为流的方法,便于对数据进行处理操作。

public class Java9Test {
    // 增强STREAM API
    @Test
    public void test5(){
        List<Integer> list = Arrays.asList(1, 2, 3);
        // takeWhile 返回从开头开始尽可能多的元素
        list.stream().takeWhile(x -> x < 2).forEach(System.out::println);
        // dropWhile 返回没有drop掉的元素
        list.stream().dropWhile(x -> x < 2).forEach(System.out::println);
        Stream.of(1, 2, 3, null).forEach(System.out::println);
        // 不被允许 NullPointerException => java8的stream不能完全为null
        // Stream<Integer> integerStream1 = Stream.of(null);
        // integerStream1.forEach(System.out::println);
        Stream.ofNullable(null).forEach(System.out::println); // 只放一个null认为什么也没有
        // java8的无限流
        Stream.iterate(0,x -> x+1).limit(10).forEach(System.out::println);
        // java9重载方法
        Stream.iterate(0,x -> x<100,x -> x+1).forEach(System.out::println);
    }
}
public class Java9Test {
    @Test
    public void test6(){
        List<String> list = new ArrayList<>();
        list.add("gz");
        list.add("zs");
        Optional<List<String>> optional = Optional.ofNullable(list);
        Stream<List<String>> stream = optional.stream();
        stream.flatMap(x->x.stream()).forEach(System.out::println);
    }
}

JS 引擎升级

Nashorn 使得 Java 应用能嵌入 Javascript,但在 JDK9 新增包含一个用于解析 Nashorn 的 ECMAScript 语法树 API,使 IDE 和服务端不需依赖 Nashorn 项目内部实现类就能分析 ECMAScript 代码。

Java10 新特性

需要注意的是 Java 9 和 Java 10 都不是 LTS Long-Term-Support 版本,和过去的 Java 大版本升级不同,这两个只有半年左右的开发和维护期。而未来的 Java 11,也就是 18.9 才是 Java8 后的第一个 LTS 版本。

JDK10 一共定义了109个新特性,其中包含12个 JEP,对于开发者来说,真正的特性只有一个,此外还有一些 API 和 JVM 规范的改动。

Local-Variable type Inference 局部变量类型推断

局部变量的显示类型声明,常常被认为是不必须的。为减少形式冗余,JDK 10 新增局部变量类型推断。

在处理 var 时,编译器先是查看表达式右边部分,并根据右边变量值的类型进行推断,作为左边变量的类型,然后将该类型写入字节码当中。

var 不是一个关键字而是一个类型名,故不需要担心变量名或方法名会与 var 发生冲突,只有在编译器需要知道类型的地方才需要用到。此外,作为一个普通合法的标识符,除了不能用作为类名,其他的都可以。值得注意的,var 并不会改变 Java 是一门静态类型语言的事实。

public class Java10Test {
    // 局部变量的类型推断 => 右边判断左边
    @Test
    public void test1(){
        // 声明变量时根据所赋值推断变量的类型
        var num = 10;
        // 没有指明泛型则推断为 Object
        var list = new ArrayList<>();
        list.add(123);
        for(var i:list){
            System.out.println(i);
            System.out.println(i.getClass()); // class java.lang.Integer
        }
    }
    @Test
    public void test2(){
        // 不能用测试
        // 1.未初始化的局部变量声明不行
        // var num;
        // 2.Lambda表达式中左边的函数式接口不能声明为var => 不能确定是否为函数接口
        // var sup = () -> Math.random();
        Supplier<Double> sup = () -> Math.random();
        // 3.方法引用左边的函数式接口不能声明为var
        Consumer<String> con = System.out::println;
        // var con = System.out::println;
        // 4.数组的静态初始化注意如下情况不可
        int[] arr = {1,2,3,4};
        // var arr = {1,2,3,4};
    }
    // 4.方法的返回值类型不可推断
    // 5.方法/构造器的参数类型不可推断
    // public var test3(var num){}
}

新增创建不可变集合方法 copyOf

JDK 为集合添加了 of 和 copyOf 方法,用于创建不可变集合。后者是 JDK 10 新增,若传入集合已是不可变,那么直接返回该集合,否则调用 of 创建不可变集合。

public class Java10Test {
    @Test
    public void test3(){
        var list1 = List.of("gz","hz");
        var copy1 = List.copyOf(list1);
        // 本身就是可读集合,那么copyOf返回同一个
        System.out.println(list1 == copy1); // true
        var list2 = new ArrayList<String>();
        var copy2 = List.copyOf(list2);
        // 本身不是可读集合,那么copyOf返回新建可读的
        System.out.println(list2 == copy2); // false
    }
}

Java11 新特性

Java11 是大版本周期变化后的第一个长期支持版本,一共包含 17 个 JEP JDK Enhancement Proposals,即 JDK 增强提案。从 JVM GC 的角度,JDK11 引入了两种新的 GC,包括目前还处于实验阶段但也许是划时代意义的 ZGC,为特定生产环境的苛刻需求提供了一个可能的选择。对部分企业核心存储产品,如果能够保证不超过 10ms 的 GC 暂停,可靠性将会上一个大的台阶,这是过去 GC 调优几乎做不到的。

String 新增方法

import org.junit.Test;

public class Java11Test {
    @Test
    public void test1(){
        // 判断字符串是否空白 => isBlank() => 制表符换行符空白
        System.out.println((" \t \n ").isBlank());
        // 去除首位空白 => strip()
        System.out.println(("---" + "\t zs \n ".strip() + "---"));
        // 去除尾部空白 => stripTrailing()
        System.out.println(("---" + "\t zs \n ".stripTrailing() + "---"));
        // 去除前部空格 => stripLeading()
        System.out.println(("---" + "\t zs".stripLeading() + "---"));
        // 复制字符串 => repeat()
        System.out.println("zsgzhz".repeat(3));
        // 行数统计 => lines().count()
        System.out.println("zsgzhz \n abc \n abc".lines().count());
    }
}

Optional 增强

import org.junit.Test;

import java.util.Optional;

public class Java11Test {
    @Test
    public void test2(){
        Optional<Object> op = Optional.empty();
        System.out.println(op.isPresent()); // 判断内部value是否存在
        // java11 新增 isEmpty()
        System.out.println(op.isEmpty()); // 判断内部value是否为空
    }
}

局部变量类型推断注解

在 var 上添加注解的语法形式在 JDK11 中得以实现。

import org.junit.Test;
import java.util.function.Consumer;

public class Java11Test {
    @Test
    public void test3(){
        // 允许
        // Consumer<String> con = (t) -> System.out.println(t.toUpperCase());
        // 错误 => 必须有类型;可以是var
        // Consumer<String> con1 = (@Deprecated t) -> System.out.println(t.toUpperCase());
        // 优势 => 在使用 Lambda 表达式给参数加上注解
        Consumer<String> con2 = (@Deprecated var t) -> System.out.println(t.toUpperCase());

    }
}

全新 HTTP 客户端 API

HTTP 用于传输网页的协议,早在1997年就被采用在目前的1.1版本中。到2015年,HTTP2 才成为标准。两者主要区别在于客户端和服务器之间构建和传输数据的方式。前者依赖于请求/响应周期,后者允许服务器 push 数据。

Java9 引入处理 HTTP 请求的 HTTP Client API 支持同步与异步,在 Java11 已成正式可用状态,储于 java.net 包中。其替代仅适用于 blocking 模式的 HttpURLConnection,并提供对 WebSocket 和 HTTP2 的支持。

import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.net.*;

public class Java11Test {
    @Test
    public void test4() throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder(URI.create("http://127/0/0/1:8080/test/")).build();
        HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
        HttpResponse<String> response = client.send(request, responseBodyHandler);
        String body = response.body();
        System.out.println(body);
    }
    @Test
    public void test5() {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder(URI.create("http://127/0/0/1:8080/test/")).build();
        HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
        CompletableFuture<HttpResponse<String>> sendAsync = client.sendAsync(request,responseBodyHandler);
        sendAsync.thenApply(t -> t.body()).thenAccept(System.out::println);
    }
}

结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!