Java新特性

2023-11-03 09:17:55 浏览数 (2)

Java新特性

介绍Java 9 - Java 17这些版本的所有新增特性

随着SpringBoot 3.0的到来,现在强制要求使用Java 17版本(同样也是LTS长期维护版本)

Java 8 关键特性

Lambda表达式

在Java 8之后,lambda表达式重写Runnable接口的run()方法:

代码语言:javascript复制
public static void main(String[] args) {
    //现在我们想新建一个线程来做事情
    Thread thread = new Thread(() -> {
        System.out.println("Hello World!");  //只需留下我们需要具体实现的方法体
    });
    thread.start();
}

匿名内部类会在编译时创建一个单独的class文件,但是lambda却不会

Lambda为所需要的接口提供了一个方法作为它的实现,而之后创建实现类就只需要交给JVM去处理就好了。

Lambda表达式的具体规范:

  • 标准格式为:([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
  • 和匿名内部类不同,Lambda仅支持接口,不支持抽象类
  • 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)

Runable类:

代码语言:javascript复制
@FunctionalInterface   //添加了此注解的接口,都支持lambda表达式,符合函数式接口定义
public interface Runnable {
    public abstract void run();   //有且仅有一个抽象方法,此方法返回值为void,且没有参数
}

只有一个参数,可以不用添加小括号(多个参数时需要)

返回语句这一行,所以可以直接写最终返回的结果,并且无需花括号

可以将某个方法作为lambda表达式的方法体实现(其实这就是一种方法引用,引用了一个方法过来)

代码语言:javascript复制
public static void main(String[] args) {
    Test test = Main::impl;    //使用 类名::方法名称 的形式来直接引用一个已有的方法作为实现
}

public static String impl(Integer i){
    return "我是已经存在的实现" i;
}

成员方法也可以让对象本身不成为参与的那一方,仅仅引用方法

Optional类

Optional特性,来让我们更优雅的处理空指针异常。

可以将任何的变量包装进Optional类中使用:

代码语言:javascript复制
public static void hello(String str){
    Optional
            .ofNullable(str)   //将str包装进Optional
            .ifPresent(s -> {   //ifPresent表示只有对象不为null才会执行里面的逻辑,实现一个Consumer(接受一个参数,返回值为void)
                System.out.println(s);   
            });
}

直接从Optional中获取被包装的对象:

代码语言:javascript复制
System.out.println(Optional.ofNullable(str).get());

当被包装的对象为null时会直接抛出异常

指定如果get的对象为null的替代方案:

代码语言:javascript复制
System.out.println(Optional.ofNullable(str).orElse("VVV"));   //orElse表示如果为空就返回里面的内容

java9新增api:

代码语言:javascript复制
public static void main(String[] args) {
    String str = null;
    Optional.ofNullable(str).ifPresentOrElse(s -> {  //通过使用ifPresentOrElse,我们同时处理两种情况
        System.out.println("被包装的元素为:" s);     //第一种情况和ifPresent是一样的
    }, () -> {
        System.out.println("被包装的元素为null");   //第二种情况是如果为null的情况
    });
}

or()方法快速替换为另一个Optional类:

代码语言:javascript复制
public static void main(String[] args) {
    String str = null;
    Optional.ofNullable(str)
      .or(() -> Optional.of("AAA"))   //如果当前被包装的类不是null,依然返回自己,但是如果是null,那就返回Supplier提供的另一个Optional包装
      .ifPresent(System.out::println);
}

Java 9 新特性

模块机制

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

模块可以由一个或者多个在一起的 Java 包组成,通过将这些包分出不同的模块,我们就可以按照模块的方式进行管理了。

src目录下,新建module-info.java文件表示此项目采用模块管理机制:

代码语言:javascript复制
module NewHelloWorld {  //模块名称随便起一个就可以,但是注意必须是唯一的,以及模块内的包名也得是唯一的,即使模块不同
    
}

进行模块导入:

代码语言:javascript复制
module NewHelloWorld {  //模块名称随便起一个就可以
    requires java.logging;   //除了JDK的一些常用包之外,只有我们明确需要的模块才会导入依赖库
  	//当然如果要导入JavaSE的所有依赖,想之前一样的话,直接 requires java.se;  即可
}

尝试通过反射获取JDK提供的类中的字段:

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

模块机制四种类型:

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

直接指定将包暴露给指定的模块:

代码语言:javascript复制
module module.a {
    exports com.test to module.b;   //这里我们将com.test包暴露给指定的模块module.b,非指定的模块即使导入也无法使用
}

如果模块module.a依赖于其他模块,不会传递依赖模块

依赖传递关键字:

代码语言:javascript复制
module module.a {
    exports com.test to module.b;  
    requires transitive java.logging;   //使用transitive来向其他模块传递此依赖
}

为其他类开通反射权限:

代码语言:javascript复制
module module.a {
    exports com.test to module.b;
    opens com.test;   //通过使用opens关键字来为其他模块开放反射权限
  	//也可以指定目标开放反射 opens com.test to module.b;
}

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

代码语言:javascript复制
open module module.a {
    exports com.test to module.b;
    uses com.test.Test;  //使用uses指定,Test是一个接口(比如需要的服务等),模块需要使用到
}

声明我们提供了实现类:

代码语言:javascript复制
package com.main;

import com.test.Test;

public class TestImpl implements Test {

}
代码语言:javascript复制
module module.b {
    requires module.a;   //导入项目A的模块,此模块暴露了com.test包
    provides com.test.Test with com.main.TestImpl;  //声明此模块提供了Test的实现类
}
接口中的private方法

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

代码语言:javascript复制
public interface Test {
    default void test(){
        System.out.println("我是test方法默认实现");
    }
}

Java 9中,接口中可以存在私有方法了:

代码语言:javascript复制
public interface Test {
    default void test(){
        System.out.println("我是test方法默认实现");
        this.inner();   //接口中方法的默认实现可以直接调用接口中的私有方法
    }
    
    private void inner(){   //声明一个私有方法
        System.out.println("我是接口中的私有方法!");
    }
}

私有方法必须要提供方法体,并且此方法只能被接口中的其他私有方法或是默认实现调用。

集合类工厂方法

Java 9之后,通过of方法来快速创建Map:

代码语言:javascript复制
public static void main(String[] args) {
    Map<String, Integer> map = Map.of("AAA", 18, "BBB", 20);  //直接一句搞定

    System.out.println(map);
}

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

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

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

改进 Stream API

JDK1.8新增的Stream API,通过它大大方便了我们的编程:

代码语言:javascript复制
public static void main(String[] args) {
    Stream
            .of("A", "B", "B", "C")   //这里我们可以直接将一些元素封装到Stream中
            .filter(s -> s.equals("B"))   //通过过滤器过滤
            .distinct()   //去重
            .forEach(System.out::println);   //最后打印
}

Java 9进一步的增强:

代码语言:javascript复制
public static void main(String[] args) {
    Stream
            .of(null)   //如果传入null会报错
            .forEach(System.out::println);

    Stream
            .ofNullable(null) //使用新增的ofNullable方法,这样就不会了,不过这样的话流里面就没东西了
            .forEach(System.out::println);
}

通过迭代快速生成一组数据:

代码语言:javascript复制
public static void main(String[] args) {
    Stream
            .iterate(0, i -> i   1)   //Java8只能像这样生成无限的流,第一个参数是种子,就是后面的UnaryOperator的参数i一开始的值,最后会返回一个值作为i的新值,每一轮都会执行UnaryOperator并生成一个新值到流中,这个是源源不断的,如果不加limit()进行限制的话,将无限生成下去。
      			.limit(20)   //这里限制生成20个
            .forEach(System.out::println); 
}
代码语言:javascript复制
public static void main(String[] args) {
    Stream
            .iterate(0, i -> i < 20, i -> i   1)  //快速生成一组0~19的int数据,中间可以添加一个断言,表示什么时候结束生成
            .forEach(System.out::println);
}

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

代码语言:javascript复制
public static void main(String[] args) {
    Stream
            .iterate(0, i -> i   1)
            .limit(20)
            .takeWhile(i -> i < 10)   //当i小于10时正常通过,一旦大于等于10直接截断
            .forEach(System.out::println);
}
代码语言:javascript复制
public static void main(String[] args) {
    Stream
            .iterate(0, i -> i   1)
            .limit(20)
            .dropWhile(i -> i < 10)   //和上面相反,上来就是截断状态,只有当满足条件时再开始通过
            .forEach(System.out::println);
}
其他变动

Try-with-resource语法可以直接将现有的变量丢进去:

代码语言:javascript复制
public static void main(String[] args) throws IOException {
    InputStream inputStream = Files.newInputStream(Paths.get("pom.xml"));
    try (inputStream) {   //单独丢进try中,效果是一样的
        for (int i = 0; i < 100; i  )
            System.out.print((char) inputStream.read());
    }
}

在Java 8及之前,匿名内部类是没办法使用钻石运算符进行自动类型推断的:

代码语言:javascript复制
public abstract class Test<T>{   //这里我们写一个泛型类
    public T t;
    public Test(T t) {
        this.t = t;
    }
    public abstract T test();
}
public static void main(String[] args) throws IOException {
    Test<String> test = new Test<>("AAA") {   //在低版本这样写是会直接报错的,因为匿名内部类不支持自动类型推断,但是很明显我们这里给的参数是String类型的,所以明明有机会进行类型推断,却还是要我们自己填类型,就很蠢
      //在Java 9之后,这样的写法终于可以编译通过了
        @Override
        public String test() {
            return t;
        }
    };
}

Java 10 新特性

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

var关键字必须位于有初始值设定的变量上

Java终究不像JS那样进行动态推断,这种类型推断仅仅发生在编译期间,到最后编译完成后还是会变成具体类型的

var关键字仅适用于局部变量,没办法在其他地方使用的

Java 11 新特性

Lambda的形参推断

在Java 10var关键字,不支持在lambda中使用,在Java 11支持了:

String类方法增强

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

代码语言:javascript复制
public static void main(String[] args) {
    var str = "ABnCnD";
    System.out.println(str.isBlank());    //isBlank方法用于判断是否字符串为空或者是仅包含空格
    str
            .lines()   //根据字符串中的n换行符进行切割,分为多个字符串,并转换为Stream进行操作
            .forEach(System.out::println);
     System.out.println(str.repeat(2));  //让字符串重复拼接
     str = " A B C D "; 
     System.out.println(str.strip());   //去除首尾空格
     System.out.println(str.stripLeading());  //去除首部空格
     System.out.println(str.stripTrailing());   //去除尾部空格
    
}
全新的HttpClient使用

新的API支持最新的HTTP2和WebSocket协议。

代码语言:javascript复制
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 新特性

新的switch语法

在Java 12引入全新的switch语法,让我们使用switch语句更加的灵活

比如编写一个根据成绩得到等级的方法:

代码语言:javascript复制
public static String grade(int score){
    score /= 10;  //既然分数段都是整数,那就直接整除10
    return switch (score) {   //增强版switch语法
        case 10, 9 -> "优秀";   //语法那是相当的简洁,而且也不需要我们自己考虑break或是return来结束switch了(有时候就容易忘记,这样的话就算忘记也没事了)
        case 8, 7 -> "良好"; 
        case 6 -> "及格";
        default -> "不及格";
    };
}

全新的switch语法称为switch表达式

代码语言:javascript复制
var res = switch (obj) {   //这里和之前的switch语句是一样的,但是注意这样的switch是有返回值的,所以可以被变量接收
    case [匹配值, ...] -> "优秀";   //case后直接添加匹配值,匹配值可以存在多个,需要使用逗号隔开,使用 -> 来返回如果匹配此case语句的结果
    case ...   //根据不同的分支,可以存在多个case
    default -> "不及格";   //注意,表达式要求必须涵盖所有的可能,所以是需要添加default的
};
代码语言:javascript复制
var res = switch (obj) {   //增强版switch语法
    case [匹配值, ...] -> "优秀";
    default -> {   //我们可以使用花括号来将整套逻辑括起来
        //... 我是其他要做的事情
        yield  "不及格";  //注意处理完成后需要返回最终结果,但是这样并不是使用return,而是yield关键字
    }
};
文本块

Java15中可以使用这样的三引号来表示字符串了,并且我们可以随意在里面使用特殊字符,包括双引号等:

新instanceof语法

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

代码语言:javascript复制
public class Student {
    private final String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Student student) {   //在比较完成的屁股后面,直接写变量名字,而这个变量就是类型转换之后的
            return student.name.equals(this.name);  //下面直接用,是不是贼方便
        }
        return false;
    }
}

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

