如何在 Java 中创建用户定义的不可变 class,将用户定义的 class 对象作为 class 成员?

How to create user defined immutable class in Java which is having user defined class object as a class member?

我想让我的不可变 class EmployeeDetails 其中包含 Employee 对象。我遵循了使 class 不可变的条件: 1. class 是最终的 2. class 成员是最终的 3. 没有二传手

如果 EmployeeDetails 是不可变的,我应该无法更改其中的内容。不过,我可以更改员工姓名或 ID。

我在这里缺少什么?

public class TestImmutable{
    public static void main(String args[]){
        EmployeeDetails empd1 = new EmployeeDetails("ABC", new Employee(1, "n1"));

        System.out.println("Id   : " + empd1.getEmployee().getId());
        System.out.println("Name : " + empd1.getEmployee().getName());
        System.out.println("Empr : " + empd1.getEmployer());

        empd1.getEmployee().setId(2);
        empd1.getEmployee().setName("n2");
        System.out.println("\nId   : " + empd1.getEmployee().getId());
        System.out.println("Name : " + empd1.getEmployee().getName());
        System.out.println("Empr : " + empd1.getEmployer());
    }
}

final class EmployeeDetails{
    private final String employer;
    private final Employee emp1;

    public EmployeeDetails(String employer, Employee emp1){
        this.employer = employer;
        this.emp1 = emp1;
    }

    public String getEmployer(){
        return this.employer;
    }

    public Employee getEmployee(){
        return this.emp1;
    }
}

class Employee{
    public int id;
    public String name;

    public Employee(int id, String name){
        this.id = id;
        this.name = name;
    }

    public int getId(){
        return this.id;
    }

    public String getName(){
        return this.name;
    }

    public void setId(int id){
        this.id = id;
    }

    public void setName(String name){
        this.name = name;
    }
}   

去掉getEmployee()。您应该无法到达 EmployeeDetails 以外的 Employee emp1。如果您需要访问 Employee emp1 中的字段,请提供 public 方法 return 它们。

例如:

final class EmployeeDetails{
    private final String employer;
    private final Employee emp1;

    public EmployeeDetails(String employer, Employee emp1){
        this.employer = employer;
        this.emp1 = emp1;
    }

    public String getEmployer(){
        return this.employer;
    }

    public String getEmployeeName() {
        return this.emp1.getName();
    }

    ...
}

使变量成为最终变量意味着您不能将它再次分配给其他对象。您仍然可以修改其引用所在对象的状态。

在这种情况下:

final class EmployeeDetails{
  private final String employer;
  **private final Employee emp1;**
}

您不能将 emp1 分配给新对象,但您仍然可以更改员工对象的状态,因为它不是不可变的。您可以通过删除所有 setter 使 Employee 对象不可变。

I have followed conditions to make class immutable: 1. class is final 2. class members are final 3. no setters

您列出的条件必要的但不足以使class不可变。困惑?

不变性是关于始终保持 class 实例的状态。一旦创建了 class 的实例,则构成该实例状态的所有属性必须永远保持不变。

如果满足上述 1 到 3,但您的实例字段之一是可变的,会发生什么情况 class?在这种情况下,将对该实例字段的引用返回给客户端使得客户端可以改变你所谓的不可变 class.

的状态。

一种解决方案是对不可变 class 本身可变的所有实例字段执行 防御性复制 。而不是...

public Employee getEmployee(){
    return this.emp1;
}

更改此代码,以便将 Employee 对象的新副本返回给客户端。这确保客户无法获得对不可变 class:

实例的内部状态的引用
public Employee getEmployee(){
    return this.emp1.clone();  // this solution assumes that Employee
                               // is safely cloneable, which requires some
                               // care on your part.  An alternative is
                               // to define a copy constructor in the
                               // Employee class and: return new Employee(emp1);
}

不可变 class 的所有可变组件都需要防御性复制,并且必须在 构造和字段访问期间应用此规则。否则,您可以让客户端代码保留对 class.

的可变内部状态的引用

EmployeeDetails class 不是一成不变的。除了一个之外,您已经遵循了不变性的通常规则。在 Joshua Bloch 的 Effective Java 中,这条规则表述如下:

Ensure exclusive access to any mutable componenent.

在你的例子中,由于 class Employee 是可变的,你需要在 getter 和构造函数中复制 Employee 个实例。

public Employee getEmployee() {
    return new Employee(emp1.getId(), empl.getName());       // Copy
}

public EmployeeDetails(String employer, Employee emp1){
    this.employer = employer;
    this.emp1 = new Employee(emp1.getId(), empl.getName());  // Copy
}

由于此版本使用副本,因此无法修改 EmployeeDetails class.

的内部结构

这种解决方案很常见。例如,String class 是不可变的。构造函数 String(char[] value) 和方法 char[] toCharArray() 都复制数组。这是必要的,因为数组是可变的。

另一种可能更适合您的情况的解决方案是使 Employee 不可变。

您甚至可以完全摆脱 Employee class,只使用 EmployeeDetails class.

中的两个字段