13

How can create immutable class in java. if Student class has a relationship(address) how to create immutable class. I want to make the class below immutable

  final public class Student {
        private final Address add;
            private final int sid;
            public Student(int sid, String name, Address add) {
                super();
                this.sid = sid;
                this.name = name;
                this.add = add;
            }
            private final String name;
            public int getSid() {
                return sid;
            }
            public final String getName() {
                return name;
            }
            @Override
            public String toString() {
                return "Student [add=" + add + ", name=" + name + ", sid=" + sid + "]";
            }
            public Address getAdd() {
                return add;
            }


        }

        //I want to make the class below immutable
        public class Address {
            public int getAid() {
                return aid;
            }
            public String getStreet() {
                return street;
            }
            @Override
            public String toString() {
                return "Address [aid=" + aid + ", street=" + street + "]";
            }
            int aid;
            String street;
            public Address(int aid, String street) {
                super();
                this.aid = aid;
                this.street = street;
            }

        }


        public class First {
        public static void main(String[] args) {
            Address myAdd=new Address(179,"Maihill");
            Student st=new Student(99,"anoj",myAdd);
            System.out.println(st.toString());
            myAdd.aid=2376;
            System.out.println(st);
            System.out.println("***************");
            Address pAdd=st.getAdd();
            //Here modified address instance then how we can make immutable.
                pAdd.aid=788;
            System.out.println(st);

        }
        }

Here we can modifiy address instances. Please give me idea

user2832497
  • 149
  • 3
  • 9

8 Answers8

21

The key points for immutable are:

  • no setters methods
  • make variables private and final
  • return lists using Collections.unmodifiableList - never return any mutable field; always return either a copy (deep if appropriate) or an immutable version of the field
  • make class final
  • if variables are changed internally in the class this change is not visible and has no effect outside of the class (including affecting things like equals() and hashcode()).
Scary Wombat
  • 44,617
  • 6
  • 35
  • 64
  • all fields are private and final no setter method – user2832497 Dec 25 '13 at 04:10
  • 1
    Your Address class definition must obey the above rules as well. – Scary Wombat Dec 25 '13 at 04:13
  • The third bullet should be generalized to: "never return any mutable field; always return either a copy (deep if appropriate) or an immutable version of the field". – Ted Hopp Dec 25 '13 at 04:23
  • Funny coincidence, I was just watching this again: http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey does not answer your question, but highly relevant. – BillRobertson42 Dec 25 '13 at 04:28
  • @Bill - are we on the same page? – Scary Wombat Dec 25 '13 at 04:30
  • Couldn't have said it better myself. :) One last point: the last bullet is a little dangerous. Not only must internal changes not be visible outside (including affecting things like `equals()` and `hashcode()`), but such internal state data (e.g., memoized results) should probably be marked `transient`. – Ted Hopp Dec 25 '13 at 04:30
4

In your class Address you should make your fields private (should) and final (must), like this -

public final class Address {       // so no sub-classes can be made.
  private final int aid;           // private and final.
  private final String street;     // private and final.
  // as before.
}

You also cannot have setter methods, but when the fields are final that is not much of a problem (since any setter method would yield a compiler error).

Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
3

Well you made Student semi-immutable well:

  • Its attributes are final
  • they are of immutable types (except the address)
  • and they are initialized in the constructor.

You shall apply the same thing to Address class, so it become immutable, and then all state of Student will be immutable. So it will be:

public final class Address {
    private final int aid;
    private final String street;

    public Address(int aid, String street) {
        this.aid = aid;
        this.street = street;
    }


    public int getAid() {
        return aid;
    }

    public String getStreet() {
        return street;
    }

    ....
}

Fortunately you don't have any modifiable types (some of the most well-knowns are Date, Collections and Maps), otherwise you shall consider them too.

If you had any mutable attributes, you shall copy protect it in constructor, and you shall return a unmodifiable or a copy of it where state leaks.

For example if your Student class had a birthDate attribute, you shall do something like:

public final class Student {
    private final Date birthDate;

    public Student(int sid, String name, Address address, Date birthDate) {
        this.sid = sid;
        this.name = name;
        this.address = address;
        this.birthDate = (birthDate == null) ? null : new Date(birthDate.getTime());
    }

    public Date getBirthDate() {
       return (birthDate == null) ? null : new Date(birthDate.getTime());
    }

    ....

}
Amir Pashazadeh
  • 7,170
  • 3
  • 39
  • 69
1

That's enough. final declared can't be mutated and since there are required in the constructor as arguments, getter are redundant.

final public class Student {

    public final Address add;
    public final int sid;
    public final String name;

    public Student(int sid, String name, Address add) {
        super();
        this.sid = sid;
        this.name = name;
        this.add = add;
    }

    @Override
    public String toString() {
        return "Student [add=" + add + ", name=" + name + ", sid=" + sid + "]";
    }
}

address and studentId/id would be better names for the fields though.

Florian Salihovic
  • 3,921
  • 2
  • 19
  • 26
  • I don't think this satisfies all the conditions of immutable class. You Address object can be modified. – manikanta nvsr May 15 '21 at 11:44
  • @manikantanvsr only if Address has mutable fields. The Student field to an Address instance s declared final, thus it can't be changed. I was proposing a general way of thinking about classes, not just the implementation of one class. – Florian Salihovic May 18 '21 at 13:26
1

To make a class immutable, follow these five rules:

from Effective Java - Third Edition - Chapter 4

  1. Don’t provide methods that modify the object’s state (known as mutators).
  2. Ensure that the class can’t be extended. This prevents careless or malicious subclasses from compromising the immutable behavior of the class by behaving as if the object’s state has changed. Preventing subclassing is generally accomplished by making the class final or provide a private constructor
  3. Make all fields final. This clearly expresses your intent in a manner that is enforced by the system. Also, it is necessary to ensure correct behavior if a reference to a newly created instance is passed from one thread to another without synchronization, as spelled out in the memory model
  4. Make all fields private. This prevents clients from obtaining access to mutable objects referred to by fields and modifying these objects directly. While it is technically permissible for immutable classes to have public final fields containing primitive values or references to immutable objects, it is not recommended because it precludes changing the internal representation in a later release.
  5. Ensure exclusive access to any mutable components. If your class has any fields that refer to mutable objects, ensure that clients of the class cannot obtain references to these objects. Never initialize such a field to a client-provided object reference or return the field from an accessor. Make defensive copies (Item 50) in constructors, accessors, and readObject methods.

Complex.java - Example Immutable Class

public final class Complex {
    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;

    }

    public double realPart() {
        return re;
    }

    public double imaginaryPart() {
        return im;
    }

    public Complex plus(Complex c) {
        return new Complex(re + c.re, im + c.im);

    }

    public Complex minus(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }

    public Complex times(Complex c) {
        return new Complex(re * c.re - im * c.im,
                re * c.im + im * c.re);
    }

    public Complex dividedBy(Complex c) {

        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
                (im * c.re - re * c.im) / tmp);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Complex))
            return false;

        Complex c = (Complex) o;
        // See page 47 to find out why we use compare instead of ==
        return Double.compare(c.re, re) == 0
                && Double.compare(c.im, im) == 0;
    }

    @Override
    public int hashCode() {
        return 31 * Double.hashCode(re) + Double.hashCode(im);

    }

    @Override
    public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}
Community
  • 1
  • 1
1

record

As of Java 16, we can use the records feature (also previewed in Java 14, and previewed in Java 15). Using record is the simplest, hassle-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 printRectanglesIntersect(Rectangle rectangleA, Rectangle rectangleB) {
    System.out.println("Checking Rectangle intersection..");
  }
}
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Player_Neo
  • 1,413
  • 2
  • 19
  • 28
0

First we need to discussed what is immutable in java.
In Java immutable means you state does not change once it has been initialize. the best example of immutable class is String.

We can also create our own immutable class, you have to do following steps.

  • Declare the Class as a final:

    Why? : As per the java final class can not be extended.
    
  • Declare all the fields as a private.

    Why? : Because private member  have not direct access out side of the class
    
  • Don't provide the setter method for that private field

    Why? : If you provide the setter method for the private members so you can access it out side of the class.    
    
  • Make all fields as final.

    Why?: As per the java final variable can be assigned only once.    
    
  • Initialize all the fields via constructor using deep copy.

                import java.util.HashMap;
        import java.util.Iterator;
    
        public final class ImmutableClassExample {
        private final int id;   
        private final String name;  
        private final HashMap<String,String> testMap;   
        public int getId() {
            return id;
        }
    
    
        public String getName() {
        return name;
        }
    
        /**
        * Accessor function for mutable objects
        */
        public HashMap<String, String> getTestMap() {
        //return testMap;
        return (HashMap<String, String>) testMap.clone();
        }
    
        /**
            * Constructor performing Deep Copy
            * @param i
            * @param n
            * @param hm
        */
    
        public ImmutableClassExample(int i, String n, HashMap<String,String> hm){
            System.out.println("Performing Deep Copy for Object initialization");
            this.id=i;
            this.name=n;
            HashMap<String,String> tempMap=new HashMap<String,String>();
            String key;
            Iterator<String> it = hm.keySet().iterator();
            while(it.hasNext()){
                key=it.next();
                tempMap.put(key, hm.get(key));
            }
            this.testMap=tempMap;
        }
    
    
      /**
        * Constructor performing Shallow Copy
        * @param i
        * @param n
     * @param hm
     */
    /**
    public ImmutableClassExample(int i, String n, HashMap<String,String> hm){
    System.out.println("Performing Shallow Copy for Object initialization");
    this.id=i;
    this.name=n;
    this.testMap=hm;
    } 
    
    */
        /**
        * To test the consequences of Shallow Copy and how to avoid it with Deep Copy for creating immutable classes
        * @param args
    */
    public static void main(String[] args) {
        HashMap<String, String> h1 = new HashMap<String,String>();
        h1.put("1", "first");
        h1.put("2", "second");
    
    String s = "original";
    
    int i=10;
    
    ImmutableClassExample ce = new ImmutableClassExample(i,s,h1);
    
        //Lets see whether its copy by field or reference
        System.out.println(s==ce.getName());
        System.out.println(h1 == ce.getTestMap());
        //print the ce values
        System.out.println("ce id:"+ce.getId());
        System.out.println("ce name:"+ce.getName());
        System.out.println("ce testMap:"+ce.getTestMap());
        //change the local variable values
        i=20;
        s="modified";
        h1.put("3", "third");
        //print the values again
            System.out.println("ce id after local variable change:"+ce.getId());
            System.out.println("ce name after local variable change:"+ce.getName());
            System.out.println("ce testMap after local variable change:"+ce.getTestMap());
    
            HashMap<String, String> hmTest = ce.getTestMap();
            hmTest.put("4", "new");
    
            System.out.println("ce testMap after changing variable from accessor 
            methods:"+ce.getTestMap());
    
            }
    
        }
    
JegsVala
  • 1,789
  • 1
  • 19
  • 26
0

you could use the lombok @Valueannotation which creates an immutable class ie. it makes all fields private and final and makes the class itself final as well. used collections are also immutable:

@Value
@Builder
public class Immutable {

    private String str;
    private int value;
    private List<String> strings;

}
pero_hero
  • 2,881
  • 3
  • 10
  • 24