当前位置: 首页 > news >正文

JDK 9-17 新特性介绍

Java新特性介绍

Java 8是Oracle 公司于 2014 年 3 月 18 日发布的,距离今天已经过了近十年的时间了,但是由于Java 8的稳定和生态完善(目前仍是LTS长期维护版本),依然有很多公司在坚持使用Java 8,不过随着SpringBoot 3.0的到来,现在强制要求使用Java 17版本(同样也是LTS长期维护版本),我们来聊聊 java 9-17的新特性.

 

Java 9 新特性

Java 9的主要特性有,全新的模块机制、接口的private方法等。

模块机制

在我们之前的开发中,不知道各位有没有发现一个问题,就是当我们导入一个jar包作为依赖时(包括JDK官方库),实际上很多功能我们并不会用到,但是由于它们是属于同一个依赖捆绑在一起,这样就会导致我们可能只用到一部分内容,但是需要引用一个完整的类库,实际上我们可以把用不到的类库排除掉,大大降低依赖库的规模。

于是,Java 9引入了模块机制来对这种情况进行优化,在之前的我们的项目是这样的:

 而在引入模块机制之后:

可以看到,模块可以由一个或者多个在一起的 Java 包组成,通过将这些包分出不同的模块,我们就可以按照模块的方式进行管理了。这里我们创建一个新的项目,并在src目录下,新建module-info.java文件表示此项目采用模块管理机制:

 

我们发现,JDK为我们提供的某些框架不见了:

  

 这里我们导入java.logging相关模块后,就可以正常使用Logger了: 

 

全新的模块化机制提供了另一个级别的Java代码可见性、可访问性的控制,不过,你以为仅仅是做了包的分离吗?我们可以来尝试通过反射获取JDK提供的类中的字段:  

但是我们发现,在程序运行之后,修改操作被阻止了: 

 反射 API 的 Java 9 封装和安全性得到了改进,如果模块没有明确授权给其他模块使用反射的权限,那么其他模块是不允许使用反射进行修改的.

模块具有四种类型:

**系统模块:**来自JDK和JRE的模块(官方提供的模块,比如我们上面用的),我们也可以直接使用java --list-modules命令来列出所有的模块,不同的模块会导出不同的包供我们使用。
**应用程序模块:**我们自己写的Java模块项目。
**自动模块:**可能有些库并不是Java 9以上的模块项目,这种时候就需要做兼容了,默认情况下是直接导出所有的包,可以访问所有其他模块提供的类,不然之前版本的库就用不了了。
**未命名模块:**我们自己创建的一个Java项目,如果没有创建module-info.java,那么会按照未命名模块进行处理,未命名模块同样可以访问所有其他模块提供的类,这样我们之前写的Java 8代码才能正常地在Java 9以及之后的版本下运行。不过,由于没有使用Java 9的模块新特性,未命名模块只能默认暴露给其他未命名的模块和自动模块,应用程序模块无法访问这些类(实际上就是传统Java 8以下的编程模式,因为没有模块只需要导包就行)

这里我们就来创建两个项目,看看如何使用模块机制,首先我们在项目A中,添加一个User类,一会项目B需要用到:

接着我们编写一下项目A的模块设置:

这里我们将com.test包下所有内容都暴露出去,默认情况下所有的包都是私有的,就算其他项目将此项目作为依赖也无法使用。

接着我们现在想要在项目B中使用项目A的User类,我们需要进行导入:

 现在我们就可以在Main类中使用模块module.a中暴露出来的包内容了.

当然除了普通的exports进行包的暴露之外,我们也可以直接指定将包暴露给指定的模块:

 我们希望依赖可以传递,就是哪个模块用了什么依赖,依赖此模块的模块也会自动进行依赖,我们可以通过一个关键字解决:

 我们依赖了一个模块,是没办法直接进行反射操作的:

那么怎么样才可以使用反射呢?我们可以为其他模块开放某些运行使用反射的类:

 

 还可以指定模块需要使用的抽象类或是接口实现:

 

 我们可以在模块B中去实现一下,然后声明我们提供了实现类:

 

JShell交互式编程

Java 9为我们通过了一种交互式编程工具JShell,有Python味.

 

 

 

 

 

