假设我在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类不变的一种有效方法吗?
答案 0 :(得分:1)
如本文https://www.journaldev.com/129/how-to-create-immutable-class-in-java所述,在最终类的构造函数中对Employee对象进行了深层克隆。这样,您将不会使用对象引用。
答案 1 :(得分:1)
我想到了两件事:
为您的ReadOnlyEmployee
添加一个Employee
接口,该接口仅公开吸气剂。然后,您必须将getEmployee()
的返回类型更改为ReadOnlyEmployee
。该解决方案的优点是对于用户而言是清晰明了的。问题在于,getter返回的是构造函数所接受的另一种类型,这可能会造成混淆。
添加一个代理类,该代理类扩展了Employee
类,该类在setter调用中抛出了IllegalAccessException
或类似内容。好处是您不必引入新的接口或更改Company
的方法。缺点是可能会出现运行时异常。
答案 2 :(得分:0)
问题是您释放了对雇员实例的引用,因此调用方可能会修改该对象。
您返回一个指向员工副本的链接,而不必担心接下来会发生什么。您保护了基础实例。调用方可以使用副本进行任何操作,而您的字段保持一致并且有效地保持不变(实际上,它是可变的)。
public class Employee {
public Employee(Employee o) {
// copy evething you need from o
}
}
public final class Company {
public Employee getEmployee() {
return new Employee(employee);
}
}
这里有问题吗?呼叫者正在更改员工的数据,无法弄清为什么公司内部未进行任何更改。
您返回对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;
}
}
这里有问题吗?继承用于控制访问。
否则,由可变组件组成的不可变类不是 不可变的。
当我引用可修改的内部对象时,这是使
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
}
}