自学内容网 自学内容网

Java中的JDK8及后续的重要新特性

目录

Java中的JDK8及后续的重要新特性

函数式接口

介绍

JDK8及之后的常见函数式接口

Supplier接口

Consumer接口

Function接口

Predicate接口

函数式编程思想和Lambda表达式

基本介绍与使用

lambda接口结合函数式接口

结合Supplier接口

结合Consumer接口

结合Function接口

结合Predicate接口

Stream流

Stream流对象获取

Stream中的方法

forEach方法

count方法

filter方法

limit方法

skip方法

concat方法

Stream流对象转换为集合对象

dinstinct方法

转换流中的类型

Stream流小练习

方法引用

基本规则

使用对象名引用成员方法

使用类名引用静态方法

使用类构造引用

使用数组引用

Java中的JDK9-17新特性

接口的私有方法

钻石操作符与匿名内部类结合

try...catch升级

局部变量类型自动推断

switch表达式

Java12的switch表达式

Java13的switch表达式

文本块

Java13文本块

instanceof模式匹配

Record类

密封类


Java中的JDK8及后续的重要新特性

函数式接口

介绍

函数式接口:接口中有且仅有一个抽象方法,但是可以有多个其他的方法

可以使用注解:@FunctionalInterface检测某个接口是否是函数式接口

例如下面的代码:

@FunctionalInterface
public interface Person {
    // 只有一个抽象方法
    void eat();
    // 可以有多个默认方法
    default void run() {
        System.out.println("Person run");
    }
    // 可以有多个静态方法
    static void sleep() {
        System.out.println("Person sleep");
    }
}

JDK8及之后的常见函数式接口

Supplier接口

Supplier接口:表示java.util.function.Supplier<T>接口,该接口的作用是通过重写的方法获取指定内容

Supplier接口中的抽象方法:T get()

基本使用如下(在重写的get方法中获取数组最大值):

public class Test {
    public static void main(String[] args) {
        int[] data = {54, 645, 211, 478, 24, 11, 555, 789};
        int max = getMax(new Supplier<Integer>() {
            @Override
            public Integer get() {
                Arrays.sort(data);
                return data[data.length - 1];
            }
        });

        System.out.println(max);
    }

    public static int getMax(Supplier<Integer> integer) {
        return integer.get(); // get()方法获取Supplier中的数据
    }
}
Consumer接口

Consumer接口:表示java.util.function.Consumer<T>接口,该接口的作用是在重写的方法体内操作重写方法的参数

Consumer接口中的抽象方法:void accept(T t)

基本使用如下(获取参数字符串的长度):

public class Test01 {
    public static void main(String[] args) {
        getLength(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s.length());
            }
        });
    }

    public static void getLength(Consumer<String> consumer) {
        consumer.accept("Hello World");
    }
}
Function接口

Function接口:表示java.util.function.Function<T, R>接口,该接口作用是将一种类型的数据转换为另一种类型

Function接口中的抽象方法:R apply(T t),参数表示需要转换的参数,返回值类型表示转换后的参数类型

基本使用如下(将整数转换为字符串类型):

public class Test02 {
    public static void main(String[] args) {
        System.out.println(alter(new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return String.valueOf(integer) + 1;
            }
        }, 20));
    }

    public static String alter(Function<Integer, String> function, Integer integer) {
        return function.apply(integer);
    }
}
Predicate接口

Predicate接口:java.util.function.Predicate<T>接口,该接口的作用是在重写的方法体内判断

Predicate接口抽象方法:boolean test(T t)

基本使用如下(判断一个账号是否为手机号:1. 至少为11位 2. 第一位必须是1 3. 最后一位不能是0):

public class Test03 {
    public static void main(String[] args) {
        System.out.println(checkPhoneNumber(new Predicate<String>() {
            @Override
            public boolean test(String string) {
                return string.matches("[1]\\d{9}[1-9]");
            }
        }, "15964224230"));
    }

    public static boolean checkPhoneNumber(Predicate<String> predicate, String string) {
        return predicate.test(string);
    }
}

