引用可变对象时,如何使类不可变?

时间:2019-04-29 11:54:35

标签: java immutability

假设我在Java中有一个名为 Employee 的类,它看起来像这样

public class Employee {
    private String empName;
    private int empId;

    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public int getEmpId() {
        return empId;
    }
    public void setEmpId(int empId) {
        this.empId = empId;
    }
}

现在,我想在一个不可变的类中使用此对象,假设一家公司采用以下格式。条件是我无法修改

public final class Company {
    final String companyName;
    final Employee employee;

    public Company(String companyName, Employee employee) { 
        this.companyName = companyName; 
        this.employee = employee; 
    } 
    public String getCompanyName() { 
        return companyName; 
    } 
    public Employee getEmployee() { 
        return employee; 
    }
}

所以我的问题是,当我引用一个可以修改的内部对象时,这是使Company类不变的一种有效方法吗?

4 个答案:

答案 0 :(得分:1)

如本文https://www.journaldev.com/129/how-to-create-immutable-class-in-java所述,在最终类的构造函数中对Employee对象进行了深层克隆。这样,您将不会使用对象引用。

答案 1 :(得分:1)

我想到了两件事:

  1. 为您的ReadOnlyEmployee添加一个Employee接口,该接口仅公开吸气剂。然后,您必须将getEmployee()的返回类型更改为ReadOnlyEmployee。该解决方案的优点是对于用户而言是清晰明了的。问题在于,getter返回的是构造函数所接受的另一种类型,这可能会造成混淆。

  2. 添加一个代理类,该代理类扩展了Employee类,该类在setter调用中抛出了IllegalAccessException或类似内容。好处是您不必引入新的接口或更改Company的方法。缺点是可能会出现运行时异常。

答案 2 :(得分:0)

问题是您释放了对雇员实例的引用,因此调用方可能会修改该对象。

  1. 您返回一个指向员工副本的链接,而不必担心接下来会发生什么。您保护了基础实例。调用方可以使用副本进行任何操作,而您的字段保持一致并且有效地保持不变(实际上,它是可变的)。

    public class Employee {
        public Employee(Employee o) {
            // copy evething you need from o
        }
    }
    
    public final class Company {
        public Employee getEmployee() { 
            return new Employee(employee); 
        }
    }
    

    这里有问题吗?呼叫者正在更改员工的数据,无法弄清为什么公司内部未进行任何更改。

  2. 您返回对Company的内部内部子类Employee的引用。在此类中,您将覆盖设置器和其他更改状态的方法。例如,调用者在检索到的UnsupportedOperationException上调用此类修改方法时,可能会得到Employee

    public final class Company {
        private final CompanyEmployee companyEmployee;
    
        public Company(String companyName, Employee employee) { 
            this.companyName = companyName; 
            companyEmployee = new CompanyEmploye(employee); 
        }
    
        private static class CompanyEmployee extends Employee {
            public Employee(Employee o) {
                super(o);
            }
    
            public void setEmpName(String empName) {
                throw new UnsupportedOperationException();
            }
            public void setEmpId(int empId) {       
                throw new UnsupportedOperationException();
            }
        }
    
        public Employee getEmployee() { 
            return companyEmployee;
        }
    }
    

    这里有问题吗?继承用于控制访问。

  3. 否则,由可变组件组成的不可变类不是 不可变的。

  

当我引用可修改的内部对象时,这是使Company类不可变的有效方法吗?

不。根据我的理解,从不可变类的实例获得的任何组件都不应该是可变的。无论在什么级别进行更改,都可能会发生。

答案 3 :(得分:0)

从技术上讲,没有。添加final会使引用不可变:您不能分配其他Employee对象。 this.employee = ...是不可能的。

但是,终结性并不像C ++中的常数性那样具有感染力。仍然可以调用getEmployee().setEmpName(...)getEmployee().setEmpId(...)并修改employee对象。您不能用新的替换它,但是可以修改其中的对象。

如果要使Company完全不变,则需要在两个位置制作Employee对象的防御性副本。一,您需要复制在构造函数中传递的对象。第二,您需要从getEmployee()返回一个副本,以防止内部对象暴露。

public final class Company {
    final String companyName;
    final Employee employee;

    public Company(String companyName, Employee employee) { 
        this.companyName = companyName; 
        this.employee = new Employee(employee);     // 1
    } 
    public String getCompanyName() { 
        return companyName; 
    } 
    public Employee getEmployee() { 
        return new Employee(employee);              // 2
    }
}