接口中的private方法

 在Java 8中,接口中 的方法支持添加default关键字来添加默认实现.

而在Java 9中,接口再次得到强化,现在接口中可以存在私有方法了:

 注意私有方法必须要提供方法体,因为权限为私有的,也只有这里能进行方法的具体实现了,并且此方法只能被接口中的其他私有方法或是默认实现调用。

集合类新增工厂方法

在之前,如果我们想要快速创建一个Map只能.

而在Java 9之后,我们可以直接通过of方法来快速创建了:

 of方法还被重载了很多次,分别适用于快速创建包含0~10对键值对的Map:

 但是注意,通过这种方式创建的Map和通过Arrays创建的List比较类似,也是无法进行修改的。

 当然,除了Map之外,其他的集合类都有相应的of方法:

Set<String> set = Set.of("BBB", "CCC", "AAA");  //注意Set中元素顺序并不一定你的添加顺序
            List<String> list = List.of("AAA", "CCC", "BBB");   //也不用 Arrays.asList()
        System.out.println("===> set: " + set);
        System.out.println("===> list: " + list);

改进的 Stream API

Stream 在Java 9得到了进一步的增强:

public class StreamApi {

    public static void main(String[] args) {
        Stream
                .of(null) //如果传入null会报错
                .forEach(System.out :: println);
        Stream
                .ofNullable(null) //使用新增的ofNullable方法,这样就不会了,不过这样的话流里面就没东西了
                .forEach(System.out :: println);
    }

}

我们可以通过迭代快速生成一组数据(实际上Java 8就有了,这里新增的是允许结束迭代的):

        // java 8
        Stream
                .iterate(0, i -> i + 1)
                //Java8只能像这样生成无限的流,第一个参数是种子,就是后面的UnaryOperator的参数i一开始的值,最后会返回一个值作为i的新值,每一轮都会执行UnaryOperator并生成一个新值到流中,这个是源源不断的,如果不加limit()进行限制的话,将无限生成下去。
                .limit(20)   //这里限制生成20个
                .forEach(System.out::println);
        // java 9
        Stream
                .iterate(0, i -> i < 20, i -> i + 1)  // 可以直接指定 i < 20
                .forEach(System.out::println);

Stream还新增了对数据的截断操作,比如我们希望在读取到某个元素时截断,不再继续操作后面的元素:

        // java 9
        Stream
                .iterate(0, i -> i < 20, i -> i + 1)  // 可以直接指定 i < 20
//                .takeWhile(i -> i < 10)  //当i小于10时正常通过,一旦大于等于10直接截断
                .dropWhile(i -> i < 10)   // 和上面的相反, 直接丢弃满足条件的.
                .forEach(System.out::println);

其他小型变动

Try-with-resource语法现在不需要再完整的声明一个变量了,我们可以直接将现有的变量丢进去:

        // java8
//        try (InputStream inputStream = Files.newInputStream(Paths.get("pom.xml"));) {   
//            for (int i = 0; i < 100; i++)
//                System.out.print((char) inputStream.read());
//        }

//        java 9
        InputStream inputStream = Files.newInputStream(Paths.get("pom.xml"));
        try (inputStream) {   //单独丢进try中,效果是一样的
            for (int i = 0; i < 100; i++)
                System.out.print((char) inputStream.read());
        }

 但是编译还是最终编译成了 try catch:

Optional类, 在Java 9新增了一些更加方便的操作:

        String str = null;
        // java 8
        Optional
                .ofNullable(str)
                .ifPresent(s -> System.out.println(s.toLowerCase()));

        // java 9
        //通过使用ifPresentOrElse,我们同时处理两种情况
        Optional.ofNullable(str).ifPresentOrElse(s -> {
            System.out.println("被包装的元素为:"+s);     //第一种情况和ifPresent是一样的
        }, () -> {
            System.out.println("被包装的元素为null");   //第二种情况是如果为null的情况
        });

当然除了以上的特性之外还有Java 9的多版本JAR包支持、CompletableFuture API的改进等,因为不太常用,就不做介绍了.

Java 10 新特性

Java 10主要带来的是一些内部更新,相比Java 9带来的直观改变不是很多,其中比较突出的就是局部变量类型推断了。