记录类型

在实际开发中,很多的类仅仅只是充当一个实体类罢了,保存的是一些不可变数据

记录类型在Java 16才正式开放使用,记录类型本质上也是一个普通的类,不过是final类型且继承自java.lang.Record抽象类的,它会在编译时,会自动编译出 public get hashcodeequalstoString 等方法

代码语言:javascript复制
public record Account(String username, String password) {  //直接把字段写在括号中

}

Java 17 新特性

密封类型

在Java中,我们可以通过继承(extends关键字)来实现类的能力复用、扩展与增强。但有的时候,可能并不是所有的类我们都希望能够被继承。

而密封类的作用就是限制类的继承

密封类型有以下要求:

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

声明格式:

代码语言:javascript复制
public sealed [abstract] [class/interface] 类名 [extends 父类] [implements 接口, ...] permits [子类, ...]{
		//里面的该咋写咋写
}

子类格式为:

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

通过反射来获取类是否为密封类型:

代码语言:javascript复制
public static void main(String[] args) {
    Class<A> a = A.class;
    System.out.println(a.isSealed());   //是否为密封
}
承的子类必须显式标记为`final`、`sealed`或是`non-sealed`类型。

声明格式:

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

子类格式为:

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

通过反射来获取类是否为密封类型:

代码语言:javascript复制
public static void main(String[] args) {
    Class<A> a = A.class;
    System.out.println(a.isSealed());   //是否为密封
}

0 人点赞