简体   繁体   English

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

[英]How to make a class immutable when its referencing a mutable object?

Let's say I have a class in java called Employee that looks something like this 假设我在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;
    }
}

Now I want to use this object in an immutable class let's say a company in the following format. 现在我想在一个不可变的类中使用这个对象,让我们说一个公司的格式如下。 and the condition is that I cannot modify 而条件是我无法修改

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; 
    }
}

So my question is, is this a valid way to make Company class immutable when I am referencing an inside object that can be modified? 所以我的问题是,当我引用一个可以修改的内部对象时,这是使公司类不可变的有效方法吗?

As referenced in this article https://www.journaldev.com/129/how-to-create-immutable-class-in-java do a deep cloning of Employee object in your constructor of final class. 在本文中引用https://www.journaldev.com/129/how-to-create-immutable-class-in-java在最终类的构造函数中深入克隆Employee对象。 This way you will won't use the object reference. 这样您就不会使用对象引用。

2 things that came to my mind: 我想到的两件事:

  1. Add a ReadOnlyEmployee Interface for your Employee which only exposes the getters. 为您的Employee添加一个只暴露getter的ReadOnlyEmployee接口。 Then you would have to change the return type of getEmployee() to ReadOnlyEmployee . 然后你必须将getEmployee()的返回类型更改为ReadOnlyEmployee The advantage of this solution is that it's clear and explicit for the user. 该解决方案的优点在于它对用户来说清晰明了。 The problem is that the getter returns another type than the constructor accepts which may be confusing. 问题是getter返回的另一种类型比构造函数接受的类型可能令人困惑。

  2. Add a proxy class that extends the Employee class that throws an IllegalAccessException or similar on setter calls. 添加一个代理类,该类扩展了在setter调用时抛出IllegalAccessException或类似的Employee类。 The advantage is that you do not have to introduce new Interfaces or change the methods of Company . 优点是您不必引入新的接口或更改Company的方法。 The disadvantage is the possible runtime Exceptions. 缺点是可能的运行时异常。

The problem is that you release a reference to an employee instance, thus the caller may modify the object. 问题是您释放对员工实例的引用,因此调用者可以修改该对象。

  1. You return a link to a copy of the employee and stop worrying about what will happen next. 您返回指向该员工副本的链接,并不再担心接下来会发生什么。 You protected the underlying instance. 您保护了基础实例。 The caller can do whatever they want with a copy, while your field remains consistent and effectively unchanged (in fact, it's changeable, of course). 调用者可以使用副本执行他们想要的任何操作,而您的字段保持一致且实际上不变(事实上,它当然是可更改的)。

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

    Problems here? 问题在这里? The caller is altering the employee's data and can't figure out why nothing has been changed within the company. 调用者正在改变员工的数据,无法弄清楚为什么公司内部没有任何变更。

  2. You return a reference to a Company 's inner subclass of Employee . 您返回对Company内部子类Employee的引用。 In this class, you override setters and other methods that change the state. 在此类中,您可以覆盖setter和其他更改状态的方法。 The caller, for instance, might be getting an UnsupportedOperationException when they call such modifying methods on a retrieved Employee . 例如,调用者在检索到的Employee上调用此类修改方法时可能会收到UnsupportedOperationException

     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; } } 

    Problems here? 问题在这里? Inheritance is used to control access. 继承用于控制访问。

  3. Otherwise, an immutable class that is made of mutable components isn't that immutable. 否则,由可变组件的不可变类不是一成不变的。

Is this a valid way to make Company class immutable when I am referencing an inside object that can be modified? 当我引用可以修改的内部对象时,这是使Company类不可变的有效方法吗?

No. From my understanding, any component obtained from an instance of an immutable class shouldn't be alterable. 根据我的理解,从不可变类的实例获得的任何组件都不应该是可以改变的。 No matter at what level a request to change may occur. 无论在何种级别,都可能发生变更请求。

Technically, no. 从技术上讲,没有。 Adding final makes the reference immutable: you cannot assign a different Employee object. 添加final使引用不可变:您不能分配不同的Employee对象。 this.employee = ... is impossible. this.employee = ...是不可能的。

However, finality isn't contagious the way constness is in C++. 但是,终结性并不像C ++中的constness那样具有传染性。 It's still possible to call getEmployee().setEmpName(...) or getEmployee().setEmpId(...) and modify the employee object. 仍然可以调用getEmployee().setEmpName(...)getEmployee().setEmpId(...)并修改employee对象。 You can't replace it with a new one but you can modify the object that's there. 你不能用新的替换它,但你可以修改那里的对象。

If you want to make Company completely immutable then you need to make defensive copies of the Employee object in two places. 如果您想使Company完全不可变,那么您需要在两个地方制作Employee对象的防御性副本。 One, you need to copy the object passed in the constructor. 一,您需要复制构造函数中传递的对象。 Two, you need to return a copy from getEmployee() to prevent the internal object from being exposed. 二,你需要从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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM