Core Java Tutorial

Java Architecture

1. Compilation and interpretation in Java
Java combines both the approaches of compilation and interpretation. First, java compiler compiles the source code into bytecode. At the run time, Java Virtual Machine (JVM) interprets this bytecode and generates machine code which will be directly executed by the machine in which java program runs. So java is both compiled and interpreted language.

Figure 1: Java Architecture
2. Java Virtual Machine (JVM)
JVM is a component which provides an environment for running Java programs. JVM interprets the bytecode into machine code which will be executed the machine in which the Java program runs.
3. Why Java is Platform Independent?
Platform independence is one of the main advantages of Java. In another words, java is portable because the same java program can be executed in multiple platforms without making any changes in the source code. You just need to write the java code for one platform and the same program will run in any platforms. But how does Java make this possible?
 
As discussed early, first the Java code is compiled by the Java compiler and generates the bytecode.  This bytecode will be stored in class filesJava Virtual Machine (JVM) is unique for each platform.   Though JVM is unique for each platform, all interpret the same bytecode and convert it into machine code required for its own platform and this machine code will be directly executed by the machine in which java program runs.  This makes Java platform independent and portable.
Let’s make it more clear with the help of the following diagram.  Here the same compiled Java bytecode is interpreted by two different JVMS to make it run in Windows and Linux platforms.

4. Java Runtime Environment (JRE) and Java Architecture in Detail
Java Runtime Environment contains JVM, class libraries and other supporting components.
As you know the Java source code is compiled into bytecode by Java compiler. This bytecode will be stored in class files. During runtime, this bytecode will be loaded, verified and   JVM interprets the bytecode into machine code which will be executed in the machine in which the Java program runs.

 A Java Runtime Environment performs the following main tasks respectively.
1. Loads the class
This is done by the class loader
2. Verifies the bytecode
This is done by bytecode verifier.
3. Interprets the bytecode
This is done by the JVM
These tasks are described in detail in the subsequent sessions.A detailed Java architecture can be drawn as given below.

Figure 3: Java Architecture in Detail

4.1. Class loader
Class loader loads all the class files required to execute the program. Class loader makes the program secure by separating the namespace for the classes obtained through the network from the classes available locally. Once the bytecode is loaded successfully, then next step is bytecode verification by bytecode verifier.
4.2. Byte code verifier
The bytecode verifier verifies the byte code to see if any security problems are there in the code. It checks the byte code and ensures the followings.
1. The code follows JVM specifications.
2. There is no unauthorized access to memory.
3. The code does not cause any stack overflows.
4. There are no illegal data conversions in the code such as float to object references.
Once this code is verified and proven that there is no security issues with the code, JVM will convert the byte code into machine code which will be directly executed by the machine in which the Java program runs.
4.3. Just in Time Compiler
You might have noticed the component “Just in Time” (JIT) compiler in Figure 3. This is a component which helps the program execution to happen faster. How? Let’s see in detail.
As we discussed earlier when the Java program is executed, the byte code is interpreted by JVM.  But this interpretation is a slower process. To overcome this difficulty, JRE include the component JIT compiler. JIT makes the execution faster.
If the JIT Compiler library exists, when a particular bytecode is executed first time, JIT complier compiles it into native machine code which can be directly executed by the machine in which the Java program runs. Once the byte code is recompiled by JIT compiler, the execution time needed will be much lesser. This compilation happens when the byte code is about to be executed and hence the name “Just in Time”. 
Once the bytecode is compiled into that particular machine code, it is cached by the JIT compiler and will be reused for the future needs. Hence the main performance improvement by using JIT compiler can be seen when the same code is executed again and again because JIT make use of the machine code which is cached and stored.
5. Why Java is Secure?
As you have noticed in the prior session “Java Runtime Environment (JRE) and Java Architecture in Detail”, the byte code is inspected carefully before execution by Java Runtime Environment (JRE).  This is mainly done by the “Class loader” and “Byte code verifier”. Hence a high level of security is achieved.
6. Garbage Collection
Garbage collection is a process by which Java achieves better memory management. As you know, in object oriented programming, objects communicate to each other by passing messages. (If you are not clear about the concepts of objects, please read the prior chapter before continuing in this session).

Whenever an object is created, there will be some memory allocated for this object. This memory will remain as allocated until there are some references to this object. When there is no reference to this object, Java will assume that this object is not used anymore.  When garbage collection process happens, these objects will be destroyed and memory will be reclaimed.

Garbage collection happens automatically. There is no way that you can force garbage collection to happen. There are two methods “System.gc()” and “Runtime.gc()” through which you can make request for garbage collation. But calling these methods also will not force garbage collection to happen and you cannot make sure when this garbage collection will happen.

OOPS



4 pillars of OOPS
Abstraction
Ploymorphism
Inheritance
Encapsulation



Abstraction : Refers to the act of representing the essential features without including background details or explanations
What is Abstraction?
Abstraction is process of hiding the implementation details and showing only the functionality.

Abstraction in java is achieved by using interface and abstract class. Interface give 100% abstraction and abstract class give 0-100% abstraction.

What is Abstract class in Java?
 A class that is declared as abstract is known as abstract class.

Syntax:
abstract class <class-name>{}

An abstract class is something which is incomplete and you cannot create instance of abstract class.
If you want to use it you need to make it complete or concrete by extending it.
A class is called concrete if it does not contain any abstract method and implements all abstract method inherited from abstract class or interface it has implemented or extended.

What is Abstract method in Java?

A method that is declare as abstract and does not have implementation is known as abstract method.
If you define abstract method than class must be abstract.

Syntax:

abstract return_type method_name ();

An abstract method in Java doesn't have body, it’s just a declaration. In order to use abstract method you need to override that method in Subclass.

Example 1 :( Without abstract method)

class Employee extends Person {
   
    private String empCode;

    public String getEmpCode() {
        return empCode;
    }

    public void setEmpCode(String empCode) {
        this.empCode = empCode;
    }
   
}

abstract  class Person {
   
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
   
}

public class Main{
               public static void main(String args[]){
        //INSTIATING AN ABSTRACT CLASS GIVES COMPILE TIME ERROR
        //Person p =  new Person() ;
       
        //THIS REFERENCE VARIABLE CAN ACESS ONLY THOSE METHOD WHICH ARE OVERRIDDEN
        Person person = new Employee();
        person.setName("Kiran Putcha");       
        System.out.println(person.getName());
    }
}

