HashMap in Java
HashMap store Key/Value pairs just like HashTable. It is faster than HashTable and accept null values and it is not synchronized.
These are the basic fundementals we know about HashMap. However, let us try to go through how Hashmap works in Java.
HashMap works on principle of hashing, we have put(key, value) and get(key) method for storing and retrieving Objects from
HashMap. When we pass Key and Value object to put() method on Java HashMap, HashMap implementation calls hashCode() method on Key object and applies returned hashcode into its own hashing function to find a bucket location for storing Entry object,
important point to mention is that HashMap in Java stores both key and value object as Map.Entry in bucket. Then, there is
another important concept of Collision detection and Collision resolution in Java HashMap
i.e What will happen when two objects have same hashcode?
According to the equals() and hashCode() contract, we know that for two objects to be equal, they need to have same hashcode.
However, the oppositeis not true. i.e Two objects with same hashcode need not necessarily be equal. Here, when 2 objects with
same hashcode is returned, this condition is called collision detection.
To resolve this collision, we should know that HashMap uses LinkedList to store object i.e the Map.Entry Object which comprises of Key and Value will be stored in a LinkedList. When an object is tried to be retrieved by calling the get(Object key) method, the linked list is traversed and if 2 objects are returned which has same hashCode, then it calls the key.equals() method to find the correct node in the linked list and return its associated value. So, it is very important that we maintain unique/unmodifiable keys.
Therefore, it is nice to remember and follow that using immutable, final object with proper equals() and hashcode() implementation would act as perfect Java HashMap keys and improve performance of Java HashMap by reducing collision. Immutability also allows caching there hashcode of different keys which makes overall retrieval process very fast and
suggest that String and various wrapper classes e.g. Integer very good keys in Java HashMap.
Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts
Thursday, June 19, 2014
Fail-Fast And Fail-Safe Iterators/ConcurrentModificationException
Fail-Fast And Fail-Safe Iterators/ConcurrentModificationException
Quite few times, while iterating over a List or collection and trying to perform some operations, we get ConcurrentModificationException. The idea behind getting to know the true reason for this is to know the type of iterator or to be precise, the type of collection that we are iterating on.
There are 2 types of Iterators:
1. Fail-Fast
2. Fail-Safe
The Fail-Fast iterators are ones which fails (And throws ConcurrentModificationException) when the collection's structure (i.e its size) changes upon iteration i.e whenever we add or remove an item from a collection or whenever a different thread adds/removes an item from a collection which is being interating currently, the ConcurrentModificationException is thrown. Collection Classes in java.util uses Fail-Fast iterators
Fail-Safe iterators are designed to overcome this. The classes in the package java.util.concurrent uses Fail-Safe iterators. i.e they do not throw ConcurrentModificationException when the collection structure i.e size changes while it is being in the middle of iteration
Looking at the example below, we see that Iterators on ArrayList from java. util package throws ConcurrentModificationException when during the iteration, we try to add another item to the ArrayList and hence change the structure of the collection. However, CopyOnWriteArrayList class in java.util.concurrent package uses Fail-Safe interators and hence, they do not throw the exception and are thread safe.
Quite few times, while iterating over a List or collection and trying to perform some operations, we get ConcurrentModificationException. The idea behind getting to know the true reason for this is to know the type of iterator or to be precise, the type of collection that we are iterating on.
There are 2 types of Iterators:
1. Fail-Fast
2. Fail-Safe
The Fail-Fast iterators are ones which fails (And throws ConcurrentModificationException) when the collection's structure (i.e its size) changes upon iteration i.e whenever we add or remove an item from a collection or whenever a different thread adds/removes an item from a collection which is being interating currently, the ConcurrentModificationException is thrown. Collection Classes in java.util uses Fail-Fast iterators
Fail-Safe iterators are designed to overcome this. The classes in the package java.util.concurrent uses Fail-Safe iterators. i.e they do not throw ConcurrentModificationException when the collection structure i.e size changes while it is being in the middle of iteration
Looking at the example below, we see that Iterators on ArrayList from java. util package throws ConcurrentModificationException when during the iteration, we try to add another item to the ArrayList and hence change the structure of the collection. However, CopyOnWriteArrayList class in java.util.concurrent package uses Fail-Safe interators and hence, they do not throw the exception and are thread safe.
package com.sample.collectionsexamples;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class FailFastAndFailSafeIteratorDemo {
public static void main(String[] args) {
List arrayList = new ArrayList();
List copyOnWriteArrayList = new CopyOnWriteArrayList<>();
arrayList.add(10);
arrayList.add(20);
arrayList.add(30);
arrayList.add(40);
copyOnWriteArrayList.add(10);
copyOnWriteArrayList.add(20);
copyOnWriteArrayList.add(30);
copyOnWriteArrayList.add(40);
iterateOperation("CopyOnWriteArrayList",copyOnWriteArrayList);
iterateOperation("ArrayList", arrayList);
}
static void iterateOperation(String type, List numbers) {
System.out.println("Iterating and adding item on: "+type);
Iterator itr = numbers.iterator();
while(itr.hasNext()) {
// Add another integer 50 after 40
// This code to add will fail for ArrayList
// throwing ConcurrentModificationException
// But it works fine for CopyOnWriteArrayList
if(itr.next() == 40) {
numbers.add(50);
}
}
System.out.println("Items in the list "+type+" = "+numbers);
}
}
Tuesday, March 4, 2014
Java 7 new features
1. Java 7 Switch String Support
Until the previous version, Switch statements work either with primitive types or enumerated types. Java 7 introduced another type that we can use in Switch statements: the String type. i.e Prior to Java 7, it supported variables and expressions which resolved into int, byte, short or char only (OR) it supported Enums. But now, Java 7 has added supporting String types for Switch statement Case values and with this feature, it will surely save a lot of complex if-else structures and improve the overall code readability. Here is a self explanatory example of Switch with Strings in action. We simply accept the String input and print the corresponding matching case
2. Java 7 Try with resources
The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource. In the below example, the resources declared in the try-with-resources statement are: Connection, Statement and ResultSet from java.sql package. The declaration statement appears within parentheses immediately after the try keyword. The class Connection, Statement and ResultSet, in Java SE 7 and later, implements the interface java.lang.AutoCloseable. Because these instances are declared in a try-with-resource statement, it will be closed regardless of whether the try statement completes normally or abruptly (as a result of the method throwing an SQLException). Prior to Java SE 7, you have to use a finally block to ensure that a resource is closed regardless of whether the try statement completes normally or abruptly.
3. Java 7 Multi catch block
In the pre Java 7 world, you would handle exceptions by writing separate catch blocks for different types of exceptions that can occur in the try block. In case of related exception, the derived class exception are listed above the base class exception, e.g. FileNotFoundException should be listed above IOException since FileNotFoundException is derived from the IOException. Java 7 multi catch block provides you the flexibility to combine such catch blocks, provided you have similar code to handle these exceptions and Java 7 provides this by the help of the pipe (|) operator which seperates the different exceptions. Lets have a look at the below example where we handle all the exceptions thrown by the try block in a single catch block, but still specifying the type of exceptions we are handling:
Java 7 also introduced new support for File IO operations i.e it introduced Java NIO 2.0 API where the most important concepts are : Path Class and the File Change Notifications using Watch service. I will post it as a seperate entry in the blog soon. Till then, Enjoy reading more about the exciting features of Java 7 :)
Until the previous version, Switch statements work either with primitive types or enumerated types. Java 7 introduced another type that we can use in Switch statements: the String type. i.e Prior to Java 7, it supported variables and expressions which resolved into int, byte, short or char only (OR) it supported Enums. But now, Java 7 has added supporting String types for Switch statement Case values and with this feature, it will surely save a lot of complex if-else structures and improve the overall code readability. Here is a self explanatory example of Switch with Strings in action. We simply accept the String input and print the corresponding matching case
package com.java7.features;
import java.util.Scanner;
public class Java7SwitchDemo {
public static void main(String[] args) {
System.out.println(" Sachin\n Dhoni\n Kumble\n ");
Scanner sc = new Scanner(System.in);
String cricketer = sc.nextLine();
System.out.println("\n");
switch (cricketer.toUpperCase()) {
case "SACHIN":
System.out.println("You like batting");
break;
case "DHONI":
System.out.println("You like Wicket keeping");
break;
case "KUMBLE":
System.out.println("You like bowling");
break;
default:
System.out.println("You dont like cricket");
}
}
}
2. Java 7 Try with resources
The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource. In the below example, the resources declared in the try-with-resources statement are: Connection, Statement and ResultSet from java.sql package. The declaration statement appears within parentheses immediately after the try keyword. The class Connection, Statement and ResultSet, in Java SE 7 and later, implements the interface java.lang.AutoCloseable. Because these instances are declared in a try-with-resource statement, it will be closed regardless of whether the try statement completes normally or abruptly (as a result of the method throwing an SQLException). Prior to Java SE 7, you have to use a finally block to ensure that a resource is closed regardless of whether the try statement completes normally or abruptly.
package com.java7.features;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TryWithResources {
public static void main(String[] args) throws SQLException {
try(Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","scott","tiger");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select * from dept"))
{
while(rs.next()) {
System.out.println("Employee Name : "+rs.getString("deptno"));
System.out.println("Salary : "+rs.getString("dnmae"));
}
};
}
}
Note: It should be noted that the resources will be closed in the reverse order of their declaration i.e in the above example, first the ResultSet will be closed followed by Statement interface and then finally, the Connection will get closed.3. Java 7 Multi catch block
In the pre Java 7 world, you would handle exceptions by writing separate catch blocks for different types of exceptions that can occur in the try block. In case of related exception, the derived class exception are listed above the base class exception, e.g. FileNotFoundException should be listed above IOException since FileNotFoundException is derived from the IOException. Java 7 multi catch block provides you the flexibility to combine such catch blocks, provided you have similar code to handle these exceptions and Java 7 provides this by the help of the pipe (|) operator which seperates the different exceptions. Lets have a look at the below example where we handle all the exceptions thrown by the try block in a single catch block, but still specifying the type of exceptions we are handling:
package com.java7.features;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Java7MultiCatch {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new FileReader(
"c:/test.txt"));
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1565:myDB");
} catch (IOException | ClassNotFoundException | SQLException e) {
System.out.println("Multi catch block here!");
e.printStackTrace();
}
}
}
Note: It should be noted that when we use multi catch block, we cannot place the exceptions in a hierarchy seperated by the pipe (|) operator like catch(FileNotFoundException | IOException e) because then the compiler will generate an error that the 'subclass exception (i.e FileNotFoundException) is already caught by an alternative superclass exception (i.e IOException)' which seems to be valid where there is a single exception's stack trace is something we will be dealing with.Java 7 also introduced new support for File IO operations i.e it introduced Java NIO 2.0 API where the most important concepts are : Path Class and the File Change Notifications using Watch service. I will post it as a seperate entry in the blog soon. Till then, Enjoy reading more about the exciting features of Java 7 :)
Saturday, September 5, 2009
Singleton Objects
Singleton objects:
The normal way in which we create an object in java is using the ‘new’ keyword along with the proper constructor. In that way, each time an object is created, we get a new one. However, we may come under scenarios sometimes where we need the same object whenever we access it. That is the time we actually go for creating a singleton object. One very common example is when we have license for only one connection for our database or our JDBC driver has trouble with multi threading. In that case, Singleton ensures that only one connection is made or that only one thread can access to the connection object at any time.
In definitive terms, we can say that a Singleton is an object that can be created but cannot be instantiated by the developers. It does mean that a singleton object has control over how it is created. The restriction on a singleton is that there can be only one instance of a singleton created by the Java Virtual Machine (JVM) and by preventing direct instantiating; we ensure that developers cannot create a second copy.
Let’s take a look at the following example for better understanding of the singleton objects:
If we look at the above example, we find that following are the different rules that need to be applied to make an object singleton.
Rule #1: We should create a default ‘private constructor’. Private constructors ensure that there is no way that an object can be created (instantiated) form outside of the same class.
private SingletonExample() {
}
Rule #2: Create a private static member variable of the class and a public accessor method that returns the singleton objects and does not create a new one every time it is called:
private static SingletonExample ref;
public static SingletonExample getSingletonExample() {
if(ref == null)
ref = new SingletonExample();
return ref;
}
If you look at the method, you will see that when it is called, we first check if the reference is null (which will be the case, when it is called first time) and if it is null, we create the object i.e. instantiate it using the constructor. Note that we can create the object by calling the private constructor here since we have this method in the same class itself and hence have access to private methods/variables/constructors too. If it is however, not null, it means it is already created and hence we return the same instance.
Also, since the member variable ‘ref’ is static, a same copy of it is returned every time allowing the method to return singleton instance.
Rule #3: Mark the accessor method that returns the singleton object as ‘synchronized’: Rule 1 and 2 ensures that the object returned is singleton. Well, not exactly. Imagine what if you have two separate threads say, Thread A and Thread B running and both the threads gets access to the accessor method we created above at the same time. In that case, we still end up in creating two different objects (one by Thread A and other by Thread B). To prevent this, we mark the accessor method as ‘synchronized’ so that only one thread is allowed to access it at any given point of time and hence multithreading also does not ends up in creating 2 different references of a singleton object.
public static synchronized SingletonExample getSingletonExample() {
if(ref == null)
ref = new SingletonExample();
return ref;
}
Rule #4: If we think, there is one more problem that can make our object deviate from its singleton rule. Well, consider what if I clone the already created the singleton object as:
SingletonExample obj = SingletonExample.getSingletonExample();
SingletonExample clone = (SingletonExample) obj.clone();
In the above way, we cloned the already created singleton object and hence ended up in having 2 different copies of the object to play with (1 is the singleton and other one is the cloned one). This violates our singleton principle. So, to prevent this, we need to add the clone() method and make sure that we cannot clone any object of this class by throwing the CloneNotSupportedException.
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Now run the above example. You will find that the name variable is assigned the value of ‘Varun’ even for the second object that is created. This is because we have created a singleton object i.e. a single object is returned every time and hence all the instance variables values are shared.
The normal way in which we create an object in java is using the ‘new’ keyword along with the proper constructor. In that way, each time an object is created, we get a new one. However, we may come under scenarios sometimes where we need the same object whenever we access it. That is the time we actually go for creating a singleton object. One very common example is when we have license for only one connection for our database or our JDBC driver has trouble with multi threading. In that case, Singleton ensures that only one connection is made or that only one thread can access to the connection object at any time.
In definitive terms, we can say that a Singleton is an object that can be created but cannot be instantiated by the developers. It does mean that a singleton object has control over how it is created. The restriction on a singleton is that there can be only one instance of a singleton created by the Java Virtual Machine (JVM) and by preventing direct instantiating; we ensure that developers cannot create a second copy.
Let’s take a look at the following example for better understanding of the singleton objects:
public class SingletonExample {
private static SingletonExample ref;
private String name;
private SingletonExample() {
}
public static synchronized SingletonExample getSingletonExample() {
if(ref == null)
ref = new SingletonExample();
return ref;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public static void main(String[] args) {
SingletonExample singletonExample =
SingletonExample.getSingletonExample();
singletonExample.name = "Varun";
SingletonExample singletonExample2 =
SingletonExample.getSingletonExample();
System.out.println(singletonExample2.name);
}
}If we look at the above example, we find that following are the different rules that need to be applied to make an object singleton.
Rule #1: We should create a default ‘private constructor’. Private constructors ensure that there is no way that an object can be created (instantiated) form outside of the same class.
private SingletonExample() {
}
Rule #2: Create a private static member variable of the class and a public accessor method that returns the singleton objects and does not create a new one every time it is called:
private static SingletonExample ref;
public static SingletonExample getSingletonExample() {
if(ref == null)
ref = new SingletonExample();
return ref;
}
If you look at the method, you will see that when it is called, we first check if the reference is null (which will be the case, when it is called first time) and if it is null, we create the object i.e. instantiate it using the constructor. Note that we can create the object by calling the private constructor here since we have this method in the same class itself and hence have access to private methods/variables/constructors too. If it is however, not null, it means it is already created and hence we return the same instance.
Also, since the member variable ‘ref’ is static, a same copy of it is returned every time allowing the method to return singleton instance.
Rule #3: Mark the accessor method that returns the singleton object as ‘synchronized’: Rule 1 and 2 ensures that the object returned is singleton. Well, not exactly. Imagine what if you have two separate threads say, Thread A and Thread B running and both the threads gets access to the accessor method we created above at the same time. In that case, we still end up in creating two different objects (one by Thread A and other by Thread B). To prevent this, we mark the accessor method as ‘synchronized’ so that only one thread is allowed to access it at any given point of time and hence multithreading also does not ends up in creating 2 different references of a singleton object.
public static synchronized SingletonExample getSingletonExample() {
if(ref == null)
ref = new SingletonExample();
return ref;
}
Rule #4: If we think, there is one more problem that can make our object deviate from its singleton rule. Well, consider what if I clone the already created the singleton object as:
SingletonExample obj = SingletonExample.getSingletonExample();
SingletonExample clone = (SingletonExample) obj.clone();
In the above way, we cloned the already created singleton object and hence ended up in having 2 different copies of the object to play with (1 is the singleton and other one is the cloned one). This violates our singleton principle. So, to prevent this, we need to add the clone() method and make sure that we cannot clone any object of this class by throwing the CloneNotSupportedException.
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Now run the above example. You will find that the name variable is assigned the value of ‘Varun’ even for the second object that is created. This is because we have created a singleton object i.e. a single object is returned every time and hence all the instance variables values are shared.
Friday, September 4, 2009
Serialization in Java
We all know the Java platform allows us to create reusable objects in memory. However, all of those objects exist only as long as the Java virtual machine1 remains running. It would be nice if the objects we create could exist beyond the lifetime of the virtual machine, wouldn't it? Well, with object serialization, you can flatten your objects and reuse them in powerful ways.
Serialization: Object serialization is the process of saving an object's state to a sequence of
bytes, as well as the process of rebuilding those bytes into a live object at some future time.
Following are the set of rules to be implemented when we make an object as Serializable:
Rule #1: The object to be persisted must implement the Serializable interface or inherit that implementation from its object hierarchy
Rule #2: The object to be persisted must mark all nonserializable fields (fields that don't get saved) as transient
Rule #3: The class that is being serialized should declare a serialVersionUID; as private static final long serialVersionUID. This is used to identify the differences between a class's object between a write and read and hence take appropriate actions. If you dont create it, it will be provided by compiler but it has its limitations.
Rule #4: By default, the serialized object would be written to the default stream and that would be taken care by compiler and JVM. However, if you wish to save to some other place like a file or a database, you need to create an OutputStream for it and call the ObjectOutputStream's writeObject method (this is the method that marshalls the actual object fields into sequence of bytes), passing the output stream created. Similarly, at the time of unmarshalling the bytes back to the actual object, we should call the readObject method and cast the received object to its actual type.
Rule #5: If we want to perform some logic (like encrypting a password before saving it and decrypting it upon restoring it), we can override the writeObject and readObject methods with their exact signatures and perform the logic inside those methods and finally call the defaultWriteObject and defaultReadObject from those 2 methods.
Let us consider the above rules with an example:
From the above example, we see each rule is marked in the same color in the code as the one it is explained under. i.e.
Rule# 1: Employee class is a persistent class as it is implementing Serializable
Rule #2: the 'department' field is marked as transient. So, this field wont be persistent. This can be seen when we called the writeObject and then subsequent call to readObject prints null value for the department field. Try to run the above example and you will see that 'though, we have passed 'Java' as the employee's department field, department' will be shown as 'null' when we print that employee object.
Rule #3: We have declared a 'serialVersionUID' for the class. This helps us in Versioning control which is explained below:
Versioning:
Imagine you create a class, instantiate it, and write it out to an object stream. That flattened object sits in the file system for some time. Meanwhile, you update the class file, perhaps adding a new field. What happens when you try to read in the flattened object?
Well, the bad news is that an exception will be thrown -- specifically, the java.io.InvalidClassException -- because all persistent-capable classes are automatically given a unique identifier. If the identifier of the class does not equal the identifier of the flattened object, the exception will be thrown. However, if you really think about it, why should it be thrown just because I added a field? Couldn't the field just be set to its default value and then written out next time?
Yes, but it takes a little code manipulation. The identifier that is part of all classes is maintained in a field called serialVersionUID. If you wish to control versioning, you simply have to provide the serialVersionUID field manually and ensure it is always the same, no matter what changes you make to the classfile.
For trying to know it better, just run the above example once. Then, comment the part that saves or writes the employee object, change the serialVersionUID to 2L or anything other than 1L (or) perform any change from the bleow list that can adversely affect the saved object.
The Sun documentation lists the various class format changes that can adversely affect the restoration of an object (if the serialVersionUID is changed or not provided). A few of these include:
1 Deleting a field, or changing it from non-static or non-transient to static or transient, respectively.
2 Changing the position of classes in a hierarchy.
3 Changing the data type of a primitive field.
You will notice that you get an exception that states:
"IOException--->java.io.InvalidClassException: Employee; local class incompatible: stream classdesc serialVersionUID = 1L, local class serialVersionUID = 2L".
On the other hand, not every change will have a negative effect. Here are some changes to class versions that do not have a detrimental effect on object behavior:
1 Adding fields, which will result in default values (based on data type) being assigned to the new fields upon restoration.
2 Adding classes will still allow an object of the added class to be created, since the class structure information is included in the stream. However, its fields will be set to the default values.
3 Changing the access modifier (public, private, etc.) for a field, since it is still possible to assign a value to the field.
4 Changing a field from static or transient to to non-static or non-transient, respectively.
Rule #4: We have created the employee object. We want to store the object into a File. So, we created the File using FileOutputStream and then created an ObjectOutputStream with the reference of fileoutput stream. Finally called the writeObject method of the stream by passing the employee object. Here, it saves all the fields of the employee object except those which are marked transient (as explained in rule#2) and those will be saved or marshalled as sequence of bytes.
Similarly, We retreive the marshalled sequence of bytes of the employee object by calling the readObject method and casting it back to employee object.
Rule #5: Say, we have a scenario where we want to process some values just before it is been saved. In this case, we can override the writeObject method. So, from our example, remove the comments for the writeObject and readObject method of the exmployee class. In the writeObject, we have performed a logic which cheks for the designation code and updates the proper designation value. After this is done, we call the defaultWriteObject() that is the method which will be called by writeObject if we dont override it. Similarly, we can even perform some logic while unmarshalling (reading) the object in the readObject method. We did nothing here but just called the defaultReadObject method.
NOTE: If we have a class in a inheritance hierarchy such that the superclass implements serializable, but we dont want the subclass to be serialized, then the only way we can acheive it is by overriding the writeObject and readObject methods in subclass with their exact signatures and throwing "NotSerializableException" from those two methods.
Serialization: Object serialization is the process of saving an object's state to a sequence of
bytes, as well as the process of rebuilding those bytes into a live object at some future time.
Following are the set of rules to be implemented when we make an object as Serializable:
Rule #1: The object to be persisted must implement the Serializable interface or inherit that implementation from its object hierarchy
Rule #2: The object to be persisted must mark all nonserializable fields (fields that don't get saved) as transient
Rule #3: The class that is being serialized should declare a serialVersionUID; as private static final long serialVersionUID. This is used to identify the differences between a class's object between a write and read and hence take appropriate actions. If you dont create it, it will be provided by compiler but it has its limitations.
Rule #4: By default, the serialized object would be written to the default stream and that would be taken care by compiler and JVM. However, if you wish to save to some other place like a file or a database, you need to create an OutputStream for it and call the ObjectOutputStream's writeObject method (this is the method that marshalls the actual object fields into sequence of bytes), passing the output stream created. Similarly, at the time of unmarshalling the bytes back to the actual object, we should call the readObject method and cast the received object to its actual type.
Rule #5: If we want to perform some logic (like encrypting a password before saving it and decrypting it upon restoring it), we can override the writeObject and readObject methods with their exact signatures and perform the logic inside those methods and finally call the defaultWriteObject and defaultReadObject from those 2 methods.
Let us consider the above rules with an example:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int empNo;
private String designation;
private transient String department;
public Employee() {
}
public Employee(String name,int empNo,String designation,String department) {
this.name = name;
this.empNo = empNo;
this.designation = designation;
this.department = department;
}
/*private void writeObject(ObjectOutputStream oos) throws IOException {
if(designation.equalsIgnoreCase("SE"))
this.designation = "Software Engineer";
else if(designation.equalsIgnoreCase("SSE"))
this.designation = "Senior Software Engineer";
oos.defaultWriteObject();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
}*/
public String toString() {
return "Employee name is: "+this.name+" and his designation is: "+this.designation+" his id is: "+this.empNo+" " +
"and his department is: "+this.department;
}
}
public class SerializedEmployee {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("emp.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Employee employee = new Employee("Varun",123,"SE","Java");
oos.writeObject(employee);
System.out.println("Object saved to the file");
FileInputStream fis = new FileInputStream("emp.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Employee emp = (Employee) ois.readObject();
System.out.println("Object read is: ");
System.out.println(emp);
} catch (FileNotFoundException e) {
System.out.println("FileNotFoundException");
} catch (IOException e) {
System.out.println("IOException");
} catch (ClassNotFoundException e) {
System.out.println("ClassNotFoundException");
}
}
}
From the above example, we see each rule is marked in the same color in the code as the one it is explained under. i.e.
Rule# 1: Employee class is a persistent class as it is implementing Serializable
Rule #2: the 'department' field is marked as transient. So, this field wont be persistent. This can be seen when we called the writeObject and then subsequent call to readObject prints null value for the department field. Try to run the above example and you will see that 'though, we have passed 'Java' as the employee's department field, department' will be shown as 'null' when we print that employee object.
Rule #3: We have declared a 'serialVersionUID' for the class. This helps us in Versioning control which is explained below:
Versioning:
Imagine you create a class, instantiate it, and write it out to an object stream. That flattened object sits in the file system for some time. Meanwhile, you update the class file, perhaps adding a new field. What happens when you try to read in the flattened object?
Well, the bad news is that an exception will be thrown -- specifically, the java.io.InvalidClassException -- because all persistent-capable classes are automatically given a unique identifier. If the identifier of the class does not equal the identifier of the flattened object, the exception will be thrown. However, if you really think about it, why should it be thrown just because I added a field? Couldn't the field just be set to its default value and then written out next time?
Yes, but it takes a little code manipulation. The identifier that is part of all classes is maintained in a field called serialVersionUID. If you wish to control versioning, you simply have to provide the serialVersionUID field manually and ensure it is always the same, no matter what changes you make to the classfile.
For trying to know it better, just run the above example once. Then, comment the part that saves or writes the employee object, change the serialVersionUID to 2L or anything other than 1L (or) perform any change from the bleow list that can adversely affect the saved object.
The Sun documentation lists the various class format changes that can adversely affect the restoration of an object (if the serialVersionUID is changed or not provided). A few of these include:
1 Deleting a field, or changing it from non-static or non-transient to static or transient, respectively.
2 Changing the position of classes in a hierarchy.
3 Changing the data type of a primitive field.
You will notice that you get an exception that states:
"IOException--->java.io.InvalidClassException: Employee; local class incompatible: stream classdesc serialVersionUID = 1L, local class serialVersionUID = 2L".
On the other hand, not every change will have a negative effect. Here are some changes to class versions that do not have a detrimental effect on object behavior:
1 Adding fields, which will result in default values (based on data type) being assigned to the new fields upon restoration.
2 Adding classes will still allow an object of the added class to be created, since the class structure information is included in the stream. However, its fields will be set to the default values.
3 Changing the access modifier (public, private, etc.) for a field, since it is still possible to assign a value to the field.
4 Changing a field from static or transient to to non-static or non-transient, respectively.
Rule #4: We have created the employee object. We want to store the object into a File. So, we created the File using FileOutputStream and then created an ObjectOutputStream with the reference of fileoutput stream. Finally called the writeObject method of the stream by passing the employee object. Here, it saves all the fields of the employee object except those which are marked transient (as explained in rule#2) and those will be saved or marshalled as sequence of bytes.
Similarly, We retreive the marshalled sequence of bytes of the employee object by calling the readObject method and casting it back to employee object.
Rule #5: Say, we have a scenario where we want to process some values just before it is been saved. In this case, we can override the writeObject method. So, from our example, remove the comments for the writeObject and readObject method of the exmployee class. In the writeObject, we have performed a logic which cheks for the designation code and updates the proper designation value. After this is done, we call the defaultWriteObject() that is the method which will be called by writeObject if we dont override it. Similarly, we can even perform some logic while unmarshalling (reading) the object in the readObject method. We did nothing here but just called the defaultReadObject method.
NOTE: If we have a class in a inheritance hierarchy such that the superclass implements serializable, but we dont want the subclass to be serialized, then the only way we can acheive it is by overriding the writeObject and readObject methods in subclass with their exact signatures and throwing "NotSerializableException" from those two methods.
Subscribe to:
Posts (Atom)
