简体   繁体   中英

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

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. 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. Then you would have to change the return type of getEmployee() to 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.

  2. Add a proxy class that extends the Employee class that throws an IllegalAccessException or similar on setter calls. The advantage is that you do not have to introduce new Interfaces or change the methods of 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 . In this class, you override setters and other methods that change the state. The caller, for instance, might be getting an UnsupportedOperationException when they call such modifying methods on a retrieved 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; } } 

    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?

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. this.employee = ... is impossible.

However, finality isn't contagious the way constness is in C++. It's still possible to call getEmployee().setEmpName(...) or getEmployee().setEmpId(...) and modify the employee object. 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. 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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