大萧条时期的野蛮生长——谈Java Record类

318 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划·4月更文挑战」的第1天,点击查看活动详情

偶尔刷一刷各大中文技术社区,还有很多标题还是“Java 8的新特性”等等,对于一个10年前发布的版本可能真的称不上“新”了。但不可忽视的是,现在业务保有量最大的版本确实还是8,继续深究Java8也完全没问题。没有追捧新技术的意思,但技术在进步,我们也应该打开视野,保有量第二的已经到了11了,虽然11也是LTS,但维护截止日期与8仅差1年,恰好17在2021年9月就正式发布,所以你的下一个版本的最佳选择应该是17。这里就再谈一下Java17正式发布的record类。

什么是Record

Record类在Java14中就开始预览,一直到最近的一个LTS版本——Java17才正式发布。根据JEP395的描述,Record类是不可变数据的载体,类似于当下广泛应用的各种model,dto,vo等POJO类,但record本身在构造之后遍不再可赋值。所有的record类都继承自java.lang.Record

Record提供了什么

record类默认提供了全字段的构造器,属性的访问,还有equals,hashcode,toString方法。可能乍一看这不就是lombok吗,或者熟悉kotlin的会觉得这就是data class啊。确实,功能性上区别大不,除了record是不可变的。下面是一个record类型的定义:

record Rectangle(double length, double width) { }

如果用lombok就是下面这样:

@Data
public Rectangle{
    private double length;
    private double width;
}

两者的区别在于,对于equals,hashcode,toString等方法,record的字节码使用了invokedynamic,而lombok使用的是普通的invokestatic等,性能要比后者高出很多。

Record怎么用

如果仅仅用于传统的POJO类的替换,其实可读性要差很多。因为record默认只提供了全字段的构造器,需要这样写:

Rectangle r = new Rectangle(4,5);

两个字段可能还好点,但通常我们需要10个或者更多的字段时,这种初始化看着就头大。当然,record类支持自定义构造器,也可以支持了一些简化操作,但跟lombok的@Builder还是没法比。是不是就应用用在字段比较少的情况下呢?个人觉得record的一个重要用法就是存储方法内的临时数据对象,举个例子:

public List<Person> sortPeopleByAge(List<Person> people) {
    
    record Data(Person person, int age){};

    return people.stream()
            .map(person -> new Data(person, computeAge(person)))
            .sorted((d1, d2) -> Double.compare(d2.age(), d1.age()))
            .map(Data::person)
            .collect(toList());
}
public int computAge(Person person) {
    return person.getAge();
}

上面的代码是把Person对象按照年龄排序,如果不是record构建一个临时存储对象,需要怎么实现呢,相对来说通过这种流式写法还是比较费劲的。

小结

Record类从性能上说要远比lombok强,毕竟是亲生的。但record类更适合用做方法的临时内部数据存储,可以很大程度上提升可读性。