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

问题描述 投票:4回答:4

假设我在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; 
    }
}

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

java immutability
4个回答
1
投票

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


1
投票

我想到的两件事:

  1. 为你的ReadOnlyEmployee添加一个Employee接口,它只暴露吸气剂。然后你必须将getEmployee()的返回类型更改为ReadOnlyEmployee。该解决方案的优点在于它对用户来说清晰明了。问题是getter返回的另一种类型比构造函数接受的类型可能令人困惑。
  2. 添加一个扩展Employee类的代理类,该类在setter调用时抛出IllegalAccessException或类似的类。优点是您不必引入新的接口或更改Company的方法。缺点是可能的运行时异常。

0
投票

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

但是,终结性并不像C ++中的constness那样具有传染性。仍然可以调用getEmployee().setEmpName(...)getEmployee().setEmpId(...)并修改员工对象。你不能用新的替换它,但你可以修改那里的对象。

如果你想使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
    }
}

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。在此类中,您可以覆盖setter和其他更改状态的方法。例如,当他们在检索到的UnsupportedOperationException上调用这样的修改方法时,调用者可能会获得Employeepublic 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类不可变的有效方法吗?

根据我的理解,从不可变类的实例获得的任何组件都不应该是可以改变的。无论在何种级别,都可能发生变更请求。

© www.soinside.com 2019 - 2024. All rights reserved.