JAVA9新特性总结

345 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

前言

今天本来想写一下ES搜索的文章来着,但是在使用Spring Boot的部分配置项的时候发现配置过期了。所以暂时先不写ES了。相信很多同学用的java版本都是1.8版本的。现在已经出来18版本了所以我就把一些常用的JAVA9的特性总结一下

  • 注意:以下代码有个Sout.show方法,源码如下
public class Sout {
    public static void show(Object object) {
        System.out.println(object);
    }
     public static void show(String format, Object... args) {
        System.out.println(String.format(format, args));
    }
}

新特性

java模块系统

java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。

  • 模块的定义:代码和数据的封装体。

  • 模块的具体实现:模块的代码被组织成多个包,每个包中包含Java类和接口;模块的数据则包括资源文件和其他静态信息。 Java 9 模块的重要特征是在其工件(artifact)的根目录中包含了一个描述模块的 module-info.class 文 件。 工件的格式可以是传统的 JAR 文件或是 Java 9 新增的 JMOD 文件。这个文件由根目录中的源代码文件 module-info.java 编译而来。该模块声明文件可以描述模块的不同特征。 示例如下:

    image.png

  • 个人理解:

    • 同:多模块开发模式、微服务来发模式、DDD开发模式 java9的模块系统我在使用的时候感觉和上边三种的开发模式差不多,与其说差不多倒不如说可以一起使用。我们在java8采用微服务开发的时候,在经典的订单、商品、库存三个服务下可以各自添加自己的module-info文件。定义开放的方法等信息
    • 异 前段时间有个比较出名的面试题很活跃:大体的意思是虽然我的变量、方法是private修饰的,但是还是可以通过反射来获取到。我们不讨论这个面试题,但是我们可以用module-info来限制某些方法属于包内可用,包外不可用,即可解决这些反射和访问权限控制粒度不足的问题。
  • 使用方法

    module 本模块的名称{ 
    exports 对外暴露的包路径;
    requires 需要依赖的其他模块名称; 
    }
    

    如果写js es6比较多的同学可能会感到莫名的熟悉感,这个和export语法简直不要太相同

新的集合工厂方法

Java9提供了以下新的静态方法来创建集合,终于不用Array.asList了(虽然真的挺好用),下面这些方法可以更简洁的来创建集合

Set<Integer> set = Set.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
List<String> list = List.of("1", "2", "3", "4", "5");
Map<String, Object> map = Map.of("name", "lmh", "age", 20);
// map的属性如果超过10,需要使用下边这个方法
Map<String, Object> bigMap = Map.ofEntries(new AbstractMap.SimpleEntry<>("A1", "Apple"),
        new AbstractMap.SimpleEntry<>("A2", "Apple"), new AbstractMap.SimpleEntry<>("A3", "Apple"),
        new AbstractMap.SimpleEntry<>("A4", "Apple"), new AbstractMap.SimpleEntry<>("A5", "Apple"),
        new AbstractMap.SimpleEntry<>("A6", "Apple"), new AbstractMap.SimpleEntry<>("A7", "Apple"),
        new AbstractMap.SimpleEntry<>("A8", "Apple"), new AbstractMap.SimpleEntry<>("A9", "Apple"),
        new AbstractMap.SimpleEntry<>("A10", "Apple"), new AbstractMap.SimpleEntry<>("A11", "Apple"),
        new AbstractMap.SimpleEntry<>("A12", "Apple"), new AbstractMap.SimpleEntry<>("A13", "Apple"),
        new AbstractMap.SimpleEntry<>("A14", "Apple"), new AbstractMap.SimpleEntry<>("A15", "Apple"));
Sout.show(set);
Sout.show(list);
Sout.show(map);
Sout.show(bigMap);

私有接口方法

Java 9 不仅像 Java 8 一样支持接口默认方法,同时还支持私有方法。 在 Java 9 中,一个接口中能定义如下几种变量/方法:

  • 常量
  • 抽象方法
  • 默认方法
  • 静态方法
  • 私有方法
  • 私有静态方法 例如下边这个代码的示例
class MysqlFactory implements SqlLog {
    @Override
    public void getEnv() {
        System.out.println("mysql");
    }
}

class OracleFactory implements SqlLog {
    @Override
    public void getEnv() {
        System.out.println("oracle");
    }
}

/**
 * 在java9中,一个接口能定义如下几种变量/方法 1:常量 2:抽象方法 3:默认方法 4:静态方法 5:私有方法 6:私有静态方法
 */
interface SqlLog {
    /**
     * 常量
     */
    static String PROJECT_NAME = "ipm";

    /**
     * 抽象方法
     */
    void getEnv();

    /**
     * 默认方法
     *
     * @param msg
     */
    default void logInfo(String msg) {
        log(msg, "INFO");
    }

    /**
     * 静态方法
     *
     * @param pass
     */
    static void setDbPass(String pass) {
        System.out.println("密码为:" + pass);
    }

    String ORACLE = "Oracle_Db";
    String MYSQL = "mysql_Db";

    /**
     * 私有方法
     *
     * @param msg
     * @param prefix
     */
    private void log(String msg, String prefix) {
        getConnection();
        System.out.println(prefix + msg);
        closeConnect();
    }

    /**
     * 私有静态方法
     */
    private static void getConnection() {
        System.out.println("Open Db Conn");
    }

    /**
     * 私有静态方法
     */
    private static void closeConnect() {
        System.out.println("Close Db Conn");
    }
}

不得不说interface越升级越复杂了,感觉离开始的的设计初衷越走越远了,这里大家安装实际情况使用吧

改进的进程 API

Java 9 向 Process API 添加了一个名为 ProcessHandle 的接口来增强 java.lang.Process 类。 ProcessHandle 接口的实例标识一个本地进程,它允许查询进程状态并管理进程。 ProcessHandle 接口中声明的 onExit() 方法可用于在某个进程终止时触发某些操作。

ProcessBuilder pb = new ProcessBuilder("notepad.exe");
String np = "Not Present";
Process p = pb.start();
ProcessHandle.Info info = p.info();
CompletableFuture<Process> cf = p.onExit();
Sout.show("Process ID : %s", p.pid());
Sout.show("Command name : %s", info.command().orElse(np));
Sout.show("Command line : %s", info.commandLine().orElse(np));
Sout.show("Start time: %s",
        info.startInstant().map(i -> i.atZone(ZoneId.systemDefault()).toLocalDateTime().toString()).orElse(np));
Sout.show("Arguments : %s",
        info.arguments().map(a -> Stream.of(a).collect(Collectors.joining(" "))).orElse(np));
Sout.show("User : %s", info.user().orElse(np));
var doneFlag = false;
do {
    doneFlag = cf.isDone();
} while (!doneFlag);
Sout.show(doneFlag);

这个算是一个优化吧,不过对于平常的工作中,对于进程的使用不是很多。吐槽一句,看进程的优化不如去好好看一下多线程及线程池呢。大家也可以在升级jdk17(长时间支持的版本)之后加上这个进程补丁

stream 新方法

  • takeWhile 方法 takeWhile() 方法使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 true。如果没有满足断言条件为true的元素,将返回一个空的 Stream。 takeWhile() 方法在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素;在无序的 Stream 中,takeWhile 返回从开头开始的符合 Predicate 要求的元素的子集。
  • dropWhile 方法 dropWhile 方法和 takeWhile 作用相反的,使用一个断言作为参数,直到断言语句第一次返回 false 才返回给定 Stream 的子集。
  • iterate方法 方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的 hasNext 的 predicate 返回 false 时,迭代停止。
  • ofNullable方法 ofNullable 方法可以预防 NullPointerExceptions 异常, 可以通过检查流来避免 null 值。 如果指定元素为非 null,则获取一个元素并生成单个元素流,元素为 null 则返回一个空流。
 List<String> strList = List.of("1", "2", "3", "4", "5", "", "6", "7", "8", "9");
 strList.stream().takeWhile(item -> !item.isEmpty()).forEach(Sout::show);
 Sout.show("123123");
 strList.stream().dropWhile(item -> !item.isEmpty()).forEach(Sout::show);
 Sout.show("456456");
 IntStream.iterate(3, x -> x < 10, x -> x + 3).forEach(Sout::show);
 Sout.show("789789");
 long count = Stream.ofNullable(100).count();
 Sout.show(count);
 count = Stream.ofNullable(null).count();
 Sout.show(count);

