How to create immutable class in Java?

UPDATED: 10 December 2019
immutable class

Immutable Object
Once created its state can not be altered.
String is good example of Immutable class in Java which you use in your day-to-day programming.

Read: Why String is immutable in Java?

Most important benefit of immutable class is, It provides thread safety so you don't have to worry about its value getting changed in multi-threaded environment. Also immutable class with valid hashCode() and equals() method is good choice for Map key.

Points to be taken care while creating immutable class.

  • Class must be declared as final so it can not be extended. i.e public final class ClassName.
  • All variables/critical methods should be private so it can not be accessed outside of class. i.e private int x;.
  • Make all mutable variables final so it can not be changed after initialization.
    i.e private final int x;.
  • Initialize all variables via constructor only by performing deep copy. (follow example)
  • Do not provide setter methods for variables. i.e public void setX(int x) {this.x = x;}
  • Return cloned object/variable in getter method rather returning actual object. (follow example)

Source code (ImmutableClassLatLon.java)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Example of Immutable class in java.
 * @author javaQuery
 * @date 2019-12-10
 * @Github: https://github.com/javaquery/Examples
 */
public final class ImmutableClassLatLon {
    private final double latitude;
    private final double longitude;
    private final List<String> labels;

    /**
     * @param latitude
     * @param longitude
     * @param labels
     */
    public ImmutableClassLatLon(double latitude, double longitude, List<String> labels) {
        this.latitude = latitude;
        this.longitude = longitude;
        if(labels != null && !labels.isEmpty()){
            this.labels = new ArrayList<>(labels);
        }else{
            this.labels = null;
        }
    }

    /**
     * Will return new copy of List rather returning reference to current list.
     * @return
     */
    public List<String> getLabels() {
        return labels != null ? new ArrayList<>(labels) : null;
    }

    /**
     * Get new object of ImmutableClassLatLon with updated label.
     * @param label
     * @return ImmutableClassLatLon
     */
    public ImmutableClassLatLon addLabel(String label){
        List<String> temporary = new ArrayList<>();
        if(labels != null && !labels.isEmpty()){
            temporary.addAll(labels);
        }
        temporary.add(label);
        return new ImmutableClassLatLon(latitude, longitude, temporary);
    }

    public static void main(String[] args) {
        ImmutableClassLatLon classLatLon = new ImmutableClassLatLon(23.0225, 72.5714, Arrays.asList("India"));
        System.out.println("classLatLon address: " + classLatLon);

        System.out.println("\n- classLatLon getLabels and add label -");
        System.out.println("classLatLon labels before: " + classLatLon.getLabels());
        /* classLatLon.getLabels() will return new copy of List rather returning reference to current list */
        List<String> localLables = classLatLon.getLabels();
        localLables.add("Hindi");
        System.out.println("localLables: " + localLables);
        System.out.println("classLatLon labels after: " + classLatLon.getLabels());

        System.out.println("\n- add new label to classLatLon -");
        System.out.println("classLatLon add label before: " + classLatLon.getLabels());
        /* When new label is added it will return new object of ImmutableClassLatLon rather updating current object's list */
        ImmutableClassLatLon classLatLonNewLabel = classLatLon.addLabel("Asia");
        System.out.println("classLatLon add label after: " + classLatLon.getLabels());
        System.out.println("classLatLonNewLabel address: " + classLatLonNewLabel);
        System.out.println("classLatLonNewLabel labels: " + classLatLonNewLabel.getLabels());

    }
}
Output
As you can see after initialization of ImmutableClassLatLon@36baf30c, user can not change its state even if we provided operation on it.
classLatLon address: com.javaquery.core.immutable.ImmutableClassLatLon@36baf30c

- classLatLon getLabels and add label -
classLatLon labels before: [India]
localLables: [India, Hindi]
classLatLon labels after: [India]

- add new label to classLatLon -
classLatLon add label before: [India]
classLatLon add label after: [India]
classLatLonNewLabel address: com.javaquery.core.immutable.ImmutableClassLatLon@7a81197d
classLatLonNewLabel labels: [India, Asia]
Further Reading
What is the difference between final and effectively final?
How HashMap works internally in Java?
How LinkedHashMap works internally in Java?

0 comments :