How to Create an Immutable Class

How to Create Immutable Class with ListString Element in class

this.courses = Collections.unmodifiableList(courses);

That creates, as the name says, an unmodifiable list. But that is just a view on the original list. Thus changes to that original list become visible in your "unmodifiable" view.

When in doubt: clone your list, like:

this.courses = new ArrayList<>(courses);

And then ensure that your getter does:

return Collections.unmodifiableList(courses);

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

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.

How do I create an immutable Class?

I think you're on the right track -

  • all information injected into the class should be supplied in the constructor
  • all properties should be getters only
  • if a collection (or Array) is passed into the constructor, it should be copied to keep the caller from modifying it later
  • if you're going to return your collection, either return a copy or a read-only version (for example, using ArrayList.ReadOnly or similar - you can combine this with the previous point and store a read-only copy to be returned when callers access it), return an enumerator, or use some other method/property that allows read-only access into the collection
  • keep in mind that you still may have the appearance of a mutable class if any of your members are mutable - if this is the case, you should copy away whatever state you will want to retain and avoid returning entire mutable objects, unless you copy them before giving them back to the caller - another option is to return only immutable "sections" of the mutable object - thanks to @Brian Rasmussen for encouraging me to expand this point

How to create immutable object from mutable in java?

tl;dr

Either:

  • Make a record like this, in Java 16 and later:
    public record Planet( String name , LocalDate discovered ) {}
  • Or, before Java 16, make a class where you:
    • Mark all member fields final and private.
    • Make getter methods as needed, but no setter methods.

Record

Just use the new records feature in Java 16 (previewed in Java 15).

Define your class as a record when its main job is to transparently and immutably carry data. The compiler implicitly creates a constructor, the getters, hashCode & equals, and toString.

Notice that the getter methods implicitly defined in a record do not begin with the JavaBeans-style get… wording. The getter method is simply the name of member field as defined in the parentheses following the class name.

Of course, if your getter methods provide access to an object that is itself mutable, being contained in a record does nothing to stop the calling programmer from mutating the contained object. Notice in the example class next that both String and LocalDate classes are themselves immutable by design. So the mutability of a contained object is a non-issue here.

package org.example;

import java.time.LocalDate;

public record Planet( String name , LocalDate discovered )
{
}

Using that record.

Planet Earth = new Planet( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );

System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.name: " + Earth.name() );
System.out.println( "Earth.discovered: " + Earth.discovered() );

When run.

Earth
------------------------------------
Earth.name: Earth
Earth.discovered: 2020-01-16

Class

Without the records feature, to make sure a class is immutable you should:

  • Mark the member fields final. This means the field cannot be assigned a different object after the constructor has finished.
  • Mark the member fields private. This means objects of other classes will not have direct access to read or change those fields.
  • Provide getter methods, if needed, but no setter methods. By convention, the JavaBeans-style get… or is… naming is used.

You should also provide appropriate override implementations of hashCode, equals, and toString. Your IDE will help generate the source code for those.

package org.example;

import java.time.LocalDate;
import java.util.Objects;

public class Planète
{
// Member fields
final String name;
final LocalDate discovered;

// Constructors
public Planète ( String name , LocalDate discovered )
{
Objects.requireNonNull( name );
Objects.requireNonNull( discovered );
this.name = name;
this.discovered = discovered;
}

// Getters (read-only immutable class, no setters)
public String getName ( ) { return this.name; }

public LocalDate getDiscovered ( ) { return this.discovered; }

// Object class overrides
@Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
Planète planète = ( Planète ) o;
return getName().equals( planète.getName() ) && getDiscovered().equals( planète.getDiscovered() );
}

@Override
public int hashCode ( )
{
return Objects.hash( getName() , getDiscovered() );
}

@Override
public String toString ( )
{
return "Planète{ " +
"name='" + name + '\'' +
" | discovered=" + discovered +
" }";
}
}

Using that class.

Planète Earth = new Planète( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );

System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.getName: " + Earth.getName() );
System.out.println( "Earth.getDiscoveryDate: " + Earth.getDiscovered() );

Side issues

Do not start a decimal integer literal with 0. The leading zero makes the number octal rather decimal. So your code passing 2020,01,16 should be 2020,1,16.

Never use the Date class, nor Calendar or SimpleDateFormat. These terrible classes are now legacy, supplanted years ago by the modern java.time classes defined in JSR 310. In code above, we used java.time.LocalDate to represent a date-only value, without a time-of-day and without a time zone.

Java, Making a class Immutable

The Tutor class presents many aspects promoting its immutability :

  • the class is final
  • the Set<Student> is protected against the modifications
  • no method allowing to change directly the state of the class

However, the defensive copy of the constructor is not complete.

It also has to copy the Students elements of the array passed. Otherwise the client of the constructor may change any instance of them and make so the Tutor instance mutable such as :

Student[] students = ...;
Tutor tutor = new Tutor(name, students);
students[0].setName("new Name!"); // break the immutability of Tutor

You should write something like :

public Tutor(String name, Student[] students){
this.name = name;
tutees = new HashSet<Student>();
for (Student student : students){
Student copy = new Student(student.getName(),
student.getCourse());
tutees.add(copy);
}
}

Additionally note that the Set returned by getTutees() is unmodifiable but elements contained in are as Student is mutable.
So to make Tutor immutable you also have to create a copy of the Student elements as you return getTutees() such as :

public Set<Student> getTutees(){
Set<Student> students = new HashSet<>();
for (Student student : tutees){
Student copy = new Student(student.getName(),
student.getCourse());
students.add(copy);
}
return Collections.unmodifiableSet(students);
}

As you may notice, getting the immutability in these conditions (an instance that we wish immutable but that contains a collection referencing mutable instances) requires to write more code (to read/to maintain/to test) and to perform more processing (so slower to execute).

If Student was an immutable class, the original getTutees() and the original constructor would be enough.



Related Topics



Leave a reply



Submit