大家可以多自己实践一下,感觉比较实用

try-with-resources

try-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。

class AutoClose implements AutoCloseable {
    @Override
    public void close() throws IOException {
        Sout.show("触发关闭");
    }
    public String toUpper(String str) {
        return str.toUpperCase();
    }
}
   /**
     * java9 优化为不必声明新变量即可接收参数,但是参数必须为final或隐式final
     */
    @Test
    public void demo2() {
        String str = "abc";
        AutoClose ac = new AutoClose();
        try (ac) {
            str = ac.toUpper(str);
            Sout.show("执行业务逻辑中");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Sout.show(str);
    }

这里感觉要在升级之后和同事做技术分享,否则会出现非final或非隐式final一直未关闭的情况

@Deprecated改进

Java 9 中注解增加了两个新元素:since 和 forRemoval

  • since: 元素指定已注解的API元素已被弃用的版本

  • forRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API

    /**
     * 2021-07-14之后过时,且将在未来的版本会删除
     */
    @Deprecated(since = "2021-07-14", forRemoval = true)
    class DeprecatedAnnoOne {
    
    }
    class DeprecatedAnnoTwo {
    
    }
    

一般写在注释上的说明可以放在这里了,起提醒的作用比较多一点

钻石操作符

钻石操作符是在 java 7 中引入的,可以让代码更易读,但它不能用于匿名的内部类。 在 java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性

public class DiamondOperatorTest {
    /**
     * 1.9之前的写法
     */
    @Test
    public void demo1() {
        BaseDiamondOperatorHandler<Integer> intHandle = new BaseDiamondOperatorHandler<Integer>(1) {
            @Override
            void handle() {
                Sout.show(content);
            }
        };
        BaseDiamondOperatorHandler<Double> doubleHandle = new BaseDiamondOperatorHandler<Double>(2.22) {
            @Override
            void handle() {
                Sout.show(content);
            }
        };
        BaseDiamondOperatorHandler<String> strHandle = new BaseDiamondOperatorHandler<String>("Hello") {
            @Override
            void handle() {
                Sout.show(content);
            }
        };
        intHandle.handle();
        doubleHandle.handle();
        strHandle.handle();
    }

    /**
     * 1.9及之后的写法
     */
    @Test
    public void demo2() {
        BaseDiamondOperatorHandler<?> intHandle = new BaseDiamondOperatorHandler<>(1) {
            @Override
            void handle() {
                Sout.show(content);
            }
        };
        BaseDiamondOperatorHandler<?> doubleHandle = new BaseDiamondOperatorHandler<>(2.22) {
            @Override
            void handle() {
                Sout.show(content);
            }
        };
        BaseDiamondOperatorHandler<?> strHandle = new BaseDiamondOperatorHandler<>("Hello") {
            @Override
            void handle() {
                Sout.show(content);
            }
        };
        intHandle.handle();
        doubleHandle.handle();
        strHandle.handle();
    }
}

abstract class BaseDiamondOperatorHandler<T> {
    public T content;

    public BaseDiamondOperatorHandler(T content) {
        this.content = content;
    }

    /**
     * handle处理
     */
    abstract void handle();

}

这里感觉是类型推导的前身,在java9有这个特性之后java10马上出了局部变量类型推导

Optional 类

在 java 9 中, 添加了三个方法来改进optional的功能:

  • stream() stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的Stream,否则返回一个空的 Stream(Stream.empty())

Sout.show(Optional.empty().stream().count());

  • ifPresentOrElse() ifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即emptyAction.run()。如果值存在,返回 Optional 指定的值,否则返回一个预设的值。

Optional.of(10086).ifPresentOrElse(Sout::show, () -> Sout.show("not present"));

  • or() or如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
 Supplier<Optional<String>> supplierOptional = () -> Optional.of("Not Present");
 Optional.empty().or(supplierOptional).ifPresent(item->Sout.show(item));

结语

java9的大部分新特性总计如上,由于这些代码我编写的比较早,一些知识点也是参考的别人的技术文档,所以可能有遗漏和不足。欢迎留言交流

  • 下期预告:spring boot初步集成es8.x版本进行增删查操作。欢迎关注!