提示

本文主要讲解 Java 9 的新特性。@ermo

# Java 9 新特性

# 模块系统(Module System)

Java 9 最大的亮点就是新增 模块系统,模块系统是对 Java 项目组织结构的一次重要变革,可以让 Java 构建的项目更加安全可靠。

# 接口可以添加私有方法(Private Interface Methods)

Java 9 允许接口定义私有方法,包括普通的私有成员方法和静态类型的私有方法。

public interface HelloService {

    default void sayDefault() {
        sayPrivate();
    }
    private void sayPrivate() {
        System.out.println("sayPrivate");
    }
}

public class HelloServiceImpl implements HelloService {

    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        helloService.sayDefault();
    }
}

执行 main 方法后输出一下内容。

sayDefault
sayPrivate
sayStatic

# Try-With-Resource 增强

Try With Resource 是 Java 7 的新特性,用于解决 IO 操作结束后繁琐的流关闭代码,前提是流的声明必须在 try 的代码块中。

Java 9 对这一功能进行了增强,流的声明可以放到 try 的代码块外部,但是流不可被多次赋值,推荐在声明一个流变量时加上 final 关键字。

import java.io.FileOutputStream;
public class ResourcesDemo {

    public static void main(String[] args) throws FileNotFoundException {
        String data = "Some text.";
        final FileOutputStream out = new FileOutputStream("ermo.txt");
        try(out) {
            out.write(data.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上例执行 main 方法后会在项目的根目录生成一个 ermo.txt 文件,文件内容为。

Some text.

如果对流变量重复赋值,编译时会提示如下错误。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class ResourcesDemo {

    public static void main(String[] args) throws FileNotFoundException {
        String data = "Some text.";
        FileOutputStream out = new FileOutputStream("ermo.txt");
        out = new FileOutputStream("ermocc.txt");
        // 编译失败
        // Variable used as a try-with-resources resource should be final or effectively final
        try(out) {
            out.write(data.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

# 匿名内部类增强

java 9 之前创建一个内部类,在创建一个泛型内部类时,后面的尖括号必须填写实际类型。

public abstract class AbstractPair<T> {
    abstract T opt(T a, T b);
}

public class InnerClassExample {

    public static void main(String[] args) {
        // new AbstractPair<String>() 这里的 <> 内必须声明类型 String,否则编译不通过
        AbstractPair<String> pair = new AbstractPair<String>() {
            @Override
            String opt(String a, String b) {
                return a.concat(b);
            }
        };
        System.out.println(pair.opt("ermo", ".cc"));
    }
}

java 9 允许后面尖括号内无需填写实际类型值,编译器会自动推断实际的类型。

public class InnerClassExample {

    public static void main(String[] args) {
        // new AbstractPair<String> 可以省略 String 类型
        AbstractPair<String> pair = new AbstractPair<>() {
            @Override
            String opt(String a, String b) {
                return a.concat(b);
            }
        };
        System.out.println(pair.opt("ermo", ".cc"));
    }
}

输出

ermo.cc

# @SafeVarargs 增强

@SafeVarargs 注解是 Java 7 引入的一个注解,用于抑制在方法形参中定义了可变参数后编译器提示的警告。

在 Java 7 时期,@SafeVarargs 只能作用于构造器、final 修饰的成员方法、静态方法上。

Java 9 允许将 @SafeVarargs 注解添加到私有的成员方法上。

import java.util.List;

public class SafeVarExample {

    // 编译器提示:Possible heap pollution from parameterized vararg type
    // @SafeVarargs
    public SafeVarExample(List<String>... names) {

    }

    // 编译器提示:Possible heap pollution from parameterized vararg type
    // @SafeVarargs
    public static void displayNameStatic(List<String>... names) {

    }

    // 编译器提示:Possible heap pollution from parameterized vararg type
    // @SafeVarargs
    public final void displayName(List<String>... names) {
    }
}

上例中的三个方法在为添加 @SafeVarargs 注解时,编译器会提示一下内容,添加 @SafeVarargs 后警告内容就会消失。

Possible heap pollution from parameterized vararg type

在 Java 9 之前,如果将 @SafeVarargs 添加加到私有的成员方法上,编译会报错,并提示以下内容。

    // Java 9 之前的版本,此处编译错误,并提示错误信息:@SafeVarargs is not allowed on non-final instance methods
    @SafeVarargs
    private void display(List<String>... names) {
        for (List<String> name : names) {
            System.out.println(name);
        }
    }

使用 Java 9 执行下例代码,可以成功编译通过。

import java.util.Arrays;
import java.util.List;

public class SafeVarExample {

    @SafeVarargs
    private void display(List<String>... names) {
        for (List<String> name : names) {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        SafeVarExample safeVar = new SafeVarExample();
        List<String> names = Arrays.asList("Java", "Python");
        safeVar.display(names);
    }
}

输出

[Java, Python]

# 集合工厂方法(Collection Factory Methods)

Java 9 引入了很多重载的集合工厂方法,用于创建不可变的集合实例。

public static void main(String[] args) {
        // Java 9 之前的版本
        List<String> names = Arrays.asList("Java", "Python", "C");    
        System.out.println(names);

        Map<String, String> oldMap = new HashMap<>();
        oldMap.put("K1", "V1");
        oldMap.put("K2", "V2");
        oldMap.put("K3", "V3");

        Collections.unmodifiableMap(new HashMap<String, String>() {{
            put("K1", "V1");
            put("K2", "V2");
            put("K2", "V2");
        }});

        // Java 9 之后的版本
        List<String> languages = List.of("Java", "Python", "C");
        Set<Integer> numbers = Set.of(1, 2, 3, 4, 5, 6, 6);
        Map<String, String> pairMap = Map.of("k1", "V1", "K2", "V2");

        List<String> emptyLit =  List.of();
        Set<String> emptySet = Set.of();
        Map<String, String> emptyMap = Map.of();
    }

# JShell-Java9 REPL

在 Java 9 引入 JShell,JShell 即 Java Shell,是一款 REPL(Read Eval Print Loop)交互解释器。

简单来说就是可以在 JShell 中输入一些简短的代码片段并立即获取到返回结果。比如 Chrome 浏览器开发者控制台和 Node 都提供了 REPL 这一功能。

# JShell 的优势

Java 属于编译类型的语言,开发完代码后需要进行编译,然后运行并验证结果,

如果是在开发和测试阶段,这个流程就比较冗长。比如开发人员想验证一个函数的语法、一行代码或者只想输出一个 hello world 类型的教学代码。

JShell 就是为解决这个问题,输入就可以立即获取到结果,免去编译工具、代码编辑工具这些不必要的因素。

# 环境变量

使用 JShell 前同样需要配置环境变量。

# 进入 JShell

$ jshell
|  欢迎使用 JShell -- 版本 19.0.1
|  要大致了解该版本, 请键入: /help intro

jshell> 

# 退出 JShell

$ /exit
|  再见

# hello world

进入 jshell 后,可以输入各种代码片段,连分号 ; 也可以不用加。

$ System.out.println("Hello World")
Hello World

# 创建变量

$ int i = 10
i ==> 10

$ BigDecimal amount = BigDecimal.ZERO;
amount ==> 0

# 表达式

$ int x = 8;int y = 9;x+y
x ==> 8
y ==> 9
$6 ==> 17

$ (2+3)*2+1
$7 ==> 11

# 方法

JShell 同样可以定义方法,如果代码过长可以键入 //,然后敲回车符进行换行。

$ public int add(int x,int y){return x+y;}
|  已创建 方法 add(int,int)

$ add(1,1)
$11 ==> 2

#

$ class Order{//
   ...>     public void sum(int x,int y){//
   ...>         System.out.println("Sum:" + (x+y));}}
|  已创建 类 Order

$ Order order = new Order();order.sum(1,1);
order ==> Order@1ddc4ec2
Sum:2

# 常用命令

命令 作用
/list 列出所有输入的记录
/vars 列出所有声明的变量
/imports 列出所有导入的包
/help 帮助命令

# 压缩字符串(Compact String)

之前 Java 版本对于字符串 String 类存储使用的是 char[],Java 9 改成了 byte[] 字节类数组。

private final char value[]; // java 9 之前的版本
private final byte[] value; // java 9 版本

# 参考