Example 2: (with abstract method)


public class Main{
               public static void main(String args[]){
        TwoWheeler test = new Honda();
        test.run();
    }
}
abstract  class TwoWheeler {
    public abstract void run();
}
class Honda extends TwoWheeler{
               public void run(){
                               System.out.println("Running..");
               }
}


When do you use abstraction?
When you know something needs to be there but not sure how exactly it should look like.

Advantages of Abstraction
By using abstraction, we can separate the things that can be grouped to another type.

Frequently changing properties and methods can be grouped to a separate type so that the main type need not undergo changes. This adds strength to the OOAD principle -"Code should be open for Extension but closed for Modification".

Simplifies the representation of the domain models.

Summary:
-    Use abstraction if you know something needs to be in class but implementation of that varies.
-     In Java you cannot create instance of abstract class , its compiler error.
-    abstract is a keyword in java.
-    A class automatically becomes abstract class when any of its method declared as abstract.
-     abstract method doesn't have method body.
-    Variable cannot be made abstract, its only behavior or methods which would be abstract.
-    If a class extends an abstract class or interface it has to provide implementation to all its abstract method to be a concrete class. Alternatively this class can also be abstract.

Polymorphism:
The word ‘polymorphism’ literally means ‘a state of having many shapes’ or ‘the capacity to take on different forms’. When applied to object oriented programming languages like Java, it describes a language’s ability to process objects of various types and classes through a single, uniform interface.
Polymorphism in Java has two types: Compile time polymorphism (static binding) and Runtime polymorphism (dynamic binding). Method overloading is an example of static polymorphism, while method overriding is an example of dynamic polymorphism.
An important example of polymorphism is how a parent class refers to a child class object.  In fact, any object that satisfies more than one IS-A relationship is polymorphic in nature.
For instance, let’s consider a class Animal and let Cat be a subclass of Animal. So, any cat IS animal. Here, Cat satisfies the IS-A relationship for its own type as well as its super class Animal.
Note: It’s also legal to say every object in Java is polymorphic in nature, as each one passes an IS-A test for itself and also for Object class.

Static Polymorphism:

In Java, static polymorphism is achieved through method overloading. Method overloading means there are several methods present in a class having the same name but different types/order/number of parameters.
At compile time, Java knows which method to invoke by checking the method signatures.  So, this is calledcompile time polymorphism or static binding. The concept will be clear from the following example:
class DemoOverload{
 
    public int add(int x, int y){  //method 1
 
    return x+y;
 
    }
 
    public int add(int x, int y, int z){ //method 2
 
    return x+y+z;
 
    }
 
    public int add(double x, int y){ //method 3
 
    return (int)x+y;
 
    }
 
    public int add(int x, double y){ //method 4
 
    return x+(int)y;
 
    }
 
}
 
class Test{
 
    public static void main(String[] args){
 
    DemoOverload demo=new DemoOverload();
 
    System.out.println(demo.add(2,3));      //method 1 called
 
    System.out.println(demo.add(2,3,4));    //method 2 called
 
    System.out.println(demo.add(2,3.4));    //method 4 called
 
    System.out.println(demo.add(2.5,3));    //method 3 called
 
    }
 
}
In the above example, there are four versions of add methods. The first method takes two parameters while the second one takes three. For the third and fourth methods there is a change of order of parameters.  The compiler looks at the method signature and decides which method to invoke for a particular method call at compile time.

Dynamic Polymorphism:

Suppose a sub class overrides a particular method of the super class. Let’s say, in the program we create an object of the subclass and assign it to the super class reference. Now, if we call the overridden method on the super class reference then the sub class version of the method will be called.
Have a look at the following example.
class Vehicle{
 
    public void move(){
 
    System.out.println(“Vehicles can move!!”);
 
    }
 
}
 
class MotorBike extends Vehicle{
 
    public void move(){
 
    System.out.println(“MotorBike can move and accelerate too!!”);
 
    }
 
}
 
class Test{
 
    public static void main(String[] args){
 
    Vehicle vh=new MotorBike();
 
    vh.move();    // prints MotorBike can move and accelerate too!!
 
    vh=new Vehicle();
 
    vh.move();    // prints Vehicles can move!!
 
    }
 
}
It should be noted that in the first call to move(), the reference type is Vehicle and the object being referenced is MotorBike. So, when a call to move() is made, Java waits until runtime to determine which object is actually being pointed to by the reference.  In this case, the object is of the class MotorBike. So, themove() method of MotorBike class will be called. In the second call to move(), the object is of the classVehicle. So, the move() method of Vehicle will be called.
As the method to call is determined at runtime, this is called dynamic binding or late binding.
Summary:
An object in Java that passes more than one IS-A tests is polymorphic in nature
Every object in Java passes a minimum of two IS-A tests: one for itself and one for Object class
Static polymorphism in Java is achieved by method overloading
Dynamic polymorphism in Java is achieved by method overriding
-overloaded method MUST change the argument list
-overloaded methods CAN change return type
-Overloaded methods CAN change access modifiers
-Overloaded methods CAN declare new or broader exceptions
-A method can be overloaded in the same class or in a subclass

Method Overriding:
Occurs when subclass declares a method that has the same type arguments as the method declared by one of its superclasss
Use: Define behavior that’s specific to a particular subclass type
-          CANNOT override a method marked as final
-          CANNOT override a method marked as static
Can Overloaded methods be overridden too?
Yes, coz compiler will not be binding the method calls since it is overloaded coz it might be overridden now or in future.
Is it possible to override the main methods?
No coz main is static
How to invoke super class ver of overrideen method
Super.overridden mehod


Inheritance

Inheritance allows one class to reuse the functionality provided by its superclasses. The extends clause in a class declaration establishes an inheritance relationship between two classes.
Note that A class may directly extend only one superclass. Each of those subclasses may itself have several subclasses.

Syntax of class to Inherit (Inheritance)

class clsName2 extends clsName1
{
      // class body
}
clsName2 is subclass of clsName1 or clsName1 is a superclass of clsName2. 
There is an important feature of java that relates to class hierarchies and reference variables. To declare a variable that references an object is as below.

Syntax of Variable declaration 