函数式编程思想和Lambda表达式

基本介绍与使用

面向对象思想与函数式编程思想:

  1. 面向对象思想:强调对象如何完成任务
  2. 函数式编程思想:强调是否完成任务

lambda表达式基本结构如下:

(参数列表) -> {
    // 方法体
}

其中:

  1. (参数列表):表示重写的方法对应的参数,此位置可以省略参数类型保留参数名,如果只有一个参数,则还可以省去参数列表的括号()
  2. ->:将参数列表传入到方法体
  3. {}:定义重写方法的方法体
    1. 如果方法体只有一句话,则可以去掉括号{}和尾部的分号;
    2. 如果方法体只有一个return语句,则可以省去return关键字

基本使用如下:

// 不使用lambda表达式排序自定义对象ArrayList
public class Test {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("张三", 18));
        people.add(new Person("李四", 20));
        people.add(new Person("王五", 22));
        people.add(new Person("赵六", 24));

        // 使用匿名内部类实现Comparator接口
        Collections.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

// 使用lambda表达式
public class Test {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("张三", 18));
        people.add(new Person("李四", 20));
        people.add(new Person("王五", 22));
        people.add(new Person("赵六", 24));

        // 使用lambda表达式
        // 省略参数类型,省略return关键字,省略大括号和分号
        Collections.sort(people, (o1, o2) -> o1.getAge() - o2.getAge());

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

lambda接口结合函数式接口

结合Supplier接口

找出数组的最大值:

public class Test01 {
    public static void main(String[] args) {
        System.out.println(getMax(() -> {
            int[] nums = {45, 64, 25, 12, 123, 42};
            Arrays.sort(nums);
            return nums[nums.length - 1];
        }));
    }

    public static int getMax(Supplier<Integer> integerSupplier) {
        return integerSupplier.get();
    }
}
结合Consumer接口

获取参数字符串的长度:

public class Test02 {
    public static void main(String[] args) {
        getLength(s -> System.out.println(s.length()), "shsudhbsnbxc");
    }

    public static void getLength(Consumer<String> consumer, String s) {
        consumer.accept(s);
    }
}
结合Function接口

将整数类型转换为字符串类型:

public class Test03 {
    public static void main(String[] args) {
        System.out.println(alter(integer -> integer + "" + 1, 123));
    }

    public static String alter(Function<Integer, String> function, Integer integer) {
        return function.apply(integer);
    }
}
结合Predicate接口

判断一个账号是否为手机号:1. 至少为11位 2. 第一位必须是1 3. 最后一位不能是0

public class Test04 {
    public static void main(String[] args) {
        System.out.println(isPhoneNumber(phone -> phone.matches("[1]\\d{9}[1-9]"), "1854125421"));
    }

    public static boolean isPhoneNumber(Predicate<String> string, String phone) {
        return string.test(phone);
    }
}

Stream

Stream流提供了一种新的编程思路:流式编程,这里的Stream流不是IO流

Stream流对象获取

Stream流对象有两种:

  1. 对于集合:使用Collection中的方法:Stream<E> stream()
  2. 对于数组来说:使用Stream接口中的静态方法:static <T> Stream<T> of(T... values)

基本使用如下:

public class Test {
    public static void main(String[] args) {
        // 对于集合
        ArrayList<Integer> integers = new ArrayList<>();
        Collections.addAll(integers, 1, 2, 3, 4);
        Stream<Integer> stream = integers.stream();

        // 对于数组
        Stream<String> stringStream = Stream.of("a", "b", "cd", "asd");
    }
}
注意, Stream流并没有重写 toString方法,所以直接打印对象名时 Stream对象的地址

Stream中的方法

forEach方法

forEach方法用于遍历流对象中的内容:void forEach(Consumer<? super T> action);,参数的函数式接口重写的方法中可以定义在遍历过程中的操作

遍历整型数组,并将数组中每一个元素加上对应下标:

public class Test01 {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5).forEach(data -> System.out.println(data+1));
    }
}

上面的代码等价于:

