How can we maintain Immutability of a class with a

2019-02-03 14:37发布

I know all the basic rules to make our class immutable but I am a little confused when there is another class reference. I know if there is collection instead of Address then we can make use of Collections.unmodifiableList(new ArrayList<>(modifiable)); and then we can make our class immutable. But in below case I am still unable to get the concept.

public final class Employee{
    private final int id;
    private Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=address;
    }
    public int getId(){
        return id;
    }
    public Address getAddress(){
        return address;
    }
}

public class Address{
    private String street;
    public String getStreet(){
        return street;
    }
    public void setStreet(String street){
        this.street = street;
    }
}

5条回答
Summer. ? 凉城
2楼-- · 2019-02-03 15:01

You can also use shallow copy using cloning

public final class Employee{
    private final int id;
    private Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=address.clone();
    }
    public int getId(){
        return id;
    }
    public Address getAddress(){
        return address.clone();
    }
}

Using this will create a separate object of Address in Employee class so in this case any changes made to the Address object passed as argument in Employee constructor will not change the member variable Address object of Employee class.

The getAddress() method is also returning a clone object so any changes made to the object fetched by this method do not effect the address object of Employee class.

Note: To use this make Address class Cloneable.

查看更多
老娘就宠你
3楼-- · 2019-02-03 15:12

So in your example Employee class is immutable, because once it is created, you can't change its state, because it has only getter methods.

Address class is mutable because you can modify it with the setStreet method.

So if you have other class which uses Address object, you are sure that that class can't modify the objects state.

查看更多
孤傲高冷的网名
4楼-- · 2019-02-03 15:13

If you want to encapsulate a mutable object into an immutable one, then you need to:

  1. Create a copy of the mutable object (i.e. via copy constructor, cloning, serialization/deserialization, etc.); never store the reference to the original mutable object.
  2. Never return the mutable object. If you must to, then return a copy of the object.
  3. Avoid methods which can change the mutable object.

public Employee(int id, Address address){

        this.id = id;
        this.address=new Address();  
        this.address.setStreet( address.getStreet() );
    }



public Address getAddress() {
        Address nuAdd = new Address(); // must copy here too
        nuAdd.setStreet( address.getStreet() );
        return nuAdd;
}
查看更多
老娘就宠你
5楼-- · 2019-02-03 15:23

Well there is steps provided by Java docs

A Strategy for Defining Immutable Objects

The following rules define a simple strategy for creating immutable objects. Not all classes documented as "immutable" follow these rules. This does not necessarily mean the creators of these classes were sloppy — they may have good reason for believing that instances of their classes never change after construction. However, such strategies require sophisticated analysis and are not for beginners.

  • Don't provide "setter" methods — methods that modify fields or objects referred to by fields.
  • Make all fields final and private.
  • Don't allow subclasses to override methods. The simplest way to do this is to declare the class as final. A more sophisticated approach is to make the constructor private and construct instances in factory methods.
  • If the instance fields include references to mutable objects, don't allow those objects to be changed:
    • Don't provide methods that modify the mutable objects.
    • Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

Address class is mutable because you can modify it with the setStreet method. So other class can modify this class.

We can defend against this by taking a copy of the of the Address instance when it is passed in rather than trusting the reference to the instance we are given.

Making Address object final

private final Address address;

Secondly,

this.address = new Address(address.getStreet());

Create constructor in Address class that sets Street.Remove setter method for street.

And finally instead of

public Address getAddress(){
    return address;
} 

Use

public Address getAddress(){
    return new Address(address.getStreet());
}
查看更多
放荡不羁爱自由
6楼-- · 2019-02-03 15:25

Well, the concept is reading the JLS and understanding it. In this case, the JLS says:

final fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. This can provide safety guarantees against misuse of an immutable class by incorrect or malicious code. final fields must be used correctly to provide a guarantee of immutability.

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

So you need to:

  1. Make address both final and private.
  2. For any mutable object, you must prevent the reference to that object from being seen externally.

In this case, #2 probably means you can't return a reference to Address like you have with getAddress(). And you have to make a defensive copy in the constructor. I.e., make a copy of any mutable parameter, and store the copy in Employee. If you can't make a defensive copy, there's really no way to make Employee immutable.

public final class Employee{
    private final int id;
    private final Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=new Address();  // defensive copy
        this.address.setStreet( address.getStreet() );
    }
    pulbic int getId(){
        return id;
    }
    public Address getAddress() {
        Address nuAdd = new Address(); // must copy here too
        nuAdd.setStreet( address.getStreet() );
        return nuAdd;
}

Implementing clone() or something similar (a copy ctor) would make creating defensive objects easier for complicated classes. However, the best recommendation I think would be to make Address immutable. Once you do that you can freely pass around its reference without any thread-safety issues.

In this example, notice I do NOT have to copy the value of street. Street is a String, and strings are immutable. If street consisted of mutable fields (integer street number for example) then I would have to make a copy of street also, and so on ad infinitum. This is why immutable objects are so valuable, they break the "infinite copy" chain.

查看更多
登录 后发表回答