简体   繁体   English

如何在将用户定义的类对象作为类成员的 Java 中创建用户定义的不可变类?

[英]How to create user defined immutable class in Java which is having user defined class object as a class member?

I want to make my immutable class EmployeeDetails which has Employee object in it.我想让我的不可变类EmployeeDetails中包含 Employee 对象。 I have followed conditions to make class immutable: 1. class is final 2. class members are final 3. no setters我遵循了使类不可变的条件:1. 类是最终的 2. 类成员是最终的 3. 没有 setter

If EmployeeDetails is immutable, I should not be able to change content in it.如果EmployeeDetails是不可变的,我应该无法更改其中的内容。 Still, I can change employee name or id.不过,我可以更改员工姓名或 ID。

What I am missing here?我在这里缺少什么?

public class TestImmutable{
    public static void main(String args[]){
        EmployeeDetails empd1 = new EmployeeDetails("ABC", new Employee(1, "n1"));

        System.out.println("Id   : " + empd1.getEmployee().getId());
        System.out.println("Name : " + empd1.getEmployee().getName());
        System.out.println("Empr : " + empd1.getEmployer());

        empd1.getEmployee().setId(2);
        empd1.getEmployee().setName("n2");
        System.out.println("\nId   : " + empd1.getEmployee().getId());
        System.out.println("Name : " + empd1.getEmployee().getName());
        System.out.println("Empr : " + empd1.getEmployer());
    }
}

final class EmployeeDetails{
    private final String employer;
    private final Employee emp1;

    public EmployeeDetails(String employer, Employee emp1){
        this.employer = employer;
        this.emp1 = emp1;
    }

    public String getEmployer(){
        return this.employer;
    }

    public Employee getEmployee(){
        return this.emp1;
    }
}

class Employee{
    public int id;
    public String name;

    public Employee(int id, String name){
        this.id = id;
        this.name = name;
    }

    public int getId(){
        return this.id;
    }

    public String getName(){
        return this.name;
    }

    public void setId(int id){
        this.id = id;
    }

    public void setName(String name){
        this.name = name;
    }
}   

Get rid of getEmployee() .摆脱getEmployee() You shouldn't be able to reach Employee emp1 outside EmployeeDetails .你不应该能够达到Employee emp1EmployeeDetails If you need access to the fields in Employee emp1 , then provide public methods that return them.如果您需要访问Employee emp1的字段,请提供返回它们的公共方法。

For example:例如:

final class EmployeeDetails{
    private final String employer;
    private final Employee emp1;

    public EmployeeDetails(String employer, Employee emp1){
        this.employer = employer;
        this.emp1 = emp1;
    }

    public String getEmployer(){
        return this.employer;
    }

    public String getEmployeeName() {
        return this.emp1.getName();
    }

    ...
}

Making a variable final means that you cannot assign it again to some other object.将变量设为 final 意味着您不能将其再次分配给其他对象。 You can still modify the state of the object whose reference it holds.您仍然可以修改其引用的对象的状态。

In this case :在这种情况下 :

final class EmployeeDetails{
  private final String employer;
  **private final Employee emp1;**
}

You cannot assign emp1 to a new object but you can still change the state of the employee object as it is not immutable.您不能将 emp1 分配给新对象,但您仍然可以更改员工对象的状态,因为它不是不可变的。 You can make Employee objects immutable by removing all the setters.您可以通过删除所有 setter 来使 Employee 对象不可变。

I have followed conditions to make class immutable: 1. class is final 2. class members are final 3. no setters我遵循了使类不可变的条件:1. 类是最终的 2. 类成员是最终的 3. 没有 setter

The conditions you have laid out are necessary but not sufficient to make a class immutable.您列出的条件是必要的,但不足以使类不可变。 Confused?使困惑?

Immutability is about preserving the state of class instances for all time.不变性是关于始终保持类实例的状态。 Once an instance of a class is created, then all the attributes that comprise the state of that instance must remain forever unchanged.一旦创建了一个类的实例,那么构成该实例状态的所有属性必须永远保持不变。

What happens if 1 through 3 above are satisfied, but one of your instance fields is a mutable class?如果满足上述 1 到 3 项,但您的实例字段之一是可变类,会发生什么情况? In this case, returning the reference to that instance field to a client makes it possible for the client to mutate the state of your supposedly immutable class.在这种情况下,将对该实例字段的引用返回给客户端可以让客户端改变您所谓的不可变类的状态。

One solution is to perform defensive copying upon all instance fields of the immutable class that are, themselves, mutable.一种解决方案是对不可变类的所有实例字段执行防御性复制,这些实例字段本身是可变的。 Instead of...代替...

public Employee getEmployee(){
    return this.emp1;
}

change this code so that a new copy of the Employee object is returned to the client.更改此代码,以便将Employee对象的新副本返回给客户端。 This assures that clients can not get a reference to the internal state of the instances of your immutable class:这确保客户端无法获得对不可变类实例的内部状态的引用:

public Employee getEmployee(){
    return this.emp1.clone();  // this solution assumes that Employee
                               // is safely cloneable, which requires some
                               // care on your part.  An alternative is
                               // to define a copy constructor in the
                               // Employee class and: return new Employee(emp1);
}

Defensive copying is necessary for all mutable components of an immutable class, and this rule must be applied during both construction and field access.防守复制是必要的一个不变类的所有可变组件,这条规则必须建设和现场访问期间应用。 Otherwise, you make it possible for client code to retain a reference to the mutable internal state of your class.否则,您可以让客户端代码保留对类的可变内部状态的引用。

The EmployeeDetails class is not immutable. EmployeeDetails类不是一成不变的。 You have followed the usual rules for immutability except one.除了一个之外,你已经遵循了不变性的通常规则。 In Joshua Bloch's Effective Java this rule is stated as follows:在 Joshua Bloch 的 Effective Java 中,这条规则表述如下:

Ensure exclusive access to any mutable componenent.确保对任何可变组件的独占访问。

In your case, since the class Employee is mutable, you need to copy Employee instances in the getter and the constructor.在您的情况下,由于类Employee是可变的,您需要在 getter 和构造函数中复制Employee实例。

public Employee getEmployee() {
    return new Employee(emp1.getId(), empl.getName());       // Copy
}

public EmployeeDetails(String employer, Employee emp1){
    this.employer = employer;
    this.emp1 = new Employee(emp1.getId(), empl.getName());  // Copy
}

Since this version uses copies, it is not possible to modify the internals of the EmployeeDetails class.由于此版本使用副本,因此无法修改EmployeeDetails类的内部结构。

This solution is very common.这种解决方案很常见。 For example, the String class is immutable.例如, String类是不可变的。 Both the constructor String(char[] value) and the method char[] toCharArray() make copies of the array.构造函数String(char[] value)和方法char[] toCharArray()都会复制数组。 This is necessary because arrays are mutable.这是必要的,因为数组是可变的。

Another solution, which may be more appropriate for your situation, is to make Employee immutable as well.另一种可能更适合您的情况的解决方案是使Employee不可变。

You could even get rid of the Employee class completely and just use two fields in the EmployeeDetails class.您甚至可以完全摆脱Employee类,而只使用EmployeeDetails类中的两个字段。

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

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