public class Test01 {
    public static void main(String[] args) {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        integerStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer.intValue() + 1);
                // 或者直接利用自动拆箱和装箱:System.out.println(integer + 1);
            }
        });
    }
}

需要注意,forEach方法是一个终结方法,一旦使用了forEach方法,就不可以再使用对应的流对象

count方法

count方法:long count(),该方法用于统计流对象中内容的个数

例如,统计一个集合中的元素:

public class Test02 {
    public static void main(String[] args) {
        ArrayList<Integer> integers = new ArrayList<>();
        Collections.addAll(integers, 1,2,3,4,5);
        System.out.println(integers.stream().count());
    }
}

需要注意,count方法是一个终结方法,一旦使用了count方法,就不可以再使用对应的流对象

filter方法

filter方法:Stream<T> filter(Predicate<? super T> predicate),该方法可以根据参数的函数式接口重写方法筛选流对象中的内容,该方法返回一个新的流对象

例如,找出字符串中以a开头并且以d结尾的字符串

public class Test03 {
    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<>();
        Collections.addAll(strings, "asd", "sdahihs", "asdiyd", "assdds");
        strings.stream().filter(
                string -> string.startsWith("a") && string.endsWith("d")).forEach(
                        string-> System.out.println(string));
    }
}
limit方法

limit方法:Stream<T> limit(long maxSize),该方法用于取出流对象中前maxSize个元素,该方法返回一个新的流对象

例如,取出数组中前3个元素:

public class Test04 {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5).limit(3).forEach(data -> System.out.println(data));
    }
}
skip方法

skip方法:Stream<T> skip(long n),该方法用于跳过前n个元素,该方法返回一个新的流对象

例如,跳过数组中的前3个元素:

public class Test04 {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5).skip(3).forEach(data -> System.out.println(data));
    }
}
concat方法

concat方法:static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b),该方法用于将两个流中的内容进行拼接存入到一个新流对象中,该方法返回一个新的流对象

例如,拼接两个数组的内容:

public class Test05 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream1 = Stream.of(6, 7, 8, 9);
        Stream.concat(stream, stream1).forEach(data -> System.out.println(data));
    }
}
Stream流对象转换为集合对象

使用Stream流中的接口方法collect()

使用时通过编译器的提示进行选择,一般选择 Collectos.toxxx()
public class Test06 {
    public static void main(String[] args) {
        for (Integer i : Stream.of(1, 2, 3, 4, 5).collect(Collectors.toList())) {
            System.out.println(i);
        }
    }
}
dinstinct方法

distinct方法:Stream<T> distinct(),将对应流对象中的内容去重,但是依赖流对象中内容对应的类重写hashCode方法和equals方法,该方法返回一个新的流对象

例如,对指定内容进行去重:

public class Test07 {
    public static void main(String[] args) {
        Stream.of(1,2,3,2,2,2,3,3,5).distinct().forEach(data -> System.out.println(data));
    }
}
转换流中的类型

使用Stream中的方法:Stream<R> map(Function<T,R> mapper),可以将T数据类型通过函数式接口的重写方法转换为R类型放到新流对象中,该方法返回一个新的流对象

例如,将整数类型转换为字符串类型:

public class Test08 {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5).map(
                data -> String.valueOf(data) + 1).forEach(
                        strings-> System.out.println(strings));
    }
}
Stream流小练习
  1. 第一个队伍只要名字为3个字的成员姓名
  2. 第一个队伍筛选之后只要前3个人
  3. 第二个队伍只要姓张的成员姓名
  4. 第二个队伍筛选之后不要前2个人
  5. 将两个队伍合并为一个队伍
  6. 打印整个队伍的姓名信息

示例代码:

public class Test09 {
    public static void main(String[] args) {
        ArrayList<String> team1 = new ArrayList<>();
        team1.add("迪丽热巴");
        team1.add("宋远桥");
        team1.add("苏星河");
        team1.add("老子");
        team1.add("庄子");
        team1.add("孙子");
        team1.add("洪七公");

        ArrayList<String> team2 = new ArrayList<>();
        team2.add("古力娜扎");
        team2.add("张无忌");
        team2.add("张三丰");
        team2.add("赵丽颖");
        team2.add("张二狗");
        team2.add("张天爱");
        team2.add("张三");

        // 第一个队伍
        Stream<String> teamFirst = team1.stream().filter(name -> name.length() == 3).limit(3);
        // 第二个队伍
        Stream<String> teamSecond = team2.stream().filter(name -> name.startsWith("张")).skip(2);

        // 合并
        Stream.concat(teamFirst, teamSecond).forEach(team -> System.out.println(team));
    }
}

方法引用

基本规则

方法引用:对需要使用的方法使用引用的方式指代

主要作用是简化lambda表达式

使用方法引用的要求:

  1. 被引用的方法要写在重写方法里面
  2. 被引用的方法从参数上,返回值上要和所在重写方法一致,而且引用的方法最好是操作重写方法的参数值的

更改为方法引用的步骤:

  1. 去掉重写方法的参数
  2. 去掉->
  3. 去掉被引用方法的参数和括号
  4. 将被引用方法的.改成::

例如下面的代码:

public class Test {
    public static void main(String[] args) {
        // 匿名内部类正常表示
        Stream.of(1,2,3,4,5).forEach(new Consumer<Integer>() {
                     @Override
                     public void accept(Integer integer) {
                         System.out.println(integer);
                     }
                 });
        // lambda表达式
        // Stream.of(1,2,3,4,5).forEach(integer -> System.out.println(integer));
        // 引用 println
        // 前提:
        // System.out.println(integer)为重写方法的方法体
        // 参数为String类型,没有返回值,与forEach中的函数式接口Consumer的方法accept返回值相同
        // println打印参数值,属于操作参数
        // 更改:
        Stream.of(1, 2, 3, 4, 5).forEach(System.out::println);
    }
}

使用对象名引用成员方法

格式:对象名::成员方法名

例如:

// 例1
public class Test01 {
    public static void main(String[] args) {
        method(() -> "    abc   ".trim());
        // 转化为
        method("    abc   "::trim);
    }

    public static void method(Supplier<String> supplier){
        String s = supplier.get();
        System.out.println("s = " + s);
    }
}

// 例2
public class Test02 {
    public static void main(String[] args) {
        System.out.println(isPhoneNumber(phone -> phone.equals("1848")));
        System.out.println(isPhoneNumber("18483"::equals));
    }

    public static boolean isPhoneNumber(Predicate<String> string) {
        return string.test("18483");
    }
}

使用类名引用静态方法

格式:类名::静态成员方法

例如:

public class Test03 {
    public static void main(String[] args) {
        // 常规lambda
        method(d -> showDouble(d), 2.35);
        // 简化
        method(Test03::showDouble, 2.35);
    }

    public static void showDouble(double d) {
        System.out.println(d);
    }

    public static void method(Consumer<Double> consumer, double d){
        consumer.accept(d);
    }
}

使用类构造引用

格式:构造方法名称::new

例如:

@ToString
public class Person {
    private String name;
    private Integer age;

    public Person (String name) {
        this.name = name;
        this.age = 18;
    }
}

public class Test04 {
    public static void main(String[] args) {
        // 常规lambda
        method(name -> new Person(name), "张三");
        // 简化
        method(Person::new, "张三");
    }
    public static void method(Function<String,Person> function, String name){
        Person person = function.apply(name);
        System.out.println(person);
    }
}

使用数组引用

格式:数组的数据类型[]::new

例如:

public class Test05 {
    public static void main(String[] args) {
        // 常规lambda
        create(data -> new int[data], 15);
        // 简化
        create(int[]::new, 15);
    }

    public static void create(Function<Integer, int[]> function, Integer data) {
        int[] arr = function.apply(data);
        System.out.println(arr.length);
    }
}

Java中的JDK9-17新特性

接口的私有方法

JDK8版本接口增加了两类成员:

  • 公共的默认方法
  • 公共的静态方法

JDK9版本接口又新增了一类成员:

  • 私有的方法
// 接口
public interface USB {
    private void open(){
        System.out.println("私有非静态方法");
    }

