开发者

Immutable class?

How can one make a Java class immutable, what is the need开发者_Go百科 of immutability and is there any advantage to using this?


What is an immutable object?

An immutable object is one that will not change state after it is instantiated.

How to make an object immutable?

In general, an immutable object can be made by defining a class which does not have any of its members exposed, and does not have any setters.

The following class will create an immutable object:

class ImmutableInt {
  private final int value;

  public ImmutableInt(int i) {
    value = i;
  }

  public int getValue() {
    return value;
  }
}

As can be seen in the above example, the value of the ImmutableInt can only be set when the object is instantiated, and by having only a getter (getValue) the object's state cannot be changed after instantiation.

However, there must be care taken that all objects that are referenced by the object must be immutable as well, or it could be possible to change the state of the object.

For example, allowing an reference to an array or ArrayList to be obtained through an getter will allow the internal state to change by changing the array or collection:

class NotQuiteImmutableList<T> {
  private final List<T> list;

  public NotQuiteImmutableList(List<T> list) {
    // creates a new ArrayList and keeps a reference to it.
    this.list = new ArrayList(list); 
  }

  public List<T> getList() {
    return list;
  }
}

The problem with the above code is, that the ArrayList can be obtained through getList and be manipulated, leading to the state of the object itself to be altered, therefore, not immutable.

// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));

// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");

One way to get around this problem is to return a copy of an array or collection when called from a getter:

public List<T> getList() {
  // return a copy of the list so the internal state cannot be altered
  return new ArrayList(list);
}

What is the advantage of immutability?

The advantage of immutability comes with concurrency. It is difficult to maintain correctness in mutable objects, as multiple threads could be trying to change the state of the same object, leading to some threads seeing a different state of the same object, depending on the timing of the reads and writes to the said object.

By having an immutable object, one can ensure that all threads that are looking at the object will be seeing the same state, as the state of an immutable object will not change.


In addition to the answers already given, I'd recommend reading about immutability in Effective Java, 2nd Ed., as there are some details that are easy to miss (e.g. defensive copies). Plus, Effective Java 2nd Ed. is a must-read for every Java developer.


You make a class immutable like this:

public final class Immutable
{
    private final String name;

    public Immutable(String name) 
    {
        this.name = name;
    }

    public String getName() { return this.name; } 

    // No setter;
}

Following are the requirements to make a Java class immutable:

  • Class must be declared as final (So that child classes can’t be created)
  • Members in the class must be declared as final (So that we can’t change the value of it after object creation)
  • Write Getter methods for all the variables in it to get Members values
  • No Setters methods

Immutable classes are useful because
- They're thread-safe.
- They also express something deep about your design: "Can't change this.", When it applies, it's exactly what you need.


Immutability can be achieved mainly in two ways:

  • using final instance attributes to avoid reassignment
  • using a class interface that simply doesn't allow any operation that is able to modify what is inside your class (just getters and no setters

Advantages of immutability are the assumptions that you can make on these object:

  • you gain the no-side effect rule (that is really popular on functional programming languages) and allows you to use objects in a concurrent environment easier, since you know that they can't be changed in a atomic or non atomic way when they are used by many threads
  • implementations of languages can treat these objects in a different way, placing them in zones of memory that are used for static data, enabling faster and safer use of these objects (this is what happens inside the JVM for strings)


Immutable classes cannot reassign values after it is instantiated.The constructor assign values to its private variables. Until the object becomes null, values cannot be changed due to unavailability of setter methods.

to be immutable should satisfy following,

  • Al the variables should be private.
  • No mutator methods(setters) are provided.
  • Avoid method overriding by making class final(Strong Immutability) or methods final(Week immutability).
  • Clone deeply if it contain non primitive or mutable classes.

/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {

// make the variables private
private String Name;

//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}

//provide getters to access values
public String getName() {

return this.Name;
}
}

Advanteges: Immutable objects contains its initialized values until it dies.

java-immutable-classes-short-note


How can one make a Java class immutable?

From JDK 14+ which has JEP 359, we can use "records". It is the simplest and hustle free way of creating Immutable class.

A record class is a shallowly immutable, transparent carrier for a fixed set of fields known as the record components that provides a state description for the record. Each component gives rise to a final field that holds the provided value and an accessor method to retrieve the value. The field name and the accessor name match the name of the component.

Let consider the example of creating an immutable rectangle

record Rectangle(double length, double width) {}

No need to declare any constructor, no need to implement equals & hashCode methods. Just any Records need a name and a state description.

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

If you want to validate the value during object creation, we have to explicitly declare the constructor.

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

The record's body may declare static methods, static fields, static initializers, constructors, instance methods, and nested types.

Instance Methods

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

static fields, methods

Since state should be part of the components we cannot add instance fields to records. But, we can add static fields and methods:

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}

what is the need of immutability and is there any advantage to using this?

Previously posted answers are good enough to justify the need of immutability and it's pros


Immutable class are those whose objects cannot be changed after creation.

Immutable classes is useful for

  • Caching purpose
  • Concurrent environment (ThreadSafe)
  • Hard for inheritance
  • Value cannot be changed in any environment

Example

String Class

Code Example

public final class Student {
    private final String name;
    private final String rollNumber;