clsName varName;

varName is a name of the variable and clsName is a name of its class. So, varName can reference any object of class clsName. Also note that varName can also reference any object whose class is a subclass of clsName. 
For example,
There is a superclass "Person" have having subclasses "Employee" and "Teacher".   "Employee" class have subclasses "Permanent Employee" and "Contract Employee".
                        
               

As per above example Person class reference can hold of its sub classes i.e. Employee, teacher, Permanent Employee or Contract class object. 
Note that above feature is an important feature of java to implement run-time polymorphism.

Example of Inheritance or Superclass - Subclass Concept

Example : Program that illustrates inheritance in java using person class

class Person
{
     String FirstName;
     String LastName;

     Person(String fName, String lName)
     {
              FirstName = fName;
              LastName = lName;
      }

      void Display()
      {
            System.out.println("First Name : " + FirstName);
            System.out.println("Last Name : " + LastName);
       }
class Student extends Person
{
     int id;
     String standard;
     String instructor;

     Student(String fName, String lName, int nId, String stnd, String instr)
     {
          super(fName,lName);
          id = nId;
          standard = stnd;
          instructor = instr;         
      }
     void Display()
     {
            super.Display();

            System.out.println("ID : " + id);
            System.out.println("Standard : " + standard);
            System.out.println("Instructor : " + instructor);
     }
}

class Teacher extends Person
{
      String mainSubject;
      int salary;
      String type; // Primary or Secondary School teacher

     Teacher(String fName, String lName, String sub, int slry, String sType)
     {
          super(fName,lName);
          mainSubject = sub;
          salary = slry;
          type = sType;         
      }
     void Display()
     {
            super.Display();

            System.out.println("Main Subject : " + mainSubject);
            System.out.println("Salary : " + salary);
            System.out.println("Type : " + type);
     }
}

class InheritanceDemo
{
       public static void main(String args[])
       {
               Person pObj = new Person("Rayan","Miller");
               Student sObj = new Student("Jacob","Smith",1,"1 - B","Roma");
               Teacher tObj = new Teacher("Daniel","Martin","English","6000","Primary Teacher");
               System.out.println("Person :");
               pObj.Display();
               System.out.println("");
               System.out.println("Student :");
               sObj.Display();
               System.out.println("");
               System.out.println("Teacher :");
               tObj.Display();
        }
}
Output
Person :
First Name : Rayan       
Last Name : Miller
Student :
First Name : Jacob
Last Name : Smith
ID : 1
Standard : 1 - B
Instructor : Roma
Teacher :
First Name : Daniel
Last Name : Martin
Main Subject : English
Salary : 6000
Type : Primary Teacher
Encapsulation:
Encapsulation(Binds): Encapsulation means binding.

Encapsulation is a process of binding or wrapping the data and its associated members into a single unit. It is also called data hiding.

Class is the best example of encapsulation.

Another example is Capsule.

Encapsulation can be implemented using private, package-private and protected access modifier.

Encapsulation(information hiding) prevents clients from seeing its inside view.

We can achieve complete encapsulation in java by making members of a class private and access them outside the class only through getters and setters.

Advantages:

1.             Maintenance will be good.  
2.             Encapsulation allows you to change one part of code without affecting other part of code.
3.             By using Encapsulation we can write immutable classes in Java.

Example: 
EncapsulationDemo.java

package com.ranga;

class Student {

  // data members
  private long no;
  private String name;
  private int age;

  // member functions
  public long getNo() {
    return no;
  }

  public void setNo(long no) {
    this.no = no;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return "Student{" +
        "no=" + no +
        ", name='" + name + '\'' +
        ", age=" + age +
        '}';
  }
}

public class EncapsulationDemo
{
    public static void main( String[] args )
    {
      Student student = new Student();
      student.setNo(100);
      student.setName("Ranga");
      student.setAge(25);
      System.out.println(student);
    }
}


Output:
Student{no=100, name='Ranga', age=25}

Note: In the above Student class, we are making data members and it's associated members into a single component or single unit.

Object Class:

The Object class
All classes in JavaTM technology are directly or indirectly derived from the Object class. Some of the subclasses of Object class are - Boolean, Number, Void, Math, String, StringBuffer etc.

Some of the important methods defined in the Object class are given below. These methods are available to all Java classes.
                      I.        boolean equals(Object obj) - The equals method in Object class returns true if two references point to the same object. Some classes like String and Boolean overload this method. The difference between the equals function and the equality operator is covered here.
                     II.        String toString() - The function is used to convert objects to String. If a subclass does not override this method, the method returns a textual representation of the object, which has the following format : <name of the class>@<hash code value of the object>".
                    III.        The following methods related to threads are also defined in Object class -
void notify()
void notifyall()
void wait(long timeout) throws InteruptedException
void wait(long timeout, int nanosec) throws InteruptedException
void wait() throws InteruptedException
Wrapper classes
Corresponding to all the primitive types Java technology defines wrapper classes. Some examples of these wrapper classes are - Character, Boolean, Integer, Double.
Important methods in the Math class
Some of the methods defined in the Math class are used frequently. These are explained below. Besides the functionality, it is important to understand the arguments and return type of these functions.


static double ceil(double(d)) : The method ceil returns the smallest double value equal to a mathematical integer, that is not less than the argument. For example, 


   ceil(3.4) returns 4.0

   ceil(-2.3) returns -2.0

   ceil(3.0) returns 3.0


static double floor(double(d)) : The method floor returns the largest double value equal to a mathematical integer, that is not greater than the argument. For example, 

   floor(3.4) returns 3.0

   floor(-2.3) returns -3.0

   floor(3.0) returns 3.0

static int round (float f) and static long round(double d) : The method round returns the integer closest to the argument.

   round(3.7) returns 4

   round(3.2) returns 3

   round(3.0) returns 3

   round(-3.1) returns -3

String class

The String class is used to implement immutable character strings. This means that the character string cannot be changed once it has been created. Some of the important methods are explained below.
int length() - The number of characters in the String class are returned by the length() method.
String substring(int startIndex)
String substring(int startIndex, int endIndex)
The method substring extracts a substring from a string. The method extracts a string from the startIndex to the index endIndex - 1. If endIndex is not specified then string till the end of input string is returned. The example below illustrates this

   String str = "I am a string";

