通过接口的不变性


通常希望有不可变的对象,即一旦构造就不能修改的对象。通常,不可变对象具有声明为final并在对象构造函数中设置的字段。字段有getter,但没有setter,因为无法保存值。一旦创建,对象状态就不会改变,并且对象可以在不同的线程之间共享,因为状态是固定的。该语句有很多警告,例如,如果您有一个列表,尽管列表字段引用可能是final的,但列表本身可能会更改,并添加和删除值,这会破坏不可变性。

实现这种状态通常很困难,因为我们依赖于许多可能不支持不变性的工具和框架,例如任何通过创建对象然后在其上设置值来构建对象的框架。

然而,解决这个问题的一种方法是接受一个可变对象,并通过接口使其不可变。我们通过创建一个接口来实现这一点,该接口表示一个对象的所有getter,但不表示任何setter。

给定一个具有id,名字和姓氏字段的人的域实体,我们可以定义一个不可变接口:

public interface ImmutablePerson {
    public int getId();
    public String getFirstName();
    public String getLastName();
}

然后我们可以在person类中实现此接口:

public class PersonEntity implements ImmutablePerson {
    private int id;
    private String firstName;
    private String lastName;
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

我们的API处理PersonEntity类,但将对象公开为ImmutablePerson欧力。

public ImmutablePerson loadPerson(int id) {
    PersonEntity result = someLoadingMechanism.getPerson(id);
    result.setxxxxx(somevalue); //we can change it internally
    return result; //once it has been returned it is only accessed as an immutable person
}

若要返回可变实例进行修改,可以返回PersonEntity。或者,您可以添加一个MutablePerson接口,并实现它。除非您希望在不可变/可变类型之间保持强制转换,否则MutablePerson接口可以扩展ImmutablePerson接口,因此可写版本也是可读的。

public interface MutablePerson extends ImmutablePerson {
    public void setId(int id) {
    public void setFirstName(String firstName);
    public void setLastName(String lastName);
}


当然,有人可以很容易地将对象强制转换为PersonEntity或者MutablePerson和更改值,但是,您还可以change static final variables with reflection如果你想的话。强制转换对象以使其仅可写的功能可能是一个仅供内部使用的特性。

此机制可用于从持久性层返回不可变对象。某些ORM工具有一些警告,因为它们使用了惰性加载机制,这会破坏不可变性。在接口下面,它仍然是一个可变对象。通过一些额外的代码,您可以通过将基础集合包装在只读集合中并将其返回给用户来创建不可变的集合。

JVM赋予标记为final的变量的不可更改性的某些好处不会赋予这种类型的解决方案,因为底层变量不是final或不可更改的。