    private static void close(){
        System.out.println("私有静态方法");
    }

    //定义一个默认方法调用私有方法
    public default void methodDef(){
        open();
        close();
    }
}

// 实现类
public class UsbImpl implements USB{
}

// 测试类
public class Test01 {
    public static void main(String[] args) {
        new UsbImpl().methodDef();
    }
}

钻石操作符与匿名内部类结合

自Java 9之后我们将能够与匿名实现类共同使用钻石操作符<>,即匿名实现类也支持类型自动推断

// 不使用lambda表达式排序自定义对象ArrayList
public class Test {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("张三", 18));
        people.add(new Person("李四", 20));
        people.add(new Person("王五", 22));
        people.add(new Person("赵六", 24));

        // 使用匿名内部类实现Comparator接口
        Collections.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

// 使用lambda表达式
public class Test {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("张三", 18));
        people.add(new Person("李四", 20));
        people.add(new Person("王五", 22));
        people.add(new Person("赵六", 24));

        // 使用lambda表达式
        // 省略参数类型,省略return关键字,省略大括号和分号
        Collections.sort(people, (o1, o2) -> o1.getAge() - o2.getAge());

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

try...catch升级

之前提过JDK 1.7引入了trywith-resources的新特性,可以实现资源的自动关闭,此时要求:

  • 该资源必须实现java.io.Closeable接口
  • try子句中声明并初始化资源对象
  • 该资源对象必须是final
try(IO流对象1声明和初始化;IO流对象2声明和初始化){
    可能出现异常的代码
}catch(异常类型 对象名){
    异常处理方案
}

JDK1.9又对trywith-resources的语法升级了

  • 该资源必须实现java.io.Closeable接口
  • try子句中声明并初始化资源对象,也可以直接使用已初始化的资源对象
  • 该资源对象必须是final
IO流对象1声明和初始化;
IO流对象2声明和初始化;

try(IO流对象1;IO流对象2){
    可能出现异常的代码
}catch(异常类型 对象名){
    异常处理方案
}

例如:

public class Test03 {
    public static void main(String[] args) throws IOException {
        //method01();
        method02();
    }

