Java新特性

简介: Java新特性

Java新特性


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

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


Java 8 关键特性


Lambda表达式

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

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


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

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


Lambda表达式的具体规范:

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


Runable类:

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


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

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

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

public static void main(String[] args) {
    Test test = Main::impl;    //使用 类名::方法名称 的形式来直接引用一个已有的方法作为实现
}
public static String impl(Integer i){
    return "我是已经存在的实现"+i;
}


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


Optional类

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

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

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


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

System.out.println(Optional.ofNullable(str).get());


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

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

System.out.println(Optional.ofNullable(str).orElse("VVV"));   //orElse表示如果为空就返回里面的内容


java9新增api:

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类:

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文件表示此项目采用模块管理机制:

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


a5991019f23b1f9d19ced68fe3a5bc80.png

进行模块导入:

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


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

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

d1c6f487d24bfa6ca22f626723e65905.png

模块机制四种类型:

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


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

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


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

依赖传递关键字:

module module.a {
    exports com.test to module.b;  
    requires transitive java.logging;   //使用transitive来向其他模块传递此依赖
}


为其他类开通反射权限:

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


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

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


声明我们提供了实现类:

package com.main;
import com.test.Test;
public class TestImpl implements Test {
}


module module.b {
    requires module.a;   //导入项目A的模块,此模块暴露了com.test包
    provides com.test.Test with com.main.TestImpl;  //声明此模块提供了Test的实现类
}

接口中的private方法

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

public interface Test {
    default void test(){
        System.out.println("我是test方法默认实现");
    }
}


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

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


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


集合类工厂方法

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

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,通过它大大方便了我们的编程:

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进一步的增强:

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


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

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); 
}


public static void main(String[] args) {
    Stream
            .iterate(0, i -> i < 20, i -> i + 1)  //快速生成一组0~19的int数据,中间可以添加一个断言,表示什么时候结束生成
            .forEach(System.out::println);
}

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

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);
}


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语法可以直接将现有的变量丢进去:

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及之前,匿名内部类是没办法使用钻石运算符进行自动类型推断的:

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 新特性

局部变量类型推断

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支持了:

5bfc63d4ba24b05170ac295531b0ec67.png

String类方法增强

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

public static void main(String[] args) {
    var str = "AB\nC\nD";
    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协议。

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语句更加的灵活

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

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表达式

var res = switch (obj) {   //这里和之前的switch语句是一样的,但是注意这样的switch是有返回值的,所以可以被变量接收
    case [匹配值, ...] -> "优秀";   //case后直接添加匹配值,匹配值可以存在多个,需要使用逗号隔开,使用 -> 来返回如果匹配此case语句的结果
    case ...   //根据不同的分支,可以存在多个case
    default -> "不及格";   //注意,表达式要求必须涵盖所有的可能,所以是需要添加default的
};


var res = switch (obj) {   //增强版switch语法
    case [匹配值, ...] -> "优秀";
    default -> {   //我们可以使用花括号来将整套逻辑括起来
        //... 我是其他要做的事情
        yield  "不及格";  //注意处理完成后需要返回最终结果,但是这样并不是使用return,而是yield关键字
    }
};

文本块

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

fab308cd49baa262ce30f86aa812cc5f.png

新instanceof语法

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

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 gethashcodeequalstoString 等方法

public record Account(String username, String password) {  //直接把字段写在括号中
}


Java 17 新特性


密封类型

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

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

密封类型有以下要求:

  • 可以基于普通类、抽象类、接口,也可以是继承自其他接抽象类的子类或是实现其他接口的类等。
  • 必须有子类继承,且不能是匿名内部类或是lambda的形式。
  • sealed写在原来final的位置,但是不能和finalnon-sealed关键字同时出现,只能选择其一。
  • 继承的子类必须显式标记为finalsealed或是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<A> a = A.class;
    System.out.println(a.isSealed());   //是否为密封
}
承的子类必须显式标记为`final`、`sealed`或是`non-sealed`类型。
声明格式:
~~~java
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<A> a = A.class;
    System.out.println(a.isSealed());   //是否为密封
}


相关文章
|
29天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
57 2
|
1月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
38 3
|
1月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
33 2
|
1月前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
31 3
|
1月前
|
安全 Java API
Java 17新特性让你的代码起飞!
【10月更文挑战第4天】自Java 8发布以来,Java语言经历了多次重大更新,每一次都引入了令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将带你从Java 8一路走到Java 17,探索那些能让你的代码起飞的关键特性。
80 1
|
1月前
|
编解码 Oracle Java
java9到java17的新特性学习--github新项目
本文宣布了一个名为"JavaLearnNote"的新GitHub项目,该项目旨在帮助Java开发者深入理解和掌握从Java 9到Java 17的每个版本的关键新特性,并通过实战演示、社区支持和持续更新来促进学习。
79 3
|
13天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
27 4
|
27天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
54 3
|
27天前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
24 2
|
1月前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
31 6
下一篇
无影云桌面