Java不可变对象整洁之道
作者:ASCE1885 发布时间:[ 2017/3/13 11:05:53 ] 推荐标签:Java 测试开发技术
在 Java 语言中,不可变类指的是这个类的实例是不可修改的,在创建类实例时我们需要指定这个类所有的属性值,而且在以后的生命周期中这些属性值都不能重新赋值。经验丰富的你可能已经知道 Java 中已经存在一些不可变类型,例如 String,BigInteger 和 BigDecimal 等。
不可变类对于开发者来说有如下好处:
· 易于设计,实现和使用
· 使用过程中不容易导致出错
· 更加的安全,可以随意地共用
· 天然具备线程安全性,无需增加额外的同步操作
本文的目标是介绍 Java 开发中构建和自动生成不可变类的几种整洁的方式,其中着重介绍自动生成不可变类的两个流行的类库:Immutables 和 AutoValue,后同时也会介绍 Guava 中的不可变集合类。
本文不会对这三者做对比,因为它们都是非常棒的类库,需要根据你的项目特点去作出选择。
普通的 Java 不可变类
为了编写出不可变类,我们需要遵循以下几个原则:
· 类不可被继承:也是要把类声明为 final,从而预防了子类继承中可能存在的非法修改父类状态的问题
· 所有的属性设置为 final:这样一来,只能通过构造函数传参或者 builder 模式来实例化类实例
· 所有属性设置为 private:这是毋庸置疑的,因为如果属性是 public,那么外界可以随意修改它的属性值
下面我们来看一个不可变类的例子:
public final class Autobot {
private final String name;
private final String fullname;
private final Boolean leader;
private final String group;
public Autobot(String name, String fullname, Boolean leader, String group) {
this.name = name;
this.fullname = fullname;
this.leader = leader;
this.group = group;
}
public String name() {
return name;
}
public String fullname() {
return fullname;
}
public Boolean leader() {
return leader;
}
public String group() {
return group;
}
}
所有的属性都是 private final 的,类也是 final 的,而且不存在任何的可变方法。
如果某些场景下我们需要一个返回这个类实例的方法,我们应该每次都返回一个新的类实例。例如 Autobot 类有一个名为 fusion(Autobot) 的方法,它接收另外一个 Autobot 的实例并将两者进行融合,如下所示:
public Autobot fusion(Autobot autobot) {
return new Autobot(name.concat(autobot.name()));
}
接下来我们正式介绍可以节省开发者很多时间的自动生成不可变类的函数库。为什么要使用自动代码生成的?因为这能够提高我们的开发效率,同时代码质量也更高。这一点并不神奇,它只是在编译期间生成代码而已,你将会非常乐意使用它。
Android:下面提到的自动生成代码的函数库:Immutables 和 AutoValue,如果是在 Android 开发中使用时,都需要在 Android Studio 工程中配置好 APT1(annotation processor tool)。
Immutables 函数库
上面我们介绍了在 Java 中如何编写一个普通的不可变类。事实上并不需要写很多的代码,但有时一个类中可能存在很多的属性,又或者你要使用 Builder 模式来实例化这个类,那么将不可避免要编写很多样板代码。这种场景通常是代码生成工具的用武之地。
下面我们来看一下使用 Immutables 函数库如何生成一个不可变类:
import org.immutables.value.Value;
@Value.Immutable public abstract class Decepticon {
public abstract String name();
public abstract String fullname();
public abstract Boolean leader();
public abstract String group();
}
使用 @Immutable 注解以及在 IDE 中正确配置对 Immutables 函数库的引用,那么上面代码将会在编译期间生成一个不可变的类。默认情况下 Immutables 会在生成的类名前面增加 Immutable 前缀,也是生成的类名为 Immutable[类名],当然开发者可以配置生成的风格2。
ImmutableDecepticon decepticon = ImmutableDecepticon.builder()
.name("Megatron")
.fullname("Megatron Galvatron")
.group("Decepticons")
.leader(true)
.build();
Immutable 函数库基于上面的 Decepticon 类,将会生成一个拥有 280 行代码的不可变类,这个类同时包含了一些有用的方法,例如copyOf(Decepticon),toString(),hashCode(),equals(),以及如上所示的一个 builder 模式系列类和方法。
Immutable 一个不错的特性是可以声明不可变类型为一个接口(interfaces)。这样我们可以实现多接口的继承,从而模拟多继承:
@Value.Immutable public interface ITransformer extends IAutobot, IDecepticon {
// it will generate and fields extended from IAutobot and IDecepticon
}
Immutables 还有很多很酷且有用的特性,如果我们不想要生成的代码使用 builder 模式,那么我们可以让 Immutables 生成一个包含我们通过注解指定的参数的构造方法:
import org.immutables.value.Value;
@Value.Immutable public interface Car {
enum MotorType {
DIESEL,
GAS
}
@Value.Parameter String manufacturer();
@Value.Parameter MotorType motorType();
}
// create instance
ImmutableCar car = new ImmutableCar("Nissan", Car.MotorType.GAS);
后提一下,Immutables 也支持 Guava 的 _Optional_ 类型。
AutoValue 函数库
AutoValue 是 Google 开发的函数库,是作为 Auto 工程的一部分存在的。它包含一系列 Java 源码生成工具例如 AutoFactory,AutoService 以及一些用于编写代码生成器的通用工具类。开发者通过编写抽象类并使用 AutoValue 注解,之后 AutoValue 会自动生成对应的不可变类,如下所示:
import com.google.auto.value.AutoValue;
@AutoValue abstract class Autobot {
abstract String name();
abstract String fullname();
abstract Boolean leader();
abstract String group();
}
编译工程后,AutoValue 会自动生成名为 AutoValue_Autobot 的类:
AutoValue_Autobot autobot =
new AutoValue_Autobot("Bumblebee", "Bumblebee Autobot", false, "Autobot");
当然,在实际开发中,我们不应该直接将 AutoValue 前缀暴露给不可变类的使用者,因此可以做如下修改:
import com.google.auto.value.AutoValue;
@AutoValue abstract class Autobot {
public static Autobot create(String name, String fullname, Boolean leader, String group) {
return new AutoValue_Autobot(name, fullname, leader, group);
}
abstract String name();
abstract String fullname();
abstract Boolean leader();
abstract String group();
}
修改后,使用者如下所示来创建 Autobot 的实例:
Autobot auto = Autobot.create("Bumblebee", "Bumblebee Autobot", false, "Autobot");
如果打开生成的类,跟 Immutables 函数库类似,AutoValue 函数库也会生成equals(),toString() 和 hashCode() 方法,同时它会进行参数校验。
import com.google.auto.value.AutoValue;
@AutoValue abstract class Decepticon {
abstract String name();
abstract String fullname();
abstract Boolean leader();
abstract String group();
static Builder builder() {
return new AutoValue_Decepticon.Builder();
}
@AutoValue.Builder abstract static class Builder {
abstract Builder name(String name);
abstract Builder fullname(String fullname);
abstract Builder leader(Boolean leader);
abstract Builder group(String group);
abstract Decepticon build();
}
}
AutoValue 函数库也可以生成 Builders,相比 Immutables 函数库,可能需要多写一些代码,你可以对比这两个函数库多优点进行选择。
在我看来,builder 模式使得代码更具可读性和更简洁。
Decepticon decepticon = Decepticon.builder()
.name("Kakuryu")
.fullname("Kakuryu Decepticon")
.leader(false)
.group("Decepticons")
.build();
AutoValue 另外一个强大的特性是扩展(extensions),你可以构建自己的扩展,本文不会进行介绍,如果你感兴趣可以看看其他不错的文章,例如 Jake Wharton 的 AutoValue Extensions3。
Guava 函数库
Guava 也提供了很多关于不可变类的有用的支持,和前面两个函数库的区别是 Guava 不会自动生成类,它仅提供了一些不可变的集合类,如下所示:
· ImmutableList
· ImmutableSet
· ImmutableSortedSet
· ImmutableMap
· ImmutableSortedMap
· ImmutableMultiset
· ImmutableSortedMultiset
· ImmutableMultimap
· ImmutableListMultimap
· ImmutableSetMultimap
· ImmutableBiMap
· ImmutableClassToInstanceMap
· ImmutableTable
上述类的用法是和静态类,迭代器和帮助类等配合使用,如下所示:
public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
"red",
"orange",
"yellow",
"green",
"blue",
"purple");
final ImmutableSet<Bar> bars = ImmutableSet.copyOf(bars); // defensive copy!
如果你对 Guava 感兴趣,可以看下这个演讲 Cleaner code with Guava[^4],它同时提供了一个例子。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11