   int len = str.length();

   String str2 = str.substring(2,5);


After the above statements str2 contains the string "am ". The string str still has the same value "I am a string". The variable len has value 13.

StringBuffer class
The StringBuffer class implements mutable strings. This means that the characters stored in the string and the capacity of the string can be changed.

Garbage Collection

Java technology's Garbage collection is complex. In this section I am only giving a brief overview of Garbage Collection. Java technology supports automatic garbage collection. This means that the programmer does not need to free the memory used by objects. Java technology's runtime environment can claim the memory from the objects that are no longer in use. Objects that are not being referred become candidates for garbage collection. It is important to note that these objects are candidates only. Java technology does not guarantee that Garbage collection would happen on these objects. Before actually freeing up the memory, garbage collector invokes the finalize() method of the Object being freed.
The System.gc() method can be invoked by the program to suggest to Java technology that the Garbage Collector be invoked. However there is no guarantee when the garbage collection would be invoked. There is also no guarantee on the order in which objects will be garbage collected.
The example illustrates when a string Object becomes available for Garbage Collection.
public class GCTest {

   public static void main(String args[]) {

      String a,b;

      String c = new String("test");

      a = c;

      c = null; // The String "test" is not yet

      //available for GC as a still points to "test"

      b = new String("xyz");

      b = c; // String "xyz" is now available for GC.

      a = null;

      //String "test" is now available for GC.

   }

}



Equals and Hashcode:

Java.lang.Object has methods called hasCode() and equals(). These methods play a significant role in the real time application. However its use is not always common to all applications. In some case these methods are overridden to perform certain purpose. In this article I will explain you some concept of these methods and why it becomes necessary to override these methods.
hashCode()
As you know this method provides the has code of an object. Basically the default implementation of hashCode() provided by Object is derived by mapping the memory address to an integer value. If look into the source of Object class , you will find the following code for the hashCode. public native int hashCode(); It indicates that hashCode is the native implementation which provides the memory address to a certain extent. However it is possible to override the hashCode method in your implementation class.
equals()
This particular method is used to make equal comparison between two objects. There are two types of comparisons in Java. One is using “= =” operator and another is “equals()”. I hope that you know the difference between this two. More specifically the “.equals()” refers to equivalence relations. So in broad sense you say that two objects are equivalent they satisfy the “equals()” condition. If you look into the source code of Object class you will find the following code for the equals() method.

public boolean equals(Object obj)
{
return (this == obj);
}
Now we will see when to override the equals() and hashCode() methods and why it is necessary to override these methods. In this regard there is a rule of thumb that if you are going to override the one of the methods( ie equals() and hashCode() ) , you have to override the both otherwise it is a violation of contract made for equals() and hashCode(). Please refer to the Sun’s java docs for the method’s contract. I provide some test case scenario where you will find the significance of these methods. Case-1: You can override the hashCode method in your own way. Please refer to the following example.
 package com.ddlab.core;
 /**
  */
public class Emp
{
private int age ;
public Emp( int age )
{
                super();
                this.age = age;
}
public int hashCode()
{
                return age;
}
}
In the above example class “Emp” the variable age is the significant factor. Here the hashCode value will return the age of the person. Now let us consider the following test harness class.
 package com.ddlab.core;
 /**
  */
public class TestEmp
{
public static void main(String[] args)
{
                Emp emp1 = new Emp(23);
                System.out.println("emp1.hashCode()--->>>"+emp1.hashCode());
}
}
If you run the above program, the output will be the age what you have given i.e. 23. Now question arises whether there is any way we can get the original hashCode(). We can say that if we do not override the hashCode() method what could have been the hashCode of this object. However please do not feel depressed, Java provide another approach even if you have overridden the hashCode() method , still you can get the original hashCode of a particular class. Now run the following test harness program.
 package com.ddlab.core;
 package com.ddlab.core;
 /**
 */
public class TestEmp
{
public static void main(String[] args)
{
                Emp emp1 = new Emp(23);
                System.out.println("Overridden hashCode()--->>>"+emp1.hashCode());
                int originalHashCode = System.identityHashCode(emp1);
                System.out.println("Original hashCode of Emp---->>>"+originalHashCode);
}
}
Here the output will be like this Overridden hashCode()--->>>23 Original hashCode of Emp---->>>8567361 As you know the above number is arbitrary, it depends upon your system. So then why it is necessary to override this method. There is one reason that if want to compare two objects based upon the equals() method. Although in a very simple class like “Emp”, you can achieve without overriding hashCode() method. But if you do this , you are going to violate the contract for the methods hashCode() and hashCode() of the object class. The similar case is for the method equals(). So funcational point is that if want to compare two objects based upon the equals() method you have to override both hashCode() and equals() methods. Please have look into the Emp class with the overridden methods and the related test harness class.
 package com.ddlab.core;
 /**
 */
public class Emp
{
private int age ;
public Emp( int age )
{
                super();
                this.age = age;
}
public int hashCode()
{
                return age;
}
public boolean equals( Object obj )
{
                boolean flag = false;
                Emp emp = ( Emp )obj;
                if( emp.age == age )
                               flag = true;
                return flag;
}
}
The related test harness class is given below.
 package com.ddlab.core;
 /**
 */
public class TestEmp
{
public static void main(String[] args)
{
                Emp emp1 = new Emp(23);
                Emp emp2 = new Emp(23);
                System.out.println("emp1.equals(emp2)--->>>"+emp1.equals(emp2));
}
}
Case- 2 Think of a test scenario where you want to store your objects in a HasSet and you want to find a particular object. First let us see if we do not override the methods and we want to store the objects in the HashSet. Let us analyse the impact of it from the following code.
 package com.ddlab.core;
 /**
 */
public class Emp
{
private int age ;
public Emp( int age )
{
                super();
                this.age = age;
}
}
In the above code it is a normal class. Now let us see the test harness class.
 package com.ddlab.core;
 import java.util.HashSet;
 /**
**/
public class TestEmp
{
public static void main(String[] args)
{
                Emp emp1 = new Emp(23);
                Emp emp2 = new Emp(24);
                Emp emp3 = new Emp(25);
                Emp emp4 = new Emp(26);
                Emp emp5 = new Emp(27);
                HashSet<Emp> hs = new HashSet<Emp>();
                hs.add(emp1);
                hs.add(emp2);
                hs.add(emp3);
                hs.add(emp4);
                hs.add(emp5);
               
                System.out.println("HashSet Size--->>>"+hs.size());
                System.out.println("hs.contains( new Emp(25))--->>>"+hs.contains(new Emp(25)));
                System.out.println("hs.remove( new Emp(24)--->>>"+hs.remove( new Emp(24));
                System.out.println("Now HashSet Size--->>>"+hs.size());
}
}
If you run the above program, the will output will be like the following. HashSet Size--->>>5 hs.contains( new Emp(25))--->>>false hs.remove( new Emp(24)--->>>false Now HashSet Size--->>>5 It means that you can not find the object. However it is not the case for Integer object. You can put object of type Integer in a HashSet and you can try and you can see the effect. Now let us modify the “Emp” class so that we will get over the problems what we faced in the above test harness class.
 package com.ddlab.core;
 /**
  */
public class Emp
{
private int age ;
public Emp( int age )
{
                super();
                this.age = age;
}
public int hashCode()
{
                return age;
}
public boolean equals( Object obj )
{
                boolean flag = false;
                Emp emp = ( Emp )obj;
                if( emp.age == age )
                               flag = true;
                return flag;
}
}
Bottom of Form
Here in the above class, we have overridden the hashCode() and equals() methods. Now if you run the same test harness class, you will get the desired output like the following. HashSet Size--->>>5 hs.contains( new Emp(25))--->>>true hs.remove( new Emp(24))--->>>true Now HashSet Size--->>>4 Case – 3 In this case you want to use your object as key not the value in the HashMap. So you have to override both the methods hashCode() and equals(). However it is left to the reader to create the object and test the feature in a Map. Case-4 If want to make your own immutable object , it will be wiser to override the equals() and hashCode() methods. To test the above programs, please create the appropriate package as mentioned in the program. You can also create your own package and modify the package name in the above programs. You can all the code in your favorable java editor.

Java.lang.String:

1.  A Brief Summary of the String Class

A Java String contains an immutable sequence of Unicode characters. Unlike C/C++, where string is simply an array of char, A Java String is an object of the classjava.lang.
Java String is, however, special. Unlike an ordinary class:
·         String is associated with string literal in the form of double-quoted texts such as "Hello, world!". You can assign a string literal directly into a String variable, instead of calling the constructor to create aString instance.
·         The '+' operator is overloaded to concatenate two String operands. '+' does not work on any other objects such as Point and Circle.
·         String is immutable. That is, its content cannot be modified once it is created. For example, the method toUpperCase() constructs and returns a new String instead of modifying the its existing content.
The commonly-used method in the String class are summarized below. Refer to the JDK API for java.lang.String a complete listing.
// Length
int length()       // returns the length of the String
boolean isEmpty()  // same as thisString.length == 0
 
// Comparison
boolean equals(String another) // CANNOT use '==' or '!=' to compare two Strings in Java
boolean equalsIgnoreCase(String another)
int compareTo(String another)  // return 0 if this string is the same as another;
                               // <0 if lexicographically less than another; or >0
int compareToIgnoreCase(String another)
boolean startsWith(String another)
boolean startsWith(String another, int fromIndex)  // search begins at fromIndex
boolean endsWith(String another)
 
// Searching & Indexing
int indexOf(String search)
int indexOf(String search, int fromIndex)
int indexOf(int character)
int indexOf(int character, int fromIndex)      // search forward starting at fromIndex
int lastIndexOf(String search)
int lastIndexOf(String search, int fromIndex)  // search backward starting at fromIndex
int lastIndexOf(int character)
int lastIndexOf(int character, int fromIndex)
 
// Extracting a char or part of the String (substring)
char charAt(int index)              // index from 0 to String's length - 1
String substring(int fromIndex)
String substring(int fromIndex, int endIndex)  // exclude endIndex
 
// Creating a new String or char[] from the original (Strings are immutable!)
String toLowerCase()
String toUpperCase()
String trim()          // create a new String removing white spaces from front and back
String replace(char oldChar, char newChar)  // create a new String with oldChar replaced by newChar
String concat(String another)               // same as thisString + another
char[] toCharArray()                        // create a char[] from this string
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)  // copy into dst char[]
 
// Static methods for converting primitives to String
static String ValueOf(type arg// type can be primitives or char[]
 
// Static method resulted in a formatted String using format specifiers
static String format(String formattingString, Object... args)   // same as printf()
 
// Regular Expression (JDK 1.4)
boolean matches(String regexe)
String replaceAll(String regexe, String replacement)
String replaceAll(String regexe, String replacement)
String[] split(String regexe)             // Split the String using regexe as delimiter,
                                          // return a String array
String[] split(String regexe, int count)  // for count times only
Static method String.format() (JDK 1.5)
The static method String.format() (introduced in JDK 1.5) can be used to produce a formatted String using C-like printf()'s format specifiers. The format() method has the same form asprintf(). For example,
String.format("%.1f", 1.234);   // returns String "1.2"
String.format() is useful if you need to produce a simple formatted String for some purposes (e.g., used in method toString()). For complex string, use StringBuffer/StringBuilder with aFormatter. If you simply need to send a simple formatted string to the console, use System.out.printf(), e.g.,
System.out.printf("%.1f", 1.234);

2.  String is Really Special

Strings receive special treatment in Java, because they are used frequently in a program. Hence, efficiency (in terms of computation and storage) is crucial.
The designers of Java decided to retain primitive types in an object-oriented language, instead of making everything an object, so as to improve the performance of the language. Primitives are stored in the call stack, which require less storage spaces and are cheaper to manipulate. On the other hand, objects are stored in the program heap, which require complex memory management and more storage spaces.
For performance reason, Java's String is designed to be in between a primitive and a class. The special features in String include:
·         The '+' operator, which performs addition on primitives (such as int and double), is overloaded to operate on String objects. '+' performs concatenation for two String operands.
Java does not support 
operator overloading for software engineering consideration. In a language that supports operator overloading like C++, you can turn a '+' operator to perform a subtraction, resulted in poor codes. The '+' operator is the only operator that is internally overloaded to support string concatenation in Java. Take note that '+' does not work on any two arbitrary objects, such asPoints or Circles.
·         A String can be constructed by either:
1.     directly assigning a string literal to a String reference - just like a primitive, or
2.     via the "new" operator and constructor, similar to any other classes. However, this is not commonly-used and is not recommended.
For example,
String str1 = "Java is Hot";           // Implicit construction via string literal
String str2 = new String("I'm cool");  // Explicit construction via new
In the first statement, str1 is declared as a String reference and initialized with a string literal "Java is Hot". In the second statement, str2 is declared as a String reference and initialized via the newoperator and constructor to contain "I'm cool".
·         String literals are stored in a common pool. This facilitates sharing of storage for strings with the same contents to conserve storage. String objects allocated via new operator are stored in the heap, and there is no sharing of storage for the same contents.

2.1  String Literal vs. String Object


As mentioned, there are two ways to construct a string: implicit construction by assigning a string literal or explicitly creating a String object via the new operator and constructor. For example,
String s1 = "Hello";              // String literal
String s2 = "Hello";              // String literal
String s3 = s1;                   // same reference
String s4 = new String("Hello");  // String object
String s5 = new String("Hello");  // String object
Java has provided a special mechanism for keeping the String literals - in a so-called string common pool. If two string literals have the same contents, they will share the same storage inside the common pool. This approach is adopted to conserve storage for frequently-used strings. On the other hand,String objects created via the new operator and constructor are kept in the heap. Each String object in the heap has its own storage just like any other object. There is no sharing of storage in heap even if twoString objects have the same contents.
You can use the method equals() of the String class to compare the contents of two Strings. You can use the relational equality operator '==' to compare the references (or pointers) of two objects. Study the following codes:
s1 == s1;         // true, same pointer
s1 == s2;         // true, s1 and s1 share storage in common pool
s1 == s3;         // true, s3 is assigned same pointer as s1
s1.equals(s3);    // true, same contents
s1 == s4;         // false, different pointers
s1.equals(s4);    // true, same contents
s4 == s5;         // false, different pointers in heap
s4.equals(s5);    // true, same contents
Important Notes:
·         In the above example, I used relational equality operator '==' to compare the references of two String objects. This is done to demonstrate the differences between string literals sharing storage in the common pool and String objects created in the heap. It is a logical error to use (str1 == str2) in your program to compare the contents of two Strings.
·         String can be created by directly assigning a String literal which is shared in a common pool. It is uncommon and not recommended to use the new operator to construct a String object in the heap.
[TODO] Explain the method String.intern().

2.2  String is Immutable

Since string literals with the same contents share storage in the common pool, Java's String is designed to be immutable. That is, once a String is constructed, its contents cannot be modified. Otherwise, the other String references sharing the same storage location will be affected by the change, which can be unpredictable and therefore is undesirable. Methods such as toUpperCase() might appear to modify the contents of a String object. In fact, a completely new String object is created and returned to the caller. The original String object will be deallocated, once there is no more references, and subsequently garbage-collected.
Because String is immutable, it is not efficient to use String if you need to modify your string frequently (that would create many new Strings occupying new storage areas). For example,
// inefficient codes
String str = "Hello";
for (int i = 1; i < 1000; ++i) {
   str = str + i;
}
If the contents of a String have to be modified frequently, use the StringBuffer or StringBuilder class instead.

3.  StringBuffer & StringBuilder

As explained earlier, Strings are immutable because String literals with same content share the same storage in the string common pool. Modifying the content of one String directly may cause adverse side-effects to other Strings sharing the same storage.
JDK provides two classes to support mutable strings: StringBuffer and StringBuilder (in core package java.lang) . A StringBuffer or StringBuilder object is just like any ordinary object, which are stored in the heap and not shared, and therefore, can be modified without causing adverse side-effect to other objects.
StringBuilder class was introduced in JDK 1.5. It is the same as StringBuffer class, except that StringBuilder is not synchronized for multi-thread operations. However, for single-thread program,StringBuilder, without the synchronization overhead, is more efficient.

3.1  java.lang.StringBuffer

Read the JDK API specification for java.lang.StringBuffer.
// Constructors
StringBuffer()             // an initially-empty StringBuffer
StringBuffer(int size)     // with the specified initial size
StringBuffer(String s)     // with the specified initial content
 
// Length
int length()
 
// Methods for building up the content
StringBuffer append(type arg// type could be primitives, char[], String, StringBuffer, etc
StringBuffer insert(int offset, arg)
 
// Methods for manipulating the content
StringBuffer delete(int start, int end)
StringBuffer deleteCharAt(int index)
void setLength(int newSize)
void setCharAt(int index, char newChar)
StringBuffer replace(int start, int end, String s)
StringBuffer reverse()
 
// Methods for extracting whole/part of the content
char charAt(int index)
String substring(int start)
String substring(int start, int end)
String toString()
 
// Methods for searching
int indexOf(String searchKey)
int indexOf(String searchKey, int fromIndex)
int lastIndexOf(String searchKey)
int lastIndexOf(String searchKey, int fromIndex)
Take note that StringBuffer is an ordinary object. You need to use a constructor to create a StringBuffer (instead of assigning to a String literal). Furthermore, '+' operator does not apply to objects, including the StringBuffer. You need to use a proper method such as append() or insert() to manipulating a StringBuffer.
To create a string from parts, It is more efficient to use StringBuffer (multi-thread) or StringBuilder (single-thread) instead of via String concatenation. For example,
// Create a string of YYYY-MM-DD HH:MM:SS
int year = 2010, month = 10, day = 10;
int hour = 10, minute = 10, second = 10;
String dateStr = new StringBuilder()
      .append(year).append("-").append(month).append("-").append(day).append(" ")
      .append(hour).append(":").append(minute).append(":").append(second).toString();
System.out.println(dateStr);
   
// StringBuilder is more efficient than String concatenation
String anotherDataStr = year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second;
System.out.println(anotherDataStr);
JDK compiler, in fact, uses both String and StringBuffer to handle string concatenation via the '+' operator. For examples,
String msg = "a" + "b" + "c";
will be compiled into the following codes for better efficiency:
String msg = new StringBuffer().append("a").append("b").append("c").toString();
Two objects are created during the process, an intermediate StringBuffer object and the returned String object.
Rule of Thumb: Strings are more efficient if they are not modified (because they are shared in the string common pool). However, if you have to modify the content of a string frequently (such as a status message), you should use the StringBuffer class (or the StringBuilder described below) instead.

3.2  java.lang.StringBuilder (JDK 1.5)

JDK 1.5 introduced a new StringBuilder class (in package java.lang), which is almost identical to the StringBuffer class, except that it is not synchronized. In other words, if multiple threads are accessing a StringBuilder instance at the same time, its integrity cannot be guaranteed. However, for a single-thread program (most commonly), doing away with the overhead of synchronization makes theStringBuilder faster.
StringBuilder is API-compatible with the StringBuffer class, i.e., having the same set of constructors and methods, but with no guarantee of synchronization. It can be a drop-in replacement forStringBuffer under a single-thread environment.

3.3  Benchmarking String/StringBuffer/StringBuilder

The following program compare the times taken to reverse a long string via a String object and a StringBuffer.
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
// Reversing a long String via a String vs. a StringBuffer
public class StringsBenchMark {
   public static void main(String[] args) {
      long beginTime, elapsedTime;
 
      // Build a long string
      String str = "";
      int size = 16536;
      char ch = 'a';
      beginTime = System.nanoTime();   // Reference time in nanoseconds
      for (int count = 0; count < size; ++count) {
         str += ch;
         ++ch;
         if (ch > 'z') {
            ch = 'a';
         }
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Build String)");
 
      // Reverse a String by building another String character-by-character in the reverse order
      String strReverse = "";
      beginTime = System.nanoTime();
      for (int pos = str.length() - 1; pos >= 0 ; pos--) {
         strReverse += str.charAt(pos);   // Concatenate
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using String to reverse)");
 
      // Reverse a String via an empty StringBuffer by appending characters in the reverse order
      beginTime = System.nanoTime();
      StringBuffer sBufferReverse = new StringBuffer(size);
      for (int pos = str.length() - 1; pos >= 0 ; pos--) {
         sBufferReverse.append(str.charAt(pos));      // append
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuffer to reverse)");
 
      // Reverse a String by creating a StringBuffer with the given String and invoke its reverse()
      beginTime = System.nanoTime();
      StringBuffer sBufferReverseMethod = new StringBuffer(str);
      sBufferReverseMethod.reverse();     // use reverse() method
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuffer's reverse() method)");
 
      // Reverse a String via an empty StringBuilder by appending characters in the reverse order
      beginTime = System.nanoTime();
      StringBuilder sBuilderReverse = new StringBuilder(size);
      for (int pos = str.length() - 1; pos >= 0 ; pos--) {
         sBuilderReverse.append(str.charAt(pos));
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuilder to reverse)");
 
      // Reverse a String by creating a StringBuilder with the given String and invoke its reverse()
      beginTime = System.nanoTime();
      StringBuffer sBuilderReverseMethod = new StringBuffer(str);
      sBuilderReverseMethod.reverse();
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuidler's reverse() method)");
   }
}
Elapsed Time is 332100 usec (Build String)
Elapsed Time is 346639 usec (Using String to reverse)
Elapsed Time is 2028 usec   (Using StringBuffer to reverse)
Elapsed Time is 847 usec    (Using StringBuffer's reverse() method)
Elapsed Time is 1092 usec   (Using StringBuilder to reverse)
Elapsed Time is 836 usec    (Using StringBuidler's reverse() method)
Observe StringBuilder is 2x faster than StringBuffer, and 300x faster than String. The reverse() method is the fastest, which take about the same time for StringBuilder and StringBuffer.

4.  java.util.StringTokenizer

Very often, you need to break a line of texts into tokens delimited by white spaces. The java.util.StringTokenizer class supports this.
For example, the following program reverses the words in a String.
// Reverse the words in a String using StringTokenizer
import java.util.StringTokenizer;
public class StringTokenizerTest {
   public static void main(String[] args) {
      String str = "Monday Tuesday Wednesday Thursday Friday Saturday Sunday";
      String strReverse;
      StringBuilder sb = new StringBuilder();
      StringTokenizer st = new StringTokenizer(str);
   
      while (st.hasMoreTokens()) {
         sb.insert(0, st.nextToken());
         if (st.hasMoreTokens()) {
            sb.insert(0, " ");
         }
      }
      strReverse = sb.toString();
      System.out.println(strReverse);
   }
}
// Constructors
StringTokenizer(String s)  // Constructs a StringTokenizer for the given string,
                           // using the default delimiter set of " \t\n\r\f"
                           // (i.e., blank, tab, newline, carriage-return, and form-feed).
                           // Delimiter characters themselves will not be treated as tokens. 
StrintTokenizer(String s, String delimiterSet)  // Use characters in delimiterSet as delimiters.
 
// Methods
boolean hasNextToken()     // Returns true if next token available
String nextToken()         // Returns the next token
 
// Code Sample
StringTokenizer tokenizer = new StringTokenizer(aString);
while (tokenizer.hasNextToken()) {
   String token = tokenizer.nextToken();
   .....
}
The JDK documentation stated that "StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split() method of String or the java.util.regex package instead."
For example, the following program uses the split() method of the String class to reverse the words of a String.
 
// Reverse the words in a String using split() method of the String class
public class StringSplitTest {
   public static void main(String[] args) {
      String str = "Monday Tuesday Wednesday Thursday Friday Saturday Sunday";
      String[] tokens = str.split("\\s");  // white space '\s' as delimiter
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < tokens.length; ++i) {
         sb.insert(0, tokens[i]);
         if (i < tokens.length - 1) {
            sb.insert(0, " ");
         }
      }
      String strReverse = sb.toString();
      System.out.println(strReverse);
   }
}


Java Serialization:

Java Object Serialization, introduced as part of the ground-breaking feature set that made up JDK 1.1, serves as a mechanism to transform a graph of Java objects into an array of bytes for storage or transmission, such that said array of bytes can be later transformed back into a graph of Java objects.
In essence, the idea of Serialization is to "freeze" the object graph, move the graph around (to disk, across a network, whatever), and then "thaw" the graph back out again into usable Java objects. All this happens more or less magically, thanks to the ObjectInputStream/ObjectOutputStream classes, full-fidelity metadata, and the willingness of programmers to "opt in" to this process by tagging their classes with the Serializable marker interface.
Listing 1 shows a Person class implementing Serializable.
Listing 1. Serializable Person
package com.tedneward;
 
public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
    }
 
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }
 
    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }
 
    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " age=" + age +
            " spouse=" + spouse.getFirstName() +
            "]";
    }    
 
    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
 
}
Once Person has been serialized, it's pretty simple to write an object graph to disk and read it back again, as demonstrated by this JUnit 4 unit test.
Listing 2. Deserializing Person
public class SerTest
{
    @Test public void serializeToDisk()
    {
        try
        {
            com.tedneward.Person ted = new com.tedneward.Person("Ted", "Neward", 39);
            com.tedneward.Person charl = new com.tedneward.Person("Charlotte",
                "Neward", 38);
 
            ted.setSpouse(charl); charl.setSpouse(ted);
 
            FileOutputStream fos = new FileOutputStream("tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(ted);
            oos.close();
        }
        catch (Exception ex)
        {
            fail("Exception thrown during test: " + ex.toString());
        }
        
        try
        {
            FileInputStream fis = new FileInputStream("tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            com.tedneward.Person ted = (com.tedneward.Person) ois.readObject();
            ois.close();
            
            assertEquals(ted.getFirstName(), "Ted");
            assertEquals(ted.getSpouse().getFirstName(), "Charlotte");
 
            // Clean up the file
            new File("tempdata.ser").delete();
        }
        catch (Exception ex)
        {
            fail("Exception thrown during test: " + ex.toString());
        }
    }
}
Nothing you've seen so far is new or exciting — it's Serialization 101 — but it's a good place to start. We'll use Person to discover five things you probably didn't already know about Java Object Serialization.

1. Serialization allows for refactoring

Serialization permits a certain amount of class variation, such that even after refactoring, ObjectInputStream will still read it just fine.
The critical things that the Java Object Serialization specification can manage automatically are:
·         Adding new fields to a class
·         Changing the fields from static to nonstatic
·         Changing the fields from transient to nontransient
Going the other way (from nonstatic to static or nontransient to transient) or deleting fields requires additional massaging, depending on the degree of backward compatibility you require.

Refactoring a serialized class

Knowing that Serialization allows for refactoring, let's see what happens when we decide to add a new field to the Person class.
PersonV2, shown in Listing 3, introduces a field for gender to the original Person class.
Listing 3. Adding a new field to serialized Person
enum Gender
{
    MALE, FEMALE
}
 
public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a, Gender g)
    {
        this.firstName = fn; this.lastName = ln; this.age = a; this.gender = g;
    }
  
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public Gender getGender() { return gender; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }
 
    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setGender(Gender value) { gender = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }
 
    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " gender=" + gender +
            " age=" + age +
            " spouse=" + spouse.getFirstName() +
            "]";
    }    
 
    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
    private Gender gender;
}
Serialization uses a calculated hash based on just about everything in a given source file — method names, field names, field types, access modifiers, you name it — and compares that hash value against the hash value in the serialized stream.
To convince the Java runtime that the two types are in fact the same, the second and subsequent versions of Person must have the same serialization version hash (stored as the private static final serialVersionUID field) as the first one. What we need, therefore, is theserialVersionUID field, which is calculated by running the JDK serialver command against the original (or V1) version of the Person class.
Once we have Person's serialVersionUID, not only can we create PersonV2 objects out of the original object's serialized data (where the new fields appear, they will default to whatever the default value is for a field, most often "null"), but the opposite is also true: we can deserialize originalPerson objects out of PersonV2 data, with no added fuss.

