Sunday, 29 March 2015

How to make a java class immutable

 

How to make a java class immutable

Benefits of making a class immutable

Lets first identify benefits of making a class immutable. Immutable classes are
  1. are simple to construct, test, and use
  2. are automatically thread-safe and have no synchronization issues
  3. do not need a copy constructor
  4. do not need an implementation of clone
  5. allow hashCode to use lazy initialization, and to cache its return value
  6. do not need to be copied defensively when used as a field
  7. make good Map keys and Set elements (these objects must not change state while in the collection)
  8. have their class invariant established once upon construction, and it never needs to be checked again
  9. always have “failure atomicity” (a term used by Joshua Bloch) : if an immutable object throws an exception, it’s never left in an undesirable or indeterminate state

Guidelines to make a class immutable

1) Don’t provide “setter” methods — methods that modify fields or objects referred to by fields.
This principle says that for all mutable properties in your class, do not provide setter methods. Setter methods are meant to change the state of object and this is what we want to prevent here.

2) Make all fields final and private
This is another way to increase immutability. Fields declared private will not be accessible outside the class and making them final will ensure the even accidentally you can not change them.

3) Don’t allow subclasses to override methods
The simplest way to do this is to declare the class as final. Final classes in java can not be overridden.

4) Special attention when having mutable instance variables
Always remember that your instance variables will be either mutable or immutable. Identify them and return new objects with copied content for all mutable objects. Immutable variables can be returned safely without extra effort.

A more sophisticated approach is to make the constructor private and construct instances in factory 
methods.

Lets add all above rules and make something concrete class implementation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.Date;
/**
* Always remember that your instance variables will be either mutable or immutable.
* Identify them and return new objects with copied content for all mutable objects.
* Immutable variables can be returned safely without extra effort.
* */
public final class ImmutableClass
{
    /**
    * Integer class is immutable as it does not provide any setter to change its content
    * */
    private final Integer immutableField1;
    /**
    * String class is immutable as it also does not provide setter to change its content
    * */
    private final String immutableField2;
    /**
    * Date class is mutable as it provide setters to change various date/time parts
    * */
    private final Date mutableField;
    //Default private constructor will ensure no unplanned construction of class
    private ImmutableClass(Integer fld1, String fld2, Date date)
    {
        this.immutableField1 = fld1;
        this.immutableField2 = fld2;
        this.mutableField = new Date(date.getTime());
    }
    //Factory method to store object creation logic in single place
    public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
    {
        return new ImmutableClass(fld1, fld2, date);
    }
    //Provide no setter methods
    /**
    * Integer class is immutable so we can return the instance variable as it is
    * */
    public Integer getImmutableField1() {
        return immutableField1;
    }
    /**
    * String class is also immutable so we can return the instance variable as it is
    * */
    public String getImmutableField2() {
        return immutableField2;
    }
    /**
    * Date class is mutable so we need a little care here.
    * We should not return the reference of original instance variable.
    * Instead a new Date object, with content copied to it, should be returned.
    * */
    public Date getMutableField() {
        return new Date(mutableField.getTime());
    }
    @Override
    public String toString() {
        return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
    }
}
Now its time to test our class:

badge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestMain
{
    public static void main(String[] args)
    {
        ImmutableClass im = ImmutableClass.createNewInstance(100,"test", new Date());
        System.out.println(im);
        tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
        System.out.println(im);
    }
    private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField)
    {
        immutableField1 = 10000;
        immutableField2 = "test changed";
        mutableField.setDate(10);
    }
}
Output: (content is unchanged)
100 - test - Tue Oct 30 21:34:08 IST 2012
100 - test - Tue Oct 30 21:34:08 IST 2012
As it can be seen that even changing the instance variables using their references does not change their value, so the class is immutable.

How prepared statement works

How prepared statement works?

Most relational databases handles a JDBC / SQL query in four steps:
  1. Parse the incoming SQL query
  2. Compile the SQL query
  3. Plan/optimize the data acquisition path
  4. Execute the optimized query / acquire and return data
A Statement will always proceed through the four steps above for each SQL query sent to the database. A Prepared Statement pre-executes steps (1) -- (3) in the execution process above. Thus, when creating a Prepared Statement some pre-optimization is performed immediately. The effect is to lessen the load on the database engine at execution time.

Advantages of using prepared statement over simple statement

  • Pre-compilation and DB-side caching of the SQL statement leads to overall faster execution and the ability to reuse the same SQL statement in batches.
  • Automatic prevention of SQL injection attacks by builtin escaping of quotes and other special characters. Note that this requires that you use any of the PreparedStatement setXxx() methods to set the values and not use inline the values in the SQL string by string-concatenating.
  • Apart from above two main usage, prepared statements makes it easy to work with complex objects like BLOBs and CLOBs.


Execution of prepared statements requires following steps:
1) Make a database connection
2) Set values and execute prepared statement
Pre-requisites include setting up a database schema and creating a table at least.

CREATE SCHEMA 'JDBCDemo' ;
CREATE TABLE 'JDBCDemo'.'EMPLOYEE'
(
   'ID' INT NOT NULL DEFAULT 0 ,
    'FIRST_NAME' VARCHAR(100) NOT NULL ,
    'LAST_NAME' VARCHAR(100) NULL ,
    'STAT_CD' TINYINT NOT NULL DEFAULT 0
);
Let’s write above steps in code:

1) Make a database connection

Though we have already learned about it in making JDBC connection, lets recap with this simple code snippet.




Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager
    .getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

2) Set values and execute prepared statement

This is the main step and core part in the post. It requires creating a Statement object and then using it’s executeQuery() method.

PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, 87);
pstmt.setString(2, "Ganesh");
pstmt.setString(3, "More");
pstmt.setInt(4, 5);
int affectedRows = pstmt.executeUpdate();
Let’s see the whole code in working.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class PreparedStatementDemo
{
    public static void main(String[] args)
    {
        Connection connection = null;
        PreparedStatement pstmt = null;
        String sql = "INSERT INTO EMPLOYEE (ID,FIRST_NAME,LAST_NAME,STAT_CD) VALUES (?,?,?,?)";
        try
        {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");
             
            pstmt = connection.prepareStatement(sql);
            pstmt.setInt(1, 87);
            pstmt.setString(2, "Ganesh");
            pstmt.setString(3, "More");
            pstmt.setInt(4, 5);
            int affectedRows = pstmt.executeUpdate();
            System.out.println(affectedRows + " row(s) affected !!");
        }
        catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                pstmt.close();
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
Output:
1 row(s) affected !!