局部变量类型推断

    public static void main(String[] args) {
        // String a = "Hello World!";   之前我们定义变量必须指定类型
        var a = "Hello World!";   //现在我们使用var关键字来自动进行类型推断,因为完全可以从后面的值来判断是什么类型
    }

但是注意,var关键字必须位于有初始值设定的变量上,否则鬼知道你要用什么类型。

虽然是有了var关键字进行自动类型推断,但是最终还是会变成String类型,得到的Class也是String类型。但是Java终究不像JS那样进行动态推断,这种类型推断仅仅发生在编译期间,到最后编译完成后还是会变成具体类型的:

var关键字仅适用于局部变量,我们是没办法在其他地方使用的,比如类的成员变量:

 Java 11 新特性

Java 11 是继Java 8之后的又一个TLS长期维护版本,在Java 17出现之前,一直都是此版本作为广泛使用的版本,其中比较关键的是用于Lambda的形参局部变量语法。

用于Lambda的形参局部变量语法

我们刚认识了var关键字,它能够直接让局部变量自动进行类型推断,不过它不支持在lambda中使用:

 但是实际上这里是完全可以进行类型推断的,所以在Java 11,支持了,这样编写就不会报错了:

    public static void main(String[] args) {
        Consumer<String> consumer = (var str) -> {

        };
        Consumer<String> consumer1 = (String str) -> {

        };
    }

针对于String类的方法增强

在Java 11为String新增一些更加方便的操作:

    public static void main(String[] args) {
        String s = "", s1 = "   ";
        System.out.println(s.isEmpty());
        System.out.println(s1.isEmpty());
        System.out.println(s1.isBlank()); //isBlank方法用于判断是否字符串为空或者是仅包含空格

        var str = "AB\nC\nD";
        str.lines()   //根据字符串中的\n换行符进行切割,分为多个字符串,并转换为Stream进行操作
            .forEach(System.out::println);
    }

我们还可以通过repeat()方法来让字符串重复拼接:

        var str = "ABCD";
        //比如现在我们有一个ABCD,但是现在我们想要一个ABCDABCD这样的基于原本字符串的重复字符串
        System.out.println(str.repeat(2));  //一个repeat就搞定了
        String str = " A B C D ";
        System.out.println(str.trim());   //去除首尾空格
        System.out.println(str.strip());   //去除首尾空格
        /**
         * trim()可以去除字符串前后的半角空白字符
         * strip()可以去除字符串前后的全角和半角空白字符
         */
        System.out.println(str.stripLeading());  //去除首部空格
        System.out.println(str.stripTrailing());   //去除尾部空格

全新的HttpClient使用

在Java 9的时候其实就已经引入了全新的Http Client API,用于取代之前比较老旧的HttpURLConnection类,新的API

主要的特性有:

  • 完整支持HTTP 2.0 或者HTTP 1.1

  • 支持 HTTPS/TLS

  • 有简单的阻塞使用方法

  • 支持异步发送,异步时间通知

  • 支持WebSocket

  • 支持响应式流

    public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {

        HttpClient client = HttpClient.newHttpClient();   //直接创建一个新的HttpClient
        //现在只需要构造一个Http请求实体,就可以让客户端帮助我们发送出去了(实际上就跟浏览器访问类似)
        HttpRequest request = HttpRequest.newBuilder().uri(new URI("https://www.baidu.com")).build();
        //现在就可以把请求发送出去了,注意send方法后面还需要一个响应体处理器(内置了很多)这里我们选择ofString直接吧响应实体转换为String字符串
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        //来看看响应实体是什么吧
        System.out.println(response.body());
    }

Java 12-16 新特性

由于Java版本的更新迭代速度自Java 9开始为半年更新一次(Java 8到Java 9隔了整整三年),所以各个版本之间的更新内容比较少,所以咱们就多个版本放在一起说了。

新的switch语法

在Java 12引入全新的switch语法,让我们使用switch语句更加的灵活,比如我们想要编写一个根据成绩得到等级的方法:

public static void main(String[] args) {
        System.out.println(gradeNew(79));

    }

    public static String gradeNew(int score) {
        score /= 10;
        return switch (score) {
            case 10, 9 -> "优秀";
            case 8, 7 -> "良好";
            case 6 -> "及格";
            default -> "不及格";
        };
    }

    // yield
    public static String gradeNew2(int score) {
        score /= 10;
        return switch (score) {
            case 10, 9 -> "优秀";
            case 8, 7 -> "良好";
            case 6 -> "及格";
            default -> {
                System.out.println("做了很多事情");
                yield "不及格";
            }
        };
    }

    /**
     * 传入分数(范围 0 - 100)返回对应的等级:
     * 100-90:优秀
     * 70-80:良好
     * 60-70:及格
     * 0-60:寄
     *
     * @param score 分数
     * @return 等级
     */
    public static String gradeBefore(int score) {
        score /= 10;  //既然分数段都是整数,那就直接整除10
        String res = null;
        switch (score) {
            case 10:
            case 9:
                res = "优秀";   //不同的分数段就可以返回不同的等级了
                break;   //别忘了break,不然会贯穿到后面
            case 8:
            case 7:
                res = "良好";
                break;
            case 6:
                res = "及格";
                break;
            default:
                res = "不及格";
                break;
        }
        return res;
    }

这种全新的语法,可以说极大地方便了我们的编码,不仅代码简短,而且语义明确。唯一遗憾的是依然不支持区间匹配。

**注意:**switch表达式在Java 14才正式开放使用,所以我们项目的代码级别需要调整到14以上。

文本块

js, python 的相同语法,三引号:

    public static void main(String[] args) {
        var str = """ 
                dsfds
                sdfsg " > < !
                 dsfdsg dfsdf
                """;
        System.out.println(str);
    }

可以看到,Java中也可以使用这样的三引号来表示字符串了,并且我们可以随意在里面使用特殊字符,包括双引号等,但是最后编译出来的结果实际上还是会变成一个之前这样使用了转义字符的字符串:

 **注意:**文本块表达式在Java 15才正式开放使用,所以我们项目的代码级别需要调整到15以上。

新的instanceof语法

在之前我们一直都是采用这种先判断类型,然后类型转换,最后才能使用的方式,但是这个版本instanceof加强之后,我们就不需要了,我们可以直接将student替换为模式变量:

//    @Override
//    public boolean equals(Object obj) {
//        if(obj instanceof Student) {   //首先判断是否为Student类型
//            Student student = (Student) obj;  //如果是,那么就类型转换
//            return student.name.equals(this.name);  //最后比对属性是否一样
//        }
//        return false;
//    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Student student) {   
            return student.name.equals(this.name);  
        }
        return false;
    }

 在使用instanceof判断类型成立后,会自动强制转换类型为指定类型,简化了我们手动转换的步骤。

**注意:**新的instanceof语法在Java 16才正式开放使用,所以我们项目的代码级别需要调整到16以上。

空指针异常的改进

    public static void main(String[] args) {
        test(null, "sdf");
    }

    public static void test(String a, String b){
        int length = a.length() + b.length();   //可能给进来的a或是b为null
        System.out.println(length);
    }

 但是当我们在Java 14或更高版本运行时:

记录类型

继类、接口、枚举、注解之后的又一新类型: "记录", 在Java 14中首次出场. 有点 Lombok 的感觉.

在实际开发中,很多的类仅仅只是充当一个实体类罢了,保存的是一些不可变数据,比如我们从数据库中查询的账户信息,最后会被映射为一个实体类:

 

 使用起来也很方便:

并且toString也是被重写了的:

 

        UserRecord userRecord = new UserRecord("zhangsan", 20);
        System.out.println(userRecord);
UserRecord[name=zhangsan, age=20]

equals()方法仅做成员字段之间的值比较,也是帮助我们实现好了的:

        UserRecord userRecord = new UserRecord("zhangsan", 20);
        System.out.println(userRecord);
        UserRecord userRecord2 = new UserRecord("zhangsan", 20);
        // 两个属性都是一模一样的
        System.out.println(userRecord.equals(userRecord2)); //得到true

//支持实现接口,但是不支持继承,因为继承的坑位已经默认被占了

 

Java 17 新特性

Java 17目前最新的LTS长期维护版本.

密封类型

密封类型可以说是Java 17正式推出的又一重磅类型,它在Java 15首次提出并测试了两个版本。