    public Student(String name, String rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

    public String getName() {
        return this.name;
    }

    public String getRollNumber() {
        return this.rollNumber;
    }
}


Another way to make immutable object is using Immutables.org library:

Assuming that required dependencies were added, create an abstract class with abstract accessor methods. You can do the same by annotating with interfaces or even annotations (@interface):

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}

It is now possible to generate and then use the generated immutable implementation:

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}


An immutable class is simply a class whose instances cannot be modified.

All ofthe information contained in each instance is fixed for the lifetime of the object, so no changes can ever be observed.

Immutable classes are easier to design, implement, and use than mutable classes.

To make a class immutable, follow these five rules:

  1. Don’t provide methods that modify the object’s state

  2. Ensure that the class can’t be extended.

  3. Make all fields final.

  4. Make all fields private.

  5. Ensure exclusive access to any mutable components.

Immutable objects are inherently thread-safe; they require no synchronization.

Immutable objects can be shared freely.

Immutable objects make great building blocks for other objects


@Jack, Having final fields and setters in class won't make class immutable. final keyword only make sure that a variable is never reassigned. You need to return deep copy of all fields in getter methods. This will make sure that, after getting a object from getter method, internal state of object is not disturbed.


Following are the requirements:

  • The class must be declared as final (So that child classes can’t be created)

  • Data members in the class must be declared as final (So that we can’t change the value of it after object creation)

  • A parameterized constructor should initialize all the fields performing a deep copy (So that data members can’t be modified with object reference)

  • Deep Copy of objects should be performed in the getter methods (To return a copy rather than returning the actual object reference)

  • No setters (To not have the option to change the value of the instance variable)

import java.util.HashMap;
import java.util.Map;
 
// An immutable class
public final class Student {
    private final String name;
    private final int regNo;
    private final Map<String, String> metadata;
 
    public Student(String name, int regNo,
                   Map<String, String> metadata)
    {
        this.name = name;
        this.regNo = regNo;
        Map<String, String> tempMap = new HashMap<>();
        for (Map.Entry<String, String> entry :
             metadata.entrySet()) {
            tempMap.put(entry.getKey(), entry.getValue());
        }
        this.metadata = tempMap;
    }
 
    public String getName() { return name; }
 
    public int getRegNo() { return regNo; }
 
    public Map<String, String> getMetadata()
    {
        Map<String, String> tempMap = new HashMap<>();
        for (Map.Entry<String, String> entry :
             this.metadata.entrySet()) {
            tempMap.put(entry.getKey(), entry.getValue());
        }
        return tempMap;
    }
}
 
// Driver class
class Test {
    public static void main(String[] args)
    {
        Map<String, String> map = new HashMap<>();
        map.put("1", "first");
        map.put("2", "second");
        Student s = new Student("ABC", 101, map);
        System.out.println(s.getName());
        System.out.println(s.getRegNo());
        System.out.println(s.getMetadata());
 
        // Uncommenting below line causes error
        // s.regNo = 102;
 
        map.put("3", "third");
        System.out.println(s.getMetadata()); // Remains unchanged due to deep copy in constructor
 
        s.getMetadata().put("4", "fourth");
        System.out.println(s.getMetadata()); // Remains unchanged due to deep copy in getter
    }
}


As a non-native English speaker, I dislike the common interpretation of "immutable class" being "constructed class objects are immutable"; rather, I myself lean to interpret that as "the class object itself is immutable".

That said, "immutable class" is a kind of immutable object. The difference is when answering what the benefit is. To my knowledge/interpretation, immutable class prevents its objects from runtime behavior modifications.


Most of the answers here are good, and some mentioned the rules but I feel its good to put down in words why& when we need to follow these rules. So am giving the below explanation

  • Declare the member variables as ‘final’ – When we declare them as final , compiler forces us to initialize them. We can initialize directly, by default constructor, by arg constructor.( see sample code below) and after initialization we cannot modify them as they are final.
  • And of course if we try to use Setters for those final variables, compiler throws error.

    public class ImmutableClassExplored {
    
        public final int a; 
        public final int b;
    
        /* OR  
        Generally we declare all properties as private, but declaring them as public 
        will not cause any issues in our scenario if we make them final     
        public final int a = 109;
        public final int b = 189;
    
         */
        ImmutableClassExplored(){
            this. a = 111;
            this.b = 222;
        }
    
        ImmutableClassExplored(int a, int b){
            this.a = a;
            this.b= b;
        }
    }
    

Do we need to declare class as ‘final’?

  • Without final keyword in class declaration, class can be inherited. So subclass can override getter methods. Here we have to consider two scenarios:

1. Having Only primitive members: We do not have problem If class has only primitive members, then we do not need to declare class as final.

2.Having Objects as member variables: If we have objects as member variables then we have to make the members of those objects also final. Means we need to traverse deep down the tree and make all objects/primitives as final which may not be possible all the times. So the workaround is to make the class final which prevents inheritance. So there is no question of subclass overriding getter methods.


@Value annotation of Lombok can be used to generate immutable classes. It's as simple as code below.

@Value
public class LombokImmutable {
    int id;
    String name;
}

As per documentation on Lombok's site:

@Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. The class itself is also made final by default, because immutability is not something that can be forced onto a subclass. Like @Data, useful toString(), equals() and hashCode() methods are also generated, each field gets a getter method, and a constructor that covers every argument (except final fields that are initialized in the field declaration) is also generated.

A fully working example can be found here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