剖析 Java15 新语法特性

https://xie.infoq.cn/article/92ba88c7926b5f5c6fbc11830

剖析 Java15 新语法特性

前言

9月15日,Java社区正式发布了Java15的GA版本,这意味着大家欠Oracle的技术债开始变得越来越多。在之前的新时代背景下的Java语法特性一文中,我为大家详细介绍了Java9~14的语法全貌;而本文,我会紧接上一篇博文的内容,继续为大家剖析Java15带给我们的改变。

进一步强化的instanceof运算符

早期,如果我们需要在程序中对某个引用类型进行强制类型转换,通常情况下,为了避免在转换过程中出现类型不匹配的转换错误,我们往往会使用instanceof运算符验证目标对象是否是特定类型的一个实例,只有当表达式条件成立时,才允许进行强制类型转换,示例1-1:

if (obj instanceof String) {
    String temp = (String) obj;
} else {
    //...
}

上述程序示例中,整个转换过程总共需要经历2个步骤,首先需要使用instanceof关键字来匹配目标对象,当条件成立后,再使用一个新的局部变量来接收强转后的值。平心而论,这样的写法着实有点冗余,因此,从Java14开始,Java的设计者们开始对instanceof运算符进行了进一步的升级强化,为开发人员提供了模式匹配功能,以便于简化样板代码的编写。示例1-2:

if (obj instanceof String str) {    //TODO 变量str的作用域仅限if代码块} else {    //...}



很明显,使用instanceof的模式匹配功能后,弱化了原本代码样板化严重的问题。instanceof关键字的右边允许开发人员直接声明变量,当满足条件后,编译器会隐式强转并为其赋值。在上一篇博文中,我之所以没有为大家分享这项新特性,是因为它当时还仅仅只是个预览功能;尽管Java15它仍未转正,但从JEP提案中可以明确得知,这项语法特性在未来发生改动的可能性较小。

扩展限制支持

在谈扩展限制之前,我们首先回顾下Java访问修饰符是如何控制资源的访问权限的。当资源被声明为public时,意味着资源对所有类可见;当资源被声明为protected时,意味着仅同包类,或派生类可见;当资源被声明为default时,意味着仅同包类可见;而当资源被声明为private时,仅同类可见。



在某些情况下,如果我们希望所定义的超类具备扩展限制时,通常会采用如下2种方式:

  • 将超类定义为final;

  • 将超类的构造函数声明为default。



如果我们将超类定义为final后,那么任何类都不允许对其进行继承,也就是说,超类将不提供任何扩展支持,很明显,这样的做法显然无法有效支撑某些特定场景(比如:我们希望超类仅允许被固定的几个子类扩展)。而如果将超类的构造函数声明为default时,尽管在一定范围内可以限制超类的扩展权限(同包类可见),但如果其它包中的子类也需要对其进行扩展时则显得无能为力。因此,在Java15诞生之前,仅凭现有的技术手段均无法有效的为开发人员提供一种声明式的方式来合理限制超类对子类的扩展能力。



值得庆幸的是,Java15的到来很好的满足了上述需求,为开发人员在语法层面上提供了sealed关键字来支持这项特性,示例1-3:

public sealed interface Animal permits Tiger, Lion {}final class Tiger implements Animal {}final class Lion implements Animal {}



上述程序示例中,我们通过sealed关键字定义了一个具备扩展限制的超类,并在classname之后使用permits关键字来限制其扩展范围;也就是说,只有在限制范围内的固定子类才允许对超类进行扩展,否则编译器将会出现编译错误。当然,permits关键字并非是强制的,当我们所定义的子类为嵌套内部类时,编译器会在编译时进行自动类型推断,示例1-4:

public sealed interface Animal{}final class Tiger implements Animal {}final class Lion implements Animal {}



相信大家也看见了,示例1-3和1-4中,子类均使用了final关键字来进行修饰,这是为何?实际上,这与sealed类的约束有关。我们使用sealed的初衷是希望对超类的扩展做出限制,在开发过程中不允许任何类都对其进行继承或实现,因此对于sealed类的子类而言,就需要追加3个约束。首先是超类和子类必须被在同包内,如果要解除这个限制,就必须被包含在同一模块中,示例1-5:

module name {}


其次,permits关键字包含的子类都必须显示extends或者implements。最后,子类都必须定义一个特定的修饰符来描述是否延续超类的扩展限制;也就是说,子类也可以使用sealed、permits关键字来定义它的下级派生,并且也可以使用final关键字来禁止所有子类继承于它,甚至还可以使用non-sealed关键字来解除所有限制(如果这么做,扩展限制将失去意义)。具体怎么定义,还需要结合具体的业务场景而定,示例1-6:

public sealed interface Animal{}/** * 子类也可以定义sealed来延续超类的扩展限制 */sealed class Tiger implements Animal permits NortheastTiger {}final class NortheastTiger extends Tiger{}/** * non-sealed解除所有限制 */non-sealed class Lion implements Animal {}class PumaConcolor extends Lion{}





进一步强化的String类型

早在Java13的时候,Java就已经开始支持多行字符串的文本块语法定义,尽管在当时这项语法特性还仅仅只是个预览功能,但也足以让大家期待;而随着Java15的正式来临,多行文本块已经被JDK正式支持。关于多行文本块的具体使用,大家可以参考我的上一篇博文。

数据载体类支持

实际上,这项语法特性也是为了给大家“减负”使用的,目的就是为了减少样板代码的编写。在实际开发过程中,我们往往会在业务代码中定义非常多的POJO类,比如:Controller和Service之间的DTO对象、持久层的PO对象等。但是这类POJO类型本身并不会处理复杂的业务逻辑,也不会包含任何行为,其作用纯粹就是作为一个数据载体,以便于数据的传输或访问处理;但我们仍然每天都需要不胜其烦的编写一大堆样板代码,比如:setter、getter、hashCode,以及equals等。在此大家需要注意,如果一个POJO类中存在较多的字段,会严重影响其维护性和可读性,示例1-7:

public class UserEntity {    private int id;    private String account, pwd;    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (!(o instanceof UserEntity)) return false;        UserEntity that = (UserEntity) o;        return getId() == that.getId() &&                Objects.equals(getAccount(), that.getAccount()) &&                Objects.equals(getPwd(), that.getPwd());    }    @Override    public int hashCode() {        return Objects.hash(getId(), getAccount(), getPwd());    }    //省略setter/getter方法}



为了减少样板代码的编写,Java的设计者们为开发人员提供了record关键字,专用于定义不可变的数据载体类,示例1-8:

public record UserEntity(int id, String account, String pwd) {}


上述程序示例中,我们仅通过一行代码即可完成一个POJO类的定义,大幅减少了样板代码的编写。通过record关键字定义的POJO类在对其进行反编译后我们不难发现,编译器在编译时,仍然会将其编译为一个标准的Java类型,相当于隐式帮我们实现了Lombok的功能示例1-9:

public final class UserEntity extends java.lang.Record {    private final int id;    private final java.lang.String account;    private final java.lang.String pwd;public UserEntity(int id, java.lang.String account,java.lang.String pwd) { /* compiled code */ }
    public final java.lang.String toString() { /* compiled code */ }    public final int hashCode() { /* compiled code */ }    public final boolean equals(java.lang.Object o) { /* compiled code */ }    public int id() { /* compiled code */ }    public java.lang.String account() { /* compiled code */ }    public java.lang.String pwd() { /* compiled code */ }}



上述程序示例中,所有的字段都被声明为了final,这也说明,record类是专用于定义不可变数据。在此大家需要注意,在使用record类时,有几点是必须注意的,如下所示:

  • record类中不允许定义实例字段,但允许定义静态字段;

  • record类中允许定义静态方法和实例方法,以便于实现一些特定的数据加工任务;

  • record类中允许定义构造函数。



或许大家存在疑问,早期我们在定义POJO类时,如果遇到有很多可选参数时,往往会采用重载构造函数的方式来解决,但如果使用record类后,我们应该如何解决这个问题?实际上,既然record类允许我们定义构造函数,那这就意味着同样可以通过相似的技术手段来解决共性问题,但实际开发过程中,我更建议大家使用Builder模式,示例1-10:

public record UserEntity(int id, String account, String pwd) {
    private UserEntity(Builder builder) {
        this(builder.id, builder.account, builder.pwd);
    }
    static class Builder {
        int id;
        String account, pwd;
        Builder(String account, String pwd) {
            this.account = account;
            this.pwd = pwd;
        }
        Builder id(int id) {
            this.id = id;
            return this;
        }
        public UserEntity build() {
            return new UserEntity(this);
        }
    }
    public static void main(String[] args) {
        var userEntity = new Builder("gxl", "123456").id(100).build();
        System.out.println(userEntity.toString());
    }
}


至此,本文内容全部结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LL1语法分析是一种自上而下的逐步分析法,常应用于编译器中对源代码的语法分析。而Java作为一种面向对象编程语言,可以很好地实现LL1语法分析。 首先需要设计一个语法分析器的类,其中核心的功能是对输入的源代码进行分析,并据此建立语法分析树。这个类需要有两个主要方法:一个用于读入源代码,并将其存储到内存中;另一个用于对源代码进行语法分析,生成语法分析树。 为了实现LL1语法分析,还需要对源代码的文法进行LL1分析表的构建。这个表包含文法中所有非终结符和终结符的FIRST集和FOLLOW集,以及文法规则对应的预测分析表格,用于判断分析串是否符合文法规则。 在具体实现过程中,需要先将输入的源代码转化为词法单元序列。这个过程可以通过使用正则表达式进行模式匹配,从而去除空格和注释等无关字符,提取出代表特定单词的有关信息。 然后建立语法分析树,树中每个节点代表一个非终结符或终结符,根据LL1分析表格,从根节点开始进行预测分析,并依次推导出分析树中的节点。 最后,将分析树与源代码进行比较,判断该源代码是否符合文法规则。如果符合,可执行相应语义分析,生成目标代码或执行相应操作。 总之,Java具有丰富的面向对象编程特性和强大的代码模块化能力,通过对LL1语法分析算法的深入理解与应用,也可以很好地实现自己的语法分析器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值