在Java中,我们可以通过继承(extends关键字)来实现类的能力复用、扩展与增强。但有的时候,可能并不是所有的类我们都希望能够被继承。所以,我们需要对继承关系有一些限制的控制手段,而密封类的作用就是限制类的继承。

实际上在之前我们如果不希望别人继承我们的类,可以直接添加final关键字. 这样有一个缺点,如果添加了final关键字,那么无论是谁,包括我们自己也是没办法实现继承的,但是现在我们有一个需求,只允许我们自己写的类继承A,但是不允许别人写的类继承A,这时该咋写?在Java 17之前想要实现就很麻烦。

 non-sealed主动放弃了密封特性:

密封类型有以下要求:

        1. 可以基于普通类、抽象类、接口,也可以是继承自其他接抽象类的子类或是实现其他接口的类等。
        2. 必须有子类继承,且不能是匿名内部类或是lambda的形式。
        3. sealed写在原来final的位置,但是不能和final、non-sealed关键字同时出现,只能选择其一。
        4. 继承的子类必须显式标记为final、sealed或是non-sealed类型。

标准的声明格式如下:

public sealed [abstract] [class/interface] 类名 [extends 父类] [implements 接口, ...] permits [子类, ...]{
		//里面的该咋写咋写
}

注意子类格式为:

public [final/sealed/non-sealed] class 子类 extends 父类 {   //必须继承自父类
			//final类型:任何类不能再继承当前类,到此为止,已经封死了。
  		//sealed类型:同父类,需要指定由哪些类继承。
  		//non-sealed类型:重新开放为普通类,任何类都可以继承。
}

我们也可以通过反射来获取类是否为密封类型:

    public static void main(String[] args) {
        Class<B> bClass = B.class;
        Class<C> cClass = C.class;
        Class<D> dClass = D.class;
        System.out.println(bClass.isSealed());   // true
        System.out.println(cClass.isSealed());   // false
        System.out.println(dClass.isSealed());   // false
    }

以上就是 Java 9 - 17的主要新特性.

相关文章:

  • 力扣 每日一题 902. 最大为 N 的数字组合【难度:困难,rating: 1989】(数学 / 数位dp)
  • 【微信小程序】电商移动前端API文档
  • 基于FPGA的远程升级系统概述
  • 适用于非科班的深度学习/机器学习快速上手路线
  • 【Python 之 Bluebridge Cup】day_007(涉VIP题,目前没有权限提交,需再提交):阶乘计算(高精度) || 高精度加法
  • 3、初识MySQL
  • Spring事务及分布式事务专题
  • Uniapp零基础开发学习笔记(1) - 项目初步创建
  • 【学生管理系统】整合JWT(完)
  • 腾讯面试——机器学习岗面试总结
  • 【Linux】4.0进程控制
  • 初识C语言--第二弹
  • React 中 memo useMemo useCallback 到底该怎么用
  • 计算模型的GFLOPs和参数量 举例VGG16和DETR
  • JDK8新时间日期API
  • CAP理论的例子讲解
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • Intervention/image 图片处理扩展包的安装和使用
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • JavaScript设计模式系列一:工厂模式
  • vue-cli在webpack的配置文件探究
  • Vue全家桶实现一个Web App
  • 仿天猫超市收藏抛物线动画工具库
  • 使用Gradle第一次构建Java程序
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • # include “ “ 和 # include < >两者的区别
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • ${factoryList }后面有空格不影响
  • (poj1.2.1)1970(筛选法模拟)
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (算法)求1到1亿间的质数或素数
  • 、写入Shellcode到注册表上线
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .NET建议使用的大小写命名原则
  • .net知识和学习方法系列(二十一)CLR-枚举
  • @我的前任是个极品 微博分析
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429
  • [ 常用工具篇 ] POC-bomber 漏洞检测工具安装及使用详解
  • [ 数据结构 - C++]红黑树RBTree
  • []T 还是 []*T, 这是一个问题
  • [BZOJ] 1001: [BeiJing2006]狼抓兔子
  • [C/C++]关于C++11中的std::move和std::forward
  • [C/C++]数据结构 循环队列
  • [Codeforces1137D]Cooperative Game
  • [Django 0-1] Core.Checks 模块
  • [gdc19]《战神4》中的全局光照技术
  • [git] windows系统安装git教程和配置