Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
174 views
in Technique[技术] by (71.8m points)

java - How can we maintain Immutability of a class with a mutable reference

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;
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Well, the concept is reading the JLS and understanding it. Chapter 17 of the JLS "Threads and Locks" describes memory visibility and synchronization. Section 17.5 "Final Field Semantics" describes the memory visibility semantics for final fields. That section says in part:

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() );
    }
    public 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.

Since this question is getting popular, I should also add a mention of Brian Goetz's book, Java Concurrency in Practice, which is how I learned about these techniques, and I'm basically paraphrasing that book above.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...