Make Immutable Java Object

Make immutable Java object

Your class is not immutable strictly speaking, it is only effectively immutable. To make it immutable, you need to use final:

private final String name;
private final String age;

Although the difference might seem subtle, it can make a significant difference in a multi-threaded context. An immutable class is inherently thread-safe, an effectively immutable class is thread safe only if it is safely published.

Is it possible to make an object immutable after it is created

An immutable is an unchangeable object, which means that once it's created there is no way to change its fields. Basically, you have to omit all the setters and declare fields as final. Declaring the class as final ensures that none can inherit from it.

public final class Car {

private final List<Door> doors;
private final Engine engine;

public Car(final Engine engine, final List<Door> doors) {
this.engine = engine;
this.doors = doors;
}

// Getters ONLY
}

This example ensures the class is not only immutable but also constant. I highly recommend you to read an article about the Gradients of Immutability.

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.

Proper way to create immutable objects in Java

I guess there is no easier way than something like this (lombok), but this is not plane java so...

@Builder(toBuilder = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
final class SimpleObservable{
private final SimpleObserver observer;
private final List<String> canChange;

public SimpleObservable(List<String> canChangeSet){
canChange = new ArrayList<>(canChangeSet);
observer = SimpleObserver.builder()
.name(canChangeSet.get(0))
.build();
}

public SimpleObservable changeCanChange(List<String> canChangeSet){
SimpleObservableBuilder sob = this.toBuilder();
sob.canChange(new ArrayList<>(canChangeSet));
sob.observer(observer.notifyMe(sob.canChange));
return sob.build();
}
}

@Builder(toBuilder = true)
final class SimpleObserver{
private final String name;

public SimpleObserver notifyMe(List<String> changed){
SimpleObserverBuilder sob = this.toBuilder();
sob.name(changed.get(0));
return sob.build();
}
}

How to make object immutable in java

  • Make constructor private and provide createInstance method with the same attributes as constructor or factory method ? How does it helps ?

Answer: making the constructor private and providing createInstance() (factory method) does not help by itself: it is one of few things you should do in order to allow users to actually use the class and its instances while you still have the control of the way instances are created.

  • Make attributes final - the post fails to explain this point and somewhere I read to avoid the modification accidentally. How can you modify accidentally, when there are no mutators and class is final ? How making an attribute final is helping ?

Answer: declaring a class as final means that the user can't extend it, so it "blocks" the user from this kind of "workaround". Declaring an attribute as final won't allow the user of the class to change it. It cannot be "modified accidentally", but it can be "modified viciously" using reflection. Let's see an example, say you have:

final public class SomeClass {
final Integer i = 1;
}

from another class you can do as follows:

class AnotherClass {

public static void main (String[] args) throws Exception {

SomeClass p = new SomeClass();
Field i =p.getClass().getDeclaredField("i");
i.setAccessible(true);
i.set(p, 5);
System.out.println("p.i = " + p.i); // prints 5
}
}
  • Can instead of factory use builder pattern ?

Answer: you can use the builder pattern or any pattern that helps you control the creation of instances of the class.

Further:
If you want to make sure your class is immutable, make sure that any getter returns a deep-copy of the class member. This technique is called "protective/defensive copy". You can read more about it here

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.

How to make a class immutable with Date object in it?

The simplest thing to do here to make the class immutable is to create a defensive copy of the Date object (when it is passed in the construction parameters). Then don't provide any setters as well. Like this no reference to the Date field in the class is visible to code outside this class and thus the Date can't be modified.

See Tom's comment for required getter characteristic! Thanks for the addition.

(Getter should return a copy of the date field as well, since Date itself is mutable, and changing the returned field from the getter will change the class's field as well.)

For more information and details:
http://www.informit.com/articles/article.aspx?p=31551&seqNum=2



Related Topics



Leave a reply



Submit