    /**
     * jdk9开始
     * 为了减轻try的压力,可以将对象放到外面去new,然后将对象名,放到 try中
     * 而且依然能自动刷新和关流
     */
    private static void method02() throws IOException {
        FileWriter fw = new FileWriter("module24\\io.txt");
        try(fw){
            fw.write("你好");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * jdk8之前
     */
    private static void method01() {
        try(FileWriter fw = new FileWriter("module24\\io.txt")){
            fw.write("你好");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

局部变量类型自动推断

JDK10之前,定义局部变量都必须要明确数据的数据类型,但是到了JDK10,出现了一个最为重要的特性,就是局部变量类型推断,顾名思义,就是定义局部变量时,不用先确定具体的数据类型了,可以直接根据具体数据推断出所属的数据类型:

语法如下:

var 变量名 = 值;

例如:

public class Test04 {
    public static void main(String[] args) {
        var i = 10;
        System.out.println("i = " + i);

        var j = "helloworld";
        System.out.println("j = " + j);

        var arr = new int[]{1,2,3,4,5};
        for (var element : arr) {
            System.out.println(element);
        }
    }
}

switch表达式

switch表达式在Java 12中作为预览语言出现,在Java 13中进行了二次预览,得到了再次改进,最终在Java 14中确定下来。另外,在Java17中预览了switch模式匹配。

传统的switch语句在使用中有以下几个问题:

  1. 匹配是自上而下的,如果忘记写break,那么后面的case语句不论匹配与否都会执行
  2. 所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复
  3. 不能在一个case语句中写多个执行结果一致的条件,即每个case语句后只能写一个常量值
  4. 整个switch语句不能作为表达式返回值
Java12的switch表达式

Java 12对switch语句进行了扩展,将其作为增强版的switch语句或称为switch表达式,可以写出更加简化的代码。

  • 允许将多个case语句合并到一行,可以简洁、清晰也更加优雅地表达逻辑分支。
  • 可以使用-> 代替 :
    • ->写法默认省略break语句,避免了因少写break语句而出错的问题。
    • ->写法在标签右侧的代码段可以是表达式、代码块或 throw语句。
    • ->写法在标签右侧的代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个switch结构。
  • 同一个switch结构中不能混用:,否则会有编译错误。使用字符:,这时fall-through规则依然有效,即不能省略原有的break语句。的写法表示继续使用传统switch语法。

案例需求:

请使用switch-case结构实现根据月份输出对应季节名称。例如,3~5月是春季,6~8月是夏季,9~11月是秋季,12~2月是冬季。

Java12之前写法:

public void test1() {
    int month = 3;
    switch (month) {
        case 3:
        case 4:
        case 5:
            System.out.println("春季");
            break;
        case 6:
        case 7:
        case 8:
            System.out.println("夏季");
            break;
        case 9:
        case 10:
        case 11:
            System.out.println("秋季");
            break;
        case 12:
        case 1:
        case 2:
            System.out.println("冬季");
            break;
        default:
            System.out.println("月份输入有误!");
    }
}

Java12之后写法:

private static void method02() {
    int month = 5;
    switch (month) {
        case 12, 1, 2 -> System.out.println("冬季");
        case 3, 4, 5 -> System.out.println("春季");
        case 6, 7, 8 -> System.out.println("夏季");
        case 9, 10, 11 -> System.out.println("秋季");
        default -> System.out.println("有毛病呀,没有这个月份");

    }
}
Java13的switch表达式

Java 13提出了第二个switch表达式预览,引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield语句,switch语句(不返回值)应该使用break语句。

案例需求:判断季节

/**
 * jdk13之后
 */
private static void method04() {
    int month = 5;
    var seson = switch (month) {
        case 12, 1, 2 -> {
            yield "冬季";
        }
        case 3, 4, 5 -> {
            yield "春季";
        }
        case 6, 7, 8 -> {
           yield "夏季";
        }
        case 9, 10, 11 -> {
           yield "秋季";
        }
        default -> {
           yield "有毛病";
        }
    };
    System.out.println("seson = " + seson);
}

/**
 * jdk13之前想要拿到switch结果,需要定义一个变量,然后为其赋值
 */
private static void method03() {
    int month = 5;
    String season = "";
    switch (month) {
        case 12, 1, 2:
            season = "冬季";
            break;
        case 3, 4, 5:
            season = "春季";
            break;
        case 6, 7, 8:
            season = "夏季";
            break;
        case 9, 10, 11:
            season = "秋季";
            break;
        default:
            season = "有毛病";
            break;
    }
    System.out.println("season = " + season);
}

文本块

预览的新特性文本块在Java 15中被最终确定下来,Java 15之后就可以放心使用该文本块了。

Java13文本块

JDK 12引入了Raw String Literals特性,但在其发布之前就放弃了这个特性。这个JEP与引入多行字符串文字(文本块)在意义上是类似的。Java 13中引入了文本块(预览特性),这个新特性跟Kotlin中的文本块是类似的。

现实问题

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

文本块就是指多行字符串,例如一段格式化后的XML、JSON等。而有了文本块以后,用户不需要转义,Java能自动搞定。因此,文本块将提高Java程序的可读性和可写性

目标

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写Java程序
  • 增强Java程序中字符串的可读性

举例

会被自动转义,如有一段以下字符串:

<html>
  <body>
      <p>Hello, 尚硅谷</p>
  </body>
</html>

将其复制到Java的字符串中,会展示成以下内容:

"<html>\n" +
"    <body>\n" +
"        <p>Hello, 尚硅谷</p>\n" +
"    </body>\n" +
"</html>\n";

即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,就可以使用以下语法:

"""
<html>
  <body>
      <p>Hello, world</p>
  </body>
</html>
""";

使用"""作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。

文本块是Java中的一种新形式,它可以用来表示任何字符串,并且提供更多的表现力和更少的复杂性。

  1. 文本块由零个或多个字符组成,由开始和结束分隔符括起来。
    • 开始分隔符由三个双引号字符表示,后面可以跟零个或多个空格,最终以行终止符结束。
    • 文本块内容以开始分隔符的行终止符后的第一个字符开始。
    • 结束分隔符也由三个双引号字符表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。

以下示例代码是错误格式的文本块:

String err1 = """""";//开始分隔符后没有行终止符,六个双引号最中间必须换行

String err2 = """  """;//开始分隔符后没有行终止符,六个双引号最中间必须换行

如果要表示空字符串需要以下示例代码表示:

String emp1 = "";//推荐
String emp2 = """
   """;//第二种需要两行,更麻烦了
  1. 允许开发人员使用\n\f\r来进行字符串的垂直格式化,使用“\b\t进行水平格式化。如以下示例代码就是合法的:
String html = """
    <html>\n
      <body>\n
        <p>Hello, world</p>\n
      </body>\n
    </html>\n
    """;
  1. 在文本块中自由使用双引号是合法的:
String story = """
Elly said,"Maybe I was a bird in another life."

Noah said,"If you're a bird , I'm a bird."
 """;

instanceof模式匹配

instanceof的模式匹配在JDK14、15中预览,在JDK16中转正。有了它就不需要编写先通过instanceof判断再强制转换的代码

例如:

// 父类
public abstract class Animal {
    public abstract void eat();
}

// 子类
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗啃骨头");
    }

    //特有方法
    public void lookDoor(){
        System.out.println("狗会看门");
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        method(dog);
    }

    public static void method(Animal animal) {
        if(animal instanceof Dog dog) {
            dog.eat();
            dog.lookDoor();
        }
    }
}

Record

Record类在JDK14、15预览特性,在JDK16中转正。

Record是一种全新的类型,它本质上是一个 final类,同时所有的属性都是 final修饰,它会自动编译出gethashCode 、比较所有属性值的equalstoString 等方法,减少了代码编写量。使用 Record 可以更方便的创建一个常量类。

注意:

  1. Record只会有一个全参构造
  2. 重写的equals方法比较所有属性值
  3. 可以在Record声明的类中定义静态字段、静态方法或实例方法(非静态成员方法)
  4. 不能在Record声明的类中定义实例字段(非静态成员变量)
  5. 类不能声明为abstract
  6. 不能显式的声明父类,默认父类是java.lang.Record
  7. 因为Record类是一个 final类,所以也没有子类等。
public record Person(String name) {
    //int i;//不能声明实例变量

    static int i;//可以声明静态变量

    //不能声明空参构造
    /* public Person(){
    
        }*/

    //可以声明静态方法
    public static void method(){

    }

    //可以声明非静态方法
    public void method01(){

    }
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        Person person = new Person("张三");
        Person person1 = new Person("张三");
        System.out.println(person);

        System.out.println(person.equals(person1));
    }
}

密封类

其实很多语言中都有密封类的概念,在Java语言中,也早就有密封类的思想,就是final修饰的类,该类不允许被继承。而从JDK15开始,针对密封类进行了升级。

Java 15通过密封的类和接口来增强Java编程语言,这是新引入的预览功能并在Java 16中进行了二次预览,并在Java17最终确定下来。这个预览功能用于限制超类的使用,密封的类和接口限制其他可能继承或实现它们的其他类或接口。

格式:

【修饰符】 sealed class 密封类 【extends 父类】【implements 父接口】 permits 子类{
    
}
【修饰符】 sealed interface 接口 【extends 父接口们】 permits 实现类{
    
}
  1. 密封类用 sealed 修饰符来描述
  2. 使用 permits 关键字来指定可以继承或实现该类的类型有哪些
  3. 一个类继承密封类或实现密封接口,该类必须是sealednon-sealedfinal修饰的
  4. sealed修饰的类或接口必须有子类或实现类

例如:

public sealed class Animal permits Dog, Cat{
}

public non-sealed class Dog extends Animal{
}

public non-sealed class Cat extends Animal{
}

原文地址:https://blog.csdn.net/m0_73281594/article/details/142535286

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!