Java 14: instanceof的模式匹配(预览)

之前的instanceof使用

在Java 14对instanceof进行了一些改动.这些改动目前是预览的(后期可能会有变化).

在Java 14之前,我们通常是这样使用instanceof的:


1
2
3
4
5
6
7
static void show(Object obj){
  User user=null;
  if(obj instanceof User){
    user=(User)obj;
    System.out.println(user.name());
  }
}

会发现在instanceof判断obj是User的实例之后,还需要进行一次强制转换并且还需要提前定义一个变量(user)接收强制转换后的结果,才能使用.显得多余且繁琐.这次的改进就是为了优化instanceof的使用而来.

java标志
image-3141

Java 14中对instanceof的改进(预览)

基本改进

先来一段代码:


1
2
3
4
5
static void show1(Object obj){
        if(obj instanceof User user){
            System.out.println(user.name());
        }
}

 

可以和上面对比一下改进,发现至少有两点改进:

  • 1. 不用提前定义局部变量user;
  • 2. 局部变量名可直接写在类型后面,也就是user,在判断为true时,可直接使用user.

模式变量的作用域

模式变量存在作用域限制: 它只能在if中使用,而不能在else if/else中使用.否则会提示编译错误.

如下:


1
2
3
4
5
6
7
8
9
static void show1(Object obj){
    if(obj instanceof User user){
        System.out.println(user.name());
    }else if(obj instanceof String){
        // System.out.println(user.name()); error
    }else{
        // System.out.println(user.name()); error
    }
}

 

当然你可以这样用:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
    private static User user=new User("aa",12);
static void show1(Object obj){

    if(obj instanceof User user){
        // 调用instanceof模式匹配变量.
        System.out.println(user.name());
    }else if(obj instanceof String){
        // 调用的静态变量user.
        System.out.println(user.name());
    }else{
        // 调用的静态变量user.
        System.out.println(user.name());
    }
}

因此,记住只在if中使用instanceof模式变量.

instanceof模式变量简化if表达式

可以使用instanceof模式变量来简化if表达式:

先看原始的:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
    String name;
    int age;

    @Override
    public boolean equals(Object o) {
        if (o instanceof Person) {
            Person other = (Person) o;
            if (name.equals(other.name) && age == other.age) {
                return true;
            }
        }
        return false;
    }
}

简化第一步,引入模式变量:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public class Person {
        String name;
        int age;

        @Override
        public boolean equals(Object o) {
            /// 将局部变量other提入条件表达式.
            if (o instanceof Person other) {
                /// 移除此行. Person other = (Person) o;
                if (name.equals(other.name) && age == other.age) {
                    return true;
                }
            }
            return false;
        }
    }

第二步,将第二个if与第一个if合并:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
        String name;
        int age;

        @Override
        public boolean equals(Object o) {
            /// 将局部变量other提入条件表达式.
            if (o instanceof Person other && name.equals(other.name) && age == other.age) {
                /// 移除此行. Person other = (Person) o;
                /// 与上层if合并. if (name.equals(other.name) && age == other.age) {
                    return true;
                /// 与上层if合并 }
            }
            return false;
        }
    }

第三步,终极优化,直接返回if中的条件判断:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {
        String name;
        int age;

        @Override
        public boolean equals(Object o) {
            return o instanceof Person other && name.equals(other.name) && age == other.age;
            /// 将局部变量other提入条件表达式.
            /// if (o instanceof Person other && name.equals(other.name) && age == other.age) {
                /// 移除此行. Person other = (Person) o;
                /// 与上层if合并. if (name.equals(other.name) && age == other.age) {
                /// return true;
                /// 与上层if合并 }
            /// }
            /// return false;
        }
}

仅需一行,就可以直接返回equals的结果.

完整代码

完整代码如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

record User(String name, int age) implements Serializable {
}

public class Main {



    public class Person {
        String name;
        int age;

        @Override
        public boolean equals(Object o) {
            return o instanceof Person other && name.equals(other.name) && age == other.age;
            /// 将局部变量other提入条件表达式.
            /// if (o instanceof Person other && name.equals(other.name) && age == other.age) {
                /// 移除此行. Person other = (Person) o;
                /// 与上层if合并. if (name.equals(other.name) && age == other.age) {
                /// return true;
                /// 与上层if合并 }
            /// }
            /// return false;
        }
    }



    static void show(Object obj){
        User user=null;
        if(obj instanceof User){
            user=(User)obj;
            System.out.println(user.name());
        }
    }

    private static User user=new User("aa",12);
    static void show1(Object obj){
        if(obj instanceof User user){
            System.out.println(user.name());
        }else if(obj instanceof String){
            System.out.println(user.name());
        }else{
            System.out.println(user.name());
        }
    }

    public static void main(String[] args) {
        var user = new User("测试用户", 20);
        show1(user);
        show1("str");
        show1(new StringBuffer("StringBuffer"));
    }
}

Java 14:Switch表达式

Java 14相关

java标志
image-3128

成为标准功能

温馨提示: 目前仅IDEA 2020.1 EAP及以上版本支持Java 14中所有新增功能.因此请使用最新版本(目前链接到2020.1 EAP版本,发布正式版之后,可在稳定版中下载.)!~

Switch表达式是Java 12加入的,在Java 13成为预览版,在Java 14成为标准版.也就是正式功能.

同时,Switch表达式在Java 14中并未增加任何新特性.因此关于Switch的变更历史.可以参考: Java 13: Switch表达式Java 12预览:Switch表达式.

Java 14:文本块(第二预览)

Java 14相关

关于文本块

温馨提示: 目前仅IDEA 2020.1 EAP及以上版本支持Java 14中所有新增功能.因此请使用最新版本(目前链接到2020.1 EAP版本,发布正式版之后,可在稳定版中下载.)!~

关于文本块的介绍,请查看这篇文章.

Java 14 在Java 13文本块的基础上,增加了两个转义序列: \ 和 \s .

新增加的转义序列

  • \ : 用来放在行尾禁止插入换行符;
  • \s : 可以用来插入一个空格

java标志
image-3122

普通文本块

例子:


1
2
3
4
5
6
7
var code = """
public void print($type o){
System.out.println(Objects.toString(o));
}
""".replace("$type", "abc");

// 输出: "public void print(abc o){\n System.out.println(Objects.toString(o));\n}\n"

 

会看到输出中包含了换行符 \n .

去掉换行符


1
2
3
4
5
6
var code = """
public void print($type o){\
System.out.println(Objects.toString(o));\
}\
""".replace("$type", "abc");
// 输出 "public void print(abc o){ System.out.println(Objects.toString(o));}"

 

会发现换行符已经被去掉了.另外,文本块结尾也没有任何空格了.

增加空格


1
2
3
4
5
6
var code = """
public void print($type o){\
Sy\stem.out.println(Objects.toString(o));\
}\
""".replace("$type", "abc");
// 输出: "public void print(abc o){ Sy tem.out.println(Objects.toString(o));}"

 

输出中,去掉了换行符.但是在System的Sy后面增加了一个空格(使用\s).可以看到输出里面明显增加了一个空格.

Java14: Record(预览)

Java 14相关

简述

温馨提示: 目前仅IDEA 2020.1 EAP及以上版本支持Java 14中所有新增功能.因此请使用最新版本(目前链接到2020.1 EAP版本,发布正式版之后,可在稳定版中下载.)!~

Java 14增加了一个Record的预览功能.预览:在后续JDK版本中可能有变化,也可能成为永久功能.

Record和enum枚举类似,只是Record是半不可变类.在很多地方也可以将Record当做class一样使用.

特点简述:

  1. record没有set,只能通过构造函数初始化;
  2. record编译后会生成一个最终类(final class修饰),并且无法继承任何类(因为生成类的时候,系统会自动继承java.lang.Record类),但是可以实现接口;
  3. 系统会自动生成: toString(),hashCode(),equals(),属性名的方法(比如属性是name,则会生成name()方法,生成的方法不带get),不会生成set方法.

java标志
image-3035

定义一个简单的Record

定义一个User的record:


1
record User(String name, int age){ }

中间可以不写任何内容.

调用Record

可以像调用类一样,调用Record,如下:


1
2
3
4
5
6
7
var dur=new User("tea2",20);
var acs=new User("tea2",20);
System.out.println(dur.name());
System.out.println(dur.age());
System.out.println(dur.equals(acs));
System.out.println(acs.age());
System.out.println(acs.name());

 

这里的dur和acs只能通过构造函数初始化属性.系统会自动生成equals,可以直接比较两个record是否一致.

在Record中添加字段

在Record中,可以添加静态字段或者静态实例,静态方法:


1
2
3
4
5
6
7
8
9
record User(String name, int age){
    public User {
        callNumber++;
    }
    private static int callNumber;
    static int getCallNumber(){
        return callNumber;
    }
}

 

调用:


1
2
3
var dur=new User("tea2",20);
var acs=new User("tea2",20);
System.out.println(User.getCallNumber());

 

Record的构造函数

构造函数可以写成两种方式.同时在构造函数中,进行各种验证操作.

在IDEA中可以通过快捷方式进行转换.如下图:

Java 14 Record简化构造函数转换为传统构造函数:

image-3036
Java 14 Record简化构造函数转换为传统构造函数

Java 14 Record传统构造函数转换为简化构造函数:

image-3037
Java 14 Record传统构造函数转换为简化构造函数

传统构造函数


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
record User(String name, int age){
    public User(String name, int age) {
        // 进行各种验证操作.
        if(age<20){
            throw new IllegalArgumentException("age小于20.");
        }
        callNumber++;
        this.name = name;
        this.age = age;
    }

    private static int callNumber;
    static int getCallNumber(){
        return callNumber;
    }
}

 

简化构造函数


1
2
3
4
5
6
7
8
9
10
11
12
13
record User(String name, int age){
    public User {
        // 进行验证操作.
        if(age<20){
            throw new IllegalArgumentException("age小于20.");
        }
        callNumber++;
    }
    private static int callNumber;
    static int getCallNumber(){
        return callNumber;
    }
}

 

在Record中使用泛型

可以这样定义泛型:


1
2
record MobilePhone(T phone,double price){
}

 

然后定义一个Record,定义一个普通类:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
record HuaWei(){
    private static final String name="Huawei";
    private static final String model="mate 20 pro";

    public String name(){
        return name;
    }
    public String model(){
        return model;
    }
}

class  XiaoMi{
    private static final String name="Xiaomi";
    private static final String model="9";

    public String name(){
        return name;
    }
    public String model(){
        return model;
    }
}

 

调用:


1
2
3
4
5
6
var huaWeiMobilePhone=new MobilePhone(new HuaWei(),6000);
var xiaomiMobilePhone=new MobilePhone(new XiaoMi(),6100);
System.out.println(huaWeiMobilePhone.phone().name());
System.out.println(xiaomiMobilePhone.phone().name());
System.out.println(huaWeiMobilePhone.toString());
System.out.println(xiaomiMobilePhone.toString());

 

输出结果:

Huawei
Xiaomi
MobilePhone[phone=HuaWei[], price=6000.0]
MobilePhone[phone=cn.bckf.java14demo.XiaoMi@25f38edc, price=6100.0]

 

可以看到,普通类和Record只在toString()的实现上有些差别.

Record文件读写

定义Record


1
2
record User(String name, int age) implements Serializable {
}

 

将Record写入文件


1
2
3
4
5
6
7
static void write(User user, String filePath) {
        try (var oos = new ObjectOutputStream(Files.newOutputStream(Paths.get(filePath)))) {
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }
}

 

从文件中读取Record


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static User read(String filePath) {
        User user = null;
        try (var oos = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)))) {
            // 此处使用了instanceof模式匹配.该特性为JDK 14新增加.
            if (oos.readObject() instanceof User user1) {
                user = user1;
            }

            /*
            上面的语句也可以写成:
            user= (User) oos.readObject();
            */
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return user;
}

 

读写文件完整代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

record User(String name, int age) implements Serializable {
}

public class Main {

    static void write(User user, String filePath) {
        try (var oos = new ObjectOutputStream(Files.newOutputStream(Paths.get(filePath)))) {
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static User read(String filePath) {
        User user = null;
        try (var oos = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)))) {
            // 此处使用了instanceof模式匹配.该特性为JDK 14新增加.
            if (oos.readObject() instanceof User user1) {
                user = user1;
            }

            /*
            上面的语句也可以写成:
            user= (User) oos.readObject();
            */
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return user;
    }

    public static void main(String[] args) {
        var user = new User("测试用户", 20);
        var filePath = "Java14Record";
        write(user, filePath);
        var readUser = read(filePath);
        System.out.println(readUser.toString());
    }
}

 

Record不是关键字

目前发布的版本中,record和var一样是受限制的标识符,并非关键字.所以record可以像下面这样使用:


1
2
var record=100;
void record(){}

 

但是不能用于类名使用,下面的代码会提示编译错误(‘record’是受限制的标识符,不能用于类型声明):


1
class record{}

 

编译后的Record

IDEA提供了一个反编译的功能,位置如下:

image-3038
编译后的Record.

编译之后的类:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package cn.bckf.java14demo;

final class User extends java.lang.Record implements java.io.Serializable {
private final java.lang.String name;
private final int age;

public User(java.lang.String name, int age) { /* compiled code */ }

public java.lang.String toString() { /* compiled code */ }

public final int hashCode() { /* compiled code */ }

public final boolean equals(java.lang.Object o) { /* compiled code */ }

public java.lang.String name() { /* compiled code */ }

public int age() { /* compiled code */ }
}

会发现,编译之后,自动生成了toString(),hashCode(),equals(),和属性的获取方法(没有get前缀),同时,属性被private final修饰.

好了,可以开始愉快的使用Record了.☺