2. Serialization is not secure

It often comes as an unpleasant surprise to Java developers that the Serialization binary format is fully documented and entirely reversible. In fact, just dumping the contents of the binary serialized stream to the console is sufficient to figure out what the class looks like and contains.
This has some disturbing implications vis-a-vis security. When making remote method calls via RMI, for example, any private fields in the objects being sent across the wire appear in the socket stream as almost plain-text, which clearly violates even the simplest security concerns.
Fortunately, Serialization gives us the ability to "hook" the serialization process and secure (or obscure) the field data both before serialization and after deserialization. We can do this by providing a writeObject method on a Serializable object.

Obscuring serialized data

Suppose the sensitive data in the Person class were the age field; after all, a lady never reveals her age and a gentleman never tells. We can obscure this data by rotating the bits once to the left before serialization, and then rotate them back after deserialization. (I'll leave it to you to develop a more secure algorithm, this one's just for example's sake.)
To "hook" the serialization process, we'll implement a writeObject method on Person; and to "hook" the deserialization process, we'll implement a readObject method on the same class. It's important to get the details right on both of these — if the access modifier, parameters, or name are at all different from what's shown in Listing 4, the code will silently fail, and our Person's age will be visible to anyone who looks.
Listing 4. Obscuring serialized data
public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
    }
 
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }
    
    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }
 
    private void writeObject(java.io.ObjectOutputStream stream)
        throws java.io.IOException
    {
        // "Encrypt"/obscure the sensitive data
        age = age >> 2;
        stream.defaultWriteObject();
    }
 
    private void readObject(java.io.ObjectInputStream stream)
        throws java.io.IOException, ClassNotFoundException
    {
        stream.defaultReadObject();
 
        // "Decrypt"/de-obscure the sensitive data
        age = age << 2;
    }
    
    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " age=" + age +
            " spouse=" + (spouse!=null ? spouse.getFirstName() : "[null]") +
            "]";
    }      
 
    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
}
If we need to see the obscured data, we can always just look at the serialized data stream/file. And, because the format is fully documented, it's possible to read the contents of the serialized stream without the class being available.

 

3. Serialized data can be signed and sealed

The previous tip assumes that you want to obscure serialized data, not encrypt it or ensure it hasn't been modified. Although cryptographic encryption and signature management are certainly possible using writeObject and readObject, there's a better way.
If you need to encrypt and sign an entire object, the simplest thing is to put it in a javax.crypto.SealedObject and/orjava.security.SignedObject wrapper. Both are serializable, so wrapping your object in SealedObject creates a sort of "gift box" around the original object. You need a symmetric key to do the encryption, and the key must be managed independently. Likewise, you can useSignedObject for data verification, and again the symmetric key must be managed independently.


Comments

Popular posts from this blog

Top 10 technological advances in IT industry

Spring Boot security latest

Spring Boot Application Deployment on Google Cloud Platform (GCP)