Day18——JDK新特性

1.JDK8的新特性

1.1 Lambda表达式

1.1.1 举例

public class LambdaTest {
    @Test
    public void test1(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("test1");
            }
        };
        r1.run();

        //Lambda表达式的写法
        Runnable r2 = () ->{
            System.out.println("test2");
        };
        r2.run();
    }
}

1.1.2 格式

->:lambda操作符或箭头操作符
->的左边:lambda形参列表,对应着要重写的接口中的抽象方法的形参列表。
->的右边:lambda体,对应着接口的实现类要重写的方法的方法体。

    //1.无参且无返回值
    @Test
    public void test2(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("test1");
            }
        };
        //Lambda表达式的写法
        Runnable r2 = () ->{
            System.out.println("test2");
        };
    }
    //2.需要一个参数但无返回值
    @Test
    public void test3(){
        Consumer<String> con = (String s) ->{
            System.out.println(s);
        };
    }
    //3.数据类型可以省略,因为可由编译器通过类型推断得出
    @Test
    public void test4(){
        Consumer<String> con = (s) ->{
            System.out.println(s);
        };
    }
    //4.lambda若只需要一个参数,参数的小括号可以省略
    @Test
    public void test5(){
        Consumer<String> con = s ->{
            System.out.println(s);
        };
    }
    //5.lambda需要两个或以上的参数,多条执行语句并且具有返回值
    @Test
    public void test6(){
        Comparator<Integer> com = (o1,o2) ->{
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
    }
    //6.当lambda体只有一条语句时,return与大括号若有,都可以省略
    @Test
    public void test7(){
        Comparator<Integer> com = (o1,o2) -> o1.compareTo(o2);
    }

1.1.3 lambda表达式的本质

  1. lambda表达式作为接口的实现类的对象。
  2. lambda表达式是一个匿名函数。

1.2 函数式(Functional)接口

1.2.1 介绍

如果接口中只声明有一个抽象方法,则此接口就称为函数式接口。
因为只有给函数式接口提供实现类的对象时,我们才可以使用lambda表达式。

1.2.2 位置

JDK8中声明的函数式接口都在java.util.function包下。

1.2.3 4个基本的函数式接口

  1. 消费型接口:Consumer 对应的抽象方法:void accept(T t)。
  2. 供给型接口:Supplier 对应的抽象方法:T get()。
  3. 函数型接口:Function 对应的抽象方法:R apply(T t)。
  4. 判断型接口:Predicate 对应的抽象方法:boolean test(T t)。

1.3 方法引用

Integer :: compare;

1.3.1 理解

  1. 可以看做是基于lambda表达式的进一步刻画。
  2. 当需要提供一个函数式接口的实例时,我们可以使用lambda表达式提供此实例。
    • 当满足一定的条件的情况下,我们还可以使用方法引用或构造器引用替换lambda表达式。

1.3.2 本质

  1. 方法引用作为了函数式接口的实例。

1.3.3 格式

类(或对象) :: 方法名

1.3.4 说明

  1. 对象 :: 实例方法
    • 要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的形参列表和返回值类型相同或一致。
    • 注意:方法b必须是非静态方法,需要对象调用。
    //方法引用1 对象 :: 实例方法
    @Test
    public void test1() {
        PrintStream ps = System.out;
        Consumer<String> con = ps::println;
        Supplier<String> sup = emp::getName;
    }
  1. 类 :: 静态方法
    • 要求:函数式接口中的抽象方法a与其内部实现时调用的类的某个静态方法b的形参列表和返回值类型相同或一致。此时,可以考虑使用方法b实现对方法a的替换、覆盖,即方法引用。
    • 注意:方法b必须是静态方法,需要类调用。
    //方法引用2 类 :: 静态方法
    @Test
    public void test2() {
        Comparator<Integer> com = Integer::compare;
        Function<Double,Long> fun = Math::round;
    }
  1. 类 :: 实例方法
    • 要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型。同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第一个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的n-1个参数的类型相同或一致。此时,可以考虑使用方法b实现对方法a的替换、覆盖,即方法引用。
    • 注意:方法b必须是非静态方法,需要对象调用。但形式上写成对象a所属的类。
    //方法引用3 类 :: 实例方法
    @Test
    public void test4() {
        Comparator<String> com = String::compareTo;
        BiPredicate<String,String> bp = String::equals;
    }

1.4 构造器引用

1.4.1 格式

类名 :: new

    @Test
    public void test1(){
        //调用的是Employee类中的空参构造器
        Supplier<Employee> sup = Employee::new;
        //调用的是Employee类中参数是Integer/int的构造器
        Function<Integer,Employee> fun = Employee::new;
        //调用的是Employee类中参数是Integer/int,String的构造器
        BiFunction<Integer,String,Employee> bfun = Employee::new;
    }

1.4.2 说明

  1. 调用了类名对应的类中的某一个确定的构造器。
  2. 具体调用类中哪一个构造器取决于函数式接口的抽象方法的形参列表。

1.5 数组引用

1.5.1 格式

数组名[] :: new

    @Test
    public void test2(){
        Function<Integer,Employee[]> fun = Employee[]::new;
    }

1.6 StreamAPI

1.6.1 介绍

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式

1.6.2 使用说明

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
  3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。
  4. Stream 一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。

1.6.3 执行流程

  1. Stream的实例化
    • 通过集合实例化
    • 通过数组实例化
    • 通过Stream的of()方法实例化
    //方式1:通过集合
    @Test
    public void test1(){
        List<Employee> list = EmployeeData.getEmployees();
        //返回一个顺序流
        Stream<Employee> stream = list.stream();
        //返回一个并行流
        Stream<Employee> stream1 = list.parallelStream();
    }

    //方式2:通过数组
    @Test
    public void test2(){
        //返回一个流
        Integer[] arr = new Integer[]{1,2,3};
        Stream<Integer> stream = Arrays.stream(arr);
        int[] arr1 = new int[]{1,2,3};
        IntStream stream1 = Arrays.stream(arr1);
    }

    //方式3:通过Stream的of()
    @Test
    public void test3(){
        Stream<String> stream = Stream.of("AA", "BB");
    }
  1. 一系列的中间操作
    • 筛选与切片
    • 映射
    • 排序
    //1-筛选与切片
    @Test
    public void test1(){
        //filter(Predicate p)-接收Lambda,从流中排除某些元素
        //练习:查询员工表中薪资大于7000的员工信息
        List<Employee> list = EmployeeData.getEmployees();
        Stream<Employee> stream = list.stream();
        stream.filter(employee -> emp.getSalary() > 7000).forEach(System.out::println);

        //limit(n)-使其元素不超过指定的数量
        Stream<Employee> stream1 = list.stream();
        stream1.limit(5).forEach(System.out::println);

        //skip(n)-跳过元素,返回一个去掉前n个元素的流,若流中元素不足n个,返回一个空流
        Stream<Employee> stream2 = list.stream();
        stream2.skip(5).forEach(System.out::println);

        //distinct()-筛选元素,通过流所生成元素的hashCode()和equals()去除重复元素
        Stream<Employee> stream3 = list.stream();
        stream3.distinct().forEach(System.out::println);
    }

    //2-映射
    @Test
    public void test2(){
        //map(Function f)-接收一个函数作为参数,将元素转换为其他形式或提取信息,该函数会被应用到每一个元素上
        //练习:转换为大写
        List<String> list = Arrays.asList("aa", "bb", "cc");
        Stream<String> stream = list.stream();
        //方式1
        stream.map(str -> str.toUpperCase()).forEach(System.out::println);
        //方式2
        stream.map(String :: toUpperCase()).forEach(System.out::println);
    }

    //3-排序
    @Test
    public void test3(){
        //sorted()-自然排序
        Integer[] arr = new Integer[]{213,432,5435,6456};
        Arrays.stream(arr).sorted().forEach(System.out::println);
        //sorted(Comparator com)-定制排序
        List<Employee> list = EmployeeData.getEmployees();
        list.stream().sorted((e1,e2)->e1.getAge() - e2.getAge()).forEach(System.out::println);
    }
  1. 执行终止操作
    • 匹配与查找
    • 归约
    • 收集
    //1-匹配与查找
    @Test
    public void test1(){
        //allMatch(Predicate p)-检查是否匹配所有元素
        //练习:是否所有员工的年龄都大于18
        List<Employee> list = EmployeeData.getEmployees();
        System.out.println(list.stream().allMatch(emp -> emp.getAge() > 18));

        //anyMatch(Predicate p)-检查是否至少匹配一个元素
        //练习:是否存在员工的工资大于10000
        System.out.println(list.stream().anyMatch(emp->emp.getSalary()>10000));

        //findFirst-返回第一个元素
        System.out.println(list.stream().findFirst());

        //count-返回流中元素的总个数
        List<Employee> list = EmployeeData.getEmployees();
        System.out.println(list.stream().count());

        //max(Comparator c)/min(Comparator c)-返回流中的最大值/最小值
        //练习:返回最高的工资
        System.out.println(list.stream().map(emp -> emp.getSalary()).max((salary1, salary2) -> Double.compare(salary1, salary2)).get());

        //forEach(Consumer c)-内部迭代
        list.stream().forEach(System.out::println)
    }

    //2-归约
    @Test
    public void test2(){
        //reduce(T identity,BinaryOperator)-可以将流中的元素反复结合起来,得到一个值,返回T。
        //练习:计算1-10自然数的和
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(list.stream().reduce(0, (x1, x2) -> x1 + x2));
        //reduce(BinaryOperator)-可以将流中的元素反复结合起来,得到一个值。返回Optional
        //练习:计算公司所有员工工资的总和
        List<Employee> list = EmployeeData.getEmployees();
        System.out.println(list.stream().map(emp -> emp.getSalary()).reduce(Double::sum));
    }

    //3-收集
    @Test
    public void test3(){
        //collect(Collector c)-将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
        //练习:查找工资大于6000的员工,结果返回为一个list或set
        List<Employee> list = EmployeeData.getEmployees();
        List<Boolean> list1 = list.stream().map(emp -> emp.getSalary() > 6000).collect(Collectors.toList());
        list1.forEach(System.out::println);
    }

2.JDK8之后的新特性

2.1 jShell命令

JDK9 的新特性。jShell。以交互式的方式对语句和表达
式进行求值。即写即得、快速运行。

2.2 异常处理之try-catch资源关闭

2.2.1 JDK7的新特性

在 try 的后面可以增加一个(),在括号中可以声明流对象并初化。try 中的代码执行完毕,会自动把流对象释放,就不用写 finally了。

try(资源对象的声明和初始化){
 业务逻辑代码,可能会产生异常
}catch(异常类型 1 e){
 处理异常代码
}catch(异常类型 2 e){
 处理异常代码
}
    //JDK7
    @Test
    public void test1() {
        try (FileWriter fw = new FileWriter("hello.txt"); BufferedWriter bw = new BufferedWriter(fw);) {
            bw.write("hello");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.2.2 JDK9的新特性

try 的前面可以定义流对象,try 后面的()中可以直接引用流对象的名称。在 try代码执行完毕后,流对象也可以释放掉,也不用写 finally 了。

A a = new A();
B b = new B();
try(a;b){
 可能产生的异常代码
}catch(异常类名 变量名){
 异常处理的逻辑
}
    //JDK9
    @Test
    public void test2() {
        InputStreamReader reader = new InputStreamReader(System.in);
        OutputStreamWriter writer = new OutputStreamWriter(System.out);
        try (reader; writer) {

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.3 局部变量类型推断

JDK10的新特性。局部变量的显示类型声明,常常被认为是不必须的,给一个好听的名字反而可以很清楚的表达出下面应该怎样继续。本新特性允许开发人员省略通常不必要的局部变量类型声明,以增强 Java 语言的体验性、可读性。

//1.局部变量的实例化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
//2.增强 for 循环中的索引
for (var v : list) {
 System.out.println(v);
}
//3.传统 for 循环中
for (var i = 0; i < 100; i++) {
 System.out.println(i);
}
//4. 返回值类型含复杂泛型结构
var iterator = set.iterator();
//Iterator> iterator = set.iterator();

不适用场景:

  • 声明一个成员变量:var i;
  • 声明一个数组变量,并为数组静态初始化(省略 new 的情况下):var arr = {1,2,3};
  • 方法的返回值类型
  • 方法的参数类型
  • 没有初始化的方法内的局部变量声明
  • 作为 catch 块中异常类型
  • Lambda 表达式中函数式接口的类型
  • 方法引用中函数式接口的类型

2.4 instanceof模式匹配

JDK14中的预览特性,在JDK15中未进行修改,在JDK16中正式使用。instanceof 模式匹配通过提供更为简便的语法,来提高生产力。有了该功能,可以减少 Java 程序中显式强制转换的数量,实现更精确、简洁的类型安全的代码。

    @Test
    public void test1() {
        Object obj = new String("hello");
        if (obj instanceof String str) {
            System.out.println(str.contains("Java"));
        } else {
            System.out.println("error");
        }
    }
}

class Computer {
    private String model;
    private double price;
    
    @Override
    public boolean equals(Object obj) {
        return obj instanceof Computer other &&this.model.equals(other.model) && this.price == other.price;
    }
}

2.5 Switch表达式

2.5.1 JDK12-14中Switch表达式

  1. JDK12中:
    • Java 12 将会对 switch 声明语句进行扩展,使用 case L ->来替代以前的 break;,省去了 break 语句,避免了因少写 break 而出错。
    • 同时将多个 case 合并到一行,显得简洁、清晰,也更加优雅的表达逻辑分支。
    • 为了保持兼容性,case 条件语句中依然可以使用字符: ,但是同一个 switch 结构里不能混用-> 和: ,否则编译错误。
public class SwitchTest2 {
 public static void main(String[] args) {
 Fruit fruit = Fruit.GRAPE;
 int numberOfLetters = switch(fruit){
 case PEAR -> 4;
 case APPLE,MANGO,GRAPE -> 5;
 case ORANGE,PAPAYA -> 6;
 default -> throw new IllegalStateException("No Such Fruit:
" + fruit);
 };
 System.out.println(numberOfLetters);
 }
}
  1. JDK13中:JDK13 中引入了 yield 语句,用于返回值。这意味着,switch 表达式(返回值)应该使用 yield,switch 语句(不返回值)应该使用 break。yield 和 return 的区别在于:return 会直接跳出当前循环或者方法,而 yield 只会跳出当前 switch 块。
@Test
public void testSwitch3() {
 String x = "3";
 int i = switch (x) {
 case "1":
 yield 1;
 case "2":
 yield 2;
 default:
 yield 3;
 };
 System.out.println(i);
}

2.5.2 JDK17中Switch的模式匹配

    static String formatter(Object o) {
        String formatted = switch (o) {
            case Integer i:
                yield "int" + i;
            case Long l:
                yield "long" + l;
            case Double d:
                yield "double" + d;
            default:
                yield o.toString();
        };
        return formatted;
    }

2.6 文本块的使用

在 Java 中,通常需要使用 String 类型表达 HTML,XML,SQL 或 JSON 等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

2.6.1 JDK13的新特性

使用"""作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。因此,文本块将提高 Java 程序的可读性和可写性。

String text2 = """
 The Sound of silence
 Hello darkness, my old friend
 I've come to talk with you again
 Because a vision softly creeping
 Left its seeds while I was sleeping
 And the vision that was planted in my brain
 Still remains
 Within the sound of silence
 """;
System.out.println(text2);

2.6.2 JDK14的新特性

JDK14 的版本主要增加了两个 escape sequences,分别是

  1. \ :取消换行操作
  2. \s:表示一个空格
String sql2 = """
 SELECT id,NAME,email \
 FROM customers\s\
 WHERE id > 4 \
 ORDER BY email DESC
 """;
 System.out.println(sql2);

2.7 record

record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是final 修饰,它会自动编译出 public get 、hashcode 、equals、toString、构造器等结构,减少了代码编写量。

public record Order1(int orderId,String orderName) {
}

用 record 声明一个类时,该类将自动拥有以下功能:

  1. 获取成员变量的简单方法,比如例题中的 name() 和 partner() 。注意区别于我们平常 getter()的写法。
  2. 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性。
  3. 重写 hashCode() 方法。
  4. 一个可以打印该类所有成员属性的 toString() 方法。
  5. 只有一个构造方法。

此外:

  • 还可以在 record 声明的类中定义静态字段、静态方法、构造器或实例方法。
  • 不能在 record 声明的类中定义实例字段;类不能声明为 abstract;不能声明显式的父类等。

2.8 密封类

在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
具体使用:
- 使用修饰符 sealed,可以将一个类声明为密封类。密封的类使用保留关键字permits 列出可以直接扩展(即 extends)它的类。
- sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 final、sealed、non-sealed 三者之一。

3.API的变化

3.1 Optional类

JDK8的新特性。
到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。以前,为了解决空指针异常,Google 在著名的 Guava 项目引入了 Optional类,通过检查空值的方式避免空指针异常。受到 Google 的启发,Optional 类已经成为 Java 8 类库的一部分。

3.1.1 引入的原因

为了避免代码中出现空指针异常。

3.1.2 介绍

Optional 类(java.util.Optional) 是一个容器类,它可以保存类型 T 的值,代表这个值存在。或者仅仅保存 null,表示这个值不存在。如果值存在,则isPresent()方法会返回 true,调用 get()方法会返回该对象。

3.1.3 实例化

  1. static Optional empty() :用来创建一个空的 Optional 实例。
  2. static Optional of(T value) :用来创建一个 Optional 实例,value 必须非空。
  3. static Optional ofNullable(T value) :用来创建一个Optional 实例,value 可能是空,也可能非空。

3.1.3 常用方法

  1. 判断 Optional 容器中是否包含对象:
    • boolean isPresent() : 判断 Optional 容器中的值是否存在。
    • void ifPresent(Consumer consumer) :判断 Optional 容器中的值是否存在,如果存在,就对它进行Consumer 指定的操作,如果不存在就不做。
  2. 获取 Optional 容器的对象:
    • T get(): 如果调用对象包含值,返回该值。否则抛异常。T get()与 of(T value)配合使用。
    • T orElse(T other):orElse(T other) 与 ofNullable(T value)配合使用,如果Optional 容器中非空,就返回所包装值,如果为空,就用 orElse(T other)other 指定的默认值(备胎)代替。
    • T orElseGet(Supplier other) :如果 Optional 容器中非空,就返回所包装值,如果为空,就用 Supplier 接口的 Lambda 表达式提供的值代替。
    • T orElseThrow(Supplier exceptionSupplier) :如果 Optional 容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException。
    @Test
    public void test1(){
        String str = "hello";
        str = null;

        //使用Optional避免空指针异常
        Optional<String> optional = Optional.ofNullable(str);
        String other = "你好";
        String finalStr = optional.orElse(other);
        System.out.println(finalStr.toString());
    }

你可能感兴趣的:(Java,SE,java,开发语言)