一、记录类 (record)
1)背景及介绍
record
类是一种被称为“数据载体类”(Data Carrier Class)的特殊类,主要用于简化不可变数据的定义和使用。它的设计目的是减少样板代码,使得开发者能够更容易地创建不可变的数据对象。
- JDK14 第一次预览: 参考:JEP 359: Records (Preview)
- JDK15 第二次预览: 参考:JEP 384: Records (Second Preview)
- JDK16 成为标准功能使用:参考:JEP 395: Records
Record
类的主要作用
- 简化不可变数据类:
Record
类自动提供了一些常见的功能,例如构造函数、equals()
、hashCode()
和toString()
方法。开发者无需手动编写这些方法。 - 减少样板代码:
Record
类的语法非常简洁,减少了手动编写 getter、setter、构造函数等代码的必要性。 - 不可变性:
Record
类默认是不可变的(immutable),即一旦创建,其字段值不能被修改。这种特性使得Record
类非常适合用于表示不需要改变的数据。
2)简单使用
以下Person类是通常的写法,它的属性是final的,并且提供了构造器,get,set,equals,hashcode,toString方法。
public class Persion {
private final String name;
private final Integer age;
public Persion(String name, Integer age) {
this.name = name;
this.age = age;
}
public String name() {
return this.name;
}
public Integer age() {
return this.age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Persion persion = (Persion) o;
return name.equals(persion.name) && age.equals(persion.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Persion[name=" + name + ", age=" + age + "]";
}
}
而有了Record类之后,我们就可以将上面的直接改成如下写法。类似于只剩下了一个构造器,支持你传参数,而参数就是这个类的属性,唯一不同的就是,这个属性是final的,无法被修改。
public record Person(String name, Integer age) {
}
3)扩展内容
Record 类也是支持定义方法的,这点和普通类是一样的,比如增加一个是否成年的方法。
public record Person(String name, Integer age) {
// 是否成年
public boolean isAdult() {
return age >= 18;
}
}
4)适用场景
record
类型非常适合用于表示不可变的数据模型。
如在配置文件中,record
可以用于表示配置项。不可变性确保配置在应用运行期间不会被意外修改。
在日志记录中,record
可以用于表示日志条目。不可变性确保日志条目在被记录后不会被修改,保证了日志的完整性和一致性。
二、密封类 (sealed classes)
1)背景及介绍
密封类在 Java 20 中引入,提供了一种强大的访问控制机制,用于限制哪些类可以扩展或实现一个特定的类或接口。密封类为开发者提供了一种更细粒度的方式来控制类的继承层次结构,从而提高代码的安全性和可维护性。
- JDK15 第一次预览: 参考:JEP 360: Sealed Classes (Preview)
- JDK16 第二次预览: 参考:JEP 397: Sealed Classes (Second Preview)
- JDK17 成为标准功能使用:参考:JEP 409: Sealed Classes
sealed 类的主要作用
-
控制继承层次结构:密封类允许开发者明确地控制哪些类可以扩展父类,防止不受控制的继承。
-
提高安全性:通过限制继承,可以减少潜在的安全风险,确保类的行为符合预期。
-
简化设计:密封类有助于明确类的意图和用途,使代码更易于理解和维护。
2)简单使用
sealed是一个关键字,可以通过 sealed 去修饰接口、类、抽象类,从而去限制它的子类,哪些可以继承或实现该父类。
比如申明一个Shape接口,通过关键字permits限制它的子类只能有圆Circle和矩形Rectangle。
sealed interface Shape permits Circle, Rectangle {
double area();
}
它的实现类就会受到两个限制
1、子类只能是 Circle 和 Rectangle
2、Circle 和 Rectangle 就必须定义成 final 或者 non-sealed
如果定义成final,那这个类就不能再被继承了,也就是变成了最终类。
final class Rectangle implements Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
但是如果定义成了 non-sealed ,那这个类又变成了普通类,任何类都可以扩展它们,只要访问修饰符允许。
3)扩展内容
- sealed 类除了对继承或实现的类有限制之外,在使用上和普通类是一致的。
- sealed 修饰符 和 record 类是不能联用的,因为使用了 record ,那这个类就是 final 的,无法被继承,不能对子类做约束,与 sealed 的用法矛盾了。
4)适用场景
在某些情况下,可能希望限制某个类的定义和扩展,以防止开发者意外地添加不必要的子类。密封类可以帮助实现这一点。通过限制类的继承层次结构,可以减少潜在的安全风险和代码复杂性。密封类有助于提高代码的安全性和可维护性。
在领域模型设计里面,定义一个明确的类型层次结构,可以确保领域模型的完整性。
案例:比如某某王者手游,里面的角色就分为坦克、战士、法师、射手、打野、辅助,那我就可以写一个类,去限定只有这几种角色。
// 定义密封类 Role,只允许坦克、战士、法师、射手、打野、辅助继承
sealed public class Role permits Tank, Warrior, Mid, Shooter, Jungler, Support {
private final String name;
public Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
// 角色描述
public abstract void describeRole();
}
然后他们各自的子类,比如 Tank 的子类,也可以进一步限制,或者通过 non-sealed 放开限制。
// 定义 Tank 的子类
@Slf4j
sealed class Tank extends Role permits HeavyTank, LightTank {
public Tank(String name) {
super(name);
}
@Override
public void describeRole() {
log.info(getName() + " is a tank!");
}
// 坦克特有的方法
public void takeDamage() {
log.info(getName() + " has high HP and can take a lot of damage!");
}
}
三、总结
以上便是对 java 新特性 record类 和 密封类(sealed classes) 的介绍,record
类非常适合用于表示不可变的数据模型,如在配置文件中,可以用于表示配置项;sealed 类非常适合用于在领域模型设计里面,定义一个明确的类型层次结构,确保领域模型的完整性,如手游中的角色都是确定的,直接使用 sealed 修饰即可。
ps:以下是我整理的java面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!