Way2Java

Java Performance Tips 1

 1.  Introduction
 2.  When to think of Performance
 3.  JVM’s and JIT Compilers
 4.  Default constructors
 5.  Constructor Hierarchies
 6.  Instance variables Vs Class(static) variables
 7.  Recycling Objects
 8.  Methods
     8.1  Inlining Methods
     8.2  Final Methods
 9.  Thread Synchronization
10. Inner classes
11. String and StringBuffer
    11.1  Accumulating data Using char[] Arrays
    11.2  Using = = and equals() with Strings
    11.3  Using the length() of a String
    11.4  Using toCharArray()
    11.5  Converting strings and double to Wrapper objects
12. I/O Streams
    12.1  Using Chaining
13. Formatting
14. Getting File Information(Disk access)
15. Libraries
    15.1  java.util package
    15.2  Vector vs. ArrayList
    15.3  Setting Initial Array Capacity
    15.4  Setting Initial Array Capacity
    15.4  ArrayList vs. LinkedList
16. Programming in Terms of Interfaces
17. Wrappers
18. Garbage Collection
1. Java Performance Tips: Introduction

Performance has been an important issue for Java developers with any language. Performance includes both speed of execution and the memory, the process, occupies. That is, how to make our program run faster and at the same time using less memory and disk space. There is no correct formula on performance because different applications have different characteristics and bottlenecks and also performance differs across different hardware, operating systems and development tools like virtual machines and compilers. This training provides how tricks such as minimizing object creation and replacing strings with arrays can really pay off in improving your code’s performance. The aim of the two days discussion is to promote the awareness of performance issues in Java so that we can make suitable design and implementation options for specific tasks. Java Performance Tuning offers common-sense advice about what to tune and what to leave alone, emphasizing techniques that provide big performance gains with minimal code

2. Java Performance Tips: When to think of Performance

Programmer should bother not only the performance but also should consider the issues like quality, readability and maintainability etc. Some techniques may ultimately lead to more complex code and should be considered only when required. Sometimes, the performance issues must be considered at design phase itself. If poor performance is built into the architecture itself, it becomes very difficult to tune the performance. For example, choosing an N*N sort of algorithm instead of more efficient N*log(N) one and using many super classes(subclass constructor invokes all its super class constructors) is a bad design. Suppose the code is designed to read the data in a number of chunks and is chunk is processed by many constructors of the super classes and such code requires complete redesign which becomes practically very difficult and time consuming. For this reason, it is important to keep in mind, the performance issues in the design phase itself.

3. Java Performance Tips: JVM’s and JIT Compilers

The latest versions of Java tools offer various approaches to increase performance compared to the original JDK1.0 version of byte code interpretation. The tools approaches includes compiling(interpretation stage) the bytecode into machine code at runtime. Some areas where performance enhancement can be done include a) method inlining and adaptive inlining where methods are inlined basing on their use and number of calls b) synchronization c) compiling Java source code into native code of the operating system etc. As a designer, examining and choosing of appropriate tools is also important. Using a JVM with built-in JIT compiler increases the performance 40 times. Choosing the right tool is very important rather than trying to tune the code for performance aspects.

A small Clock program developed to know the performance:

public class Clock 
{
  long time;
  public Clock() 
  {
    setZero();
  }
  public void setZero() 
  {
    time = System.currentTimeMillis();
  }
  public long timeTaken() 
  {
    return System.currentTimeMillis() - time;
  }
  public void printTime(String str) 
  {
    System.out.println(str + ": " + timeTaken());
  }
  public static void main(String args[])
  {
    Clock c1 = new Clock();
    for(int i = 0; i < 1000000; i++) { }
    c1.printTime("Time tiken for 1000000 iterations");
    c1.setZero();
    
    for(int i = 0; i < 100000000; i++) { }
    c1.printTime("Time tiken for 100000000 iterations");
  }
}

The above screen shows different results when executed different times. It is because a number processes may be running right now on your system, some of which may be in pause, working or stopped etc. Also it depends on the availability of JVM free time. The above Clock program is used in every program to know the performance.

4. Java Performance Tips: Default constructors

Observe the code:

public class Demo
{
  public static void main(String args[])
  {
    Demo d1 = new Demo();
  }
}

In the above code, we created an object of Demo class, d1, by accessing the default constructor; but the default constructor is not provided by the programmer. If the programmer does not provide his own default constructor, the JVM creates one and supplies and this takes time. So, always provide a default constructor in your program.

5. Java Performance Tips: Constructor Hierarchies

Suppose we have the following class hierarchy:

class A {...}
class B extends A {...}
class C extends B {...}

As discussed above, when we create an object of class C, its default constructor calls the default constructors of all super classes up to class Object. This takes some time. For this reason, involve less super classes in the hierarchy.

6. Java Performance Tips: Instance variables Vs Class(static) variables

When the data is the same used by all the objects of the class, better declare the variables as static. Static variables are initialized only once. By declaring non-static, we must assign the value for each object separately which is time consuming and takes more space. The difference makes a lot as the following program explains:

public class StaticVsInstance
{
  int rno;   	 	                  // declare static 
  String name;      	                  // declare static and see the difference
  public void call()
  {
    rno = 35;
    name = "S N Rao";
  }
  public static void main(String args[])
  {
    StaticVsInstance svi[]  = new StaticVsInstance[200000];
    Clock c1 = new Clock();
    for(int i = 0; i < 200000; i++)
    {
      svi[i] = new StaticVsInstance();
      svi[i].call();
    } 
    c1.printTime("Non-static variables");   
  }
} 

If the variables are not static, 2,00,000 objects will have their own memory locations for rno and name. If static, only one memory location is created and shared by all 2,00,000 objects of the class.

7. Java Performance Tips: Recycling Objects

Instead of creating a number of objects for a single class, if an object job is over, the same object can be reused a number of times as follows:

Demo d1 = new Demo(); // create an object initially

If the job of d1 is over:

d1 = null; // this is, in fact, not required
d1 = new Demo();

Now the object d1 can be used for new data.

8. Methods

There exists an inherent overhead associated with calling methods. These overheads includes actual transfer of control to the called method, parameters passing, return value return to the calling method and establishment of the called method’s stack frame for local variable storage. This type of overheads exists in other programming languages also.

8.1 Java Performance Tips: Inlining Methods

The most efficient style to deal with method call overhead is method inlining. The method inlining can be done by the programmer or a compiler also implicitly. Inlining is nothing but writing the called method code in the calling method itself. Observe the following example:

			// Not Inline code

public class MethodInlining
{
  public int getMaximum(int a, int b)
  {
    return (a > b ? a : b);
  }
  public static void main(String args[])
  {
    MethodInlining mi = new MethodInlining();
    Clock c1 = new Clock();
    for( int i = 0; i < 10000000; i++)
    {
      int c = mi.getMaximum(10,20);
    }
    c1.printTime("Not Inline");   
  }      
}

Now shift the Inline code. Shift the calling method code into the calling method main itself.

                          // Inline code
public class MethodInlining
{
  public static void main(String args[])
  {
    MethodInlining mi = new MethodInlining();
    Clock c1 = new Clock();
    int a = 10, b = 20;
    for( int i = 0; i < 10000000; i++)
    {
      int c = a > b ? a : b;
    }
    c1.printTime("Inline");   
  }
}

With inline code, the performance is improved 2 to 3 times.

8.2 Java Performance Tips: Final Methods

The compiler places final methods as inline methods because subclass methods need not be called. Final methods cannot be overridden by subclass.

public final display() { }

The same style can be applied to classes also by declaring them final. A final class cannot be inherited and thereby all the methods of a final class are implicitly final.

public final class Demo { }

If a subclass method is assigned to a super class, the super class will call subclass overridden method. If the class is not declared as final, the JRE has to check its subclasses whether the super class method is overridden or not. If overridden, it must call the subclass method. For the dynamic method dispatch, it takes a long time and is a overhead. It is very difficult to inline subclass methods as which subclass method is to be called is decided at runtime. So, when the code does not include method overriding, simply declare the super class method as final and also when you do not extend your class, declare it as final.


9. Thread Synchronization

Synchronization is a concept used with threads where you want to give the method access exclusively to an object of a class. We must synchronize the data when other objects should wait until the accessing object completes its job. For example, when one object updates the data, the other object should not be allowed to modify the data. Synchronization is a very big overhead to the system and should be avoided generally. We must synchronize sensitive or critical data only.

In the following program, two methods, one synchronized and the other non-synchronized are called.

public class SyncDemo
{
  public void update()  {  }
  public synchronized void update1()  {  }
  public static void main(String args[])
  {
    SyncDemo sd = new SyncDemo();
                 		   // calling non-synchronized method
    Clock c1 = new Clock();
    for( int i = 0; i < 1000000; i++)
    {
      sd.update();
    }
    c1.printTime("Non-synchronization call time");

			          // calling synchronized method
    c1.setZero();
    for( int i = 0; i < 1000000; i++)
    {
      sd.update1();
    }
    c1.printTime("Synchronization call time");
  }
}

Calling synchronized method is 75 times overhead than non-synchronized method.

Java permits to synchronize a whole method or a block of a method.

			// synchronizing the whole method
public synchronized void update(double balance)
{
  balance = balance - amount;             // important statement
  System.out.println("Hello 1");         // not important statement
}

Java permits to synchronize a part(block) of a method

public void update(double balance)
{
  synchronzied(this)
  {
    balance = balance - amount;    // only important is synchronized
  }
  System.out.println("Hello 1");
  }

Comparatively, the second style gives more performance.

This difference is important in some areas. The Vector and ArrayList works the same. Vector methods are synchronized, but ArrayList methods are not synchronized. For ordinary data, use ArrayList in place of Vector.

10. Java Performance Tips: Inner classes

A class declared in another class is called nested class or inner class. As the specification says, the inner class can access the private data of outer class. But it is against the rules of Java; one call private properties cannot be accessed by other classes.

In the following program, OuterOne is outer class and the InnerOne is inner class. The private method calculate() of outer class is accessed by inner class constructor. Let us see the code and then we will discuss how Java executes the code of private property of outer class by inner class.

public class OuterOne                             // enclosing class
{
  private void calculate() {   }
  class InnerOne		                  // enclosed class or inner class
  {
    public InnerOne()
    {
      calculate();
    } 
  }
  public void outerMethod()
  {
    InnerOne io = new  InnerOne();
  }
  public static void main(String args[])
  {
    OuterOne oo = new OuterOne();
    oo.outerMethod();
  }
}

InnerOne is enclosed within OuterOne. calculate() method is defined within OuterOne. Because the Java Virtual Machine has restrictions on calling private members from outside of their class, a special access method is generated by the compiler and added internally to the OuterOne class. This method has a name access$0(), and it in turns calls calculate(). In other words, a method is generated that can be called from outside of InnerOne, and grant access to a private method of OuterOne. So when calculate() is called, the above generated method access$0() is called, and it in turn calls calculate(). So use inner classes, that too access the private data of the outer, only when the code demands.

11. String and StringBuffer

String is designed to be immutable. A string is a sequence of Unicode characters and represented by an object of String class(no terminating \0). Immutable means once created, they never change. If you reassign a new value, the new value is stored in a new location and the new location reference is stored in the String object and the old value is garbage collected. This increases a lot of overhead and should be avoided. If a string value is to be changed, the programmer should prefer StringBuffer which is mutable.

String str = "Hello";
str = str + "World";

the string "Hello", once created, does not change, but a reference to the string (value) may change. The string reference in str originally points to "Hello", but then is changed to point to a new string formed by concatenating str and "World". The above sequence is implemented internally using code like:

String str = "Hello";
StringBuffer sb = new StringBuffer(str);
sb.append("World");
str = sb.toString();

In other words, the two strings to be concatenated are copied to a temporary string buffer, then copied back. As it is clear, such concatenation a big overhead. We should not use + and += with strings.

public class StringOverhead 
{
  public static void main(String args[]) 
  {                                           // using +
    Clock c1 = new Clock();
    String s1 = "Hello";
    for (int i = 0; i < 10000; i++)
        s1 = s1 + "World";
    c1.printTime("Concatenation with +");

                              // using StringBuffer
    c1.setZero();
    String s2 = "Hello";
    StringBuffer sb = new StringBuffer(s2);
    for (int i = 0; i < 10000; i++)
      sb.append("World");
    String s3 = sb.toString();
    c1.printTime("Concatenation with StringBuffer");
  }
}

String concatenation, nearly 2500 times, makes the process to run slower than using StringBuffer.

11.1 Accumulating data Using char[] Arrays

To concatenate characters, there is a better and effective way than using a StringBuffer. We place the data in a character array and then converting the array into a string.

public class CharArrayDemo
{
  public static void main(String args[])
  {
    char ch[] = new char[10000];
    int counter = 0;
    Clock c1 = new Clock();
    for(int i = 0; i < 10000; i++)
    {
      ch[counter++] = 'A';
    }
    String s1 = new String(ch, 0, ch.length);     // SOP(s1) takes a long time
    c1.printTime("Time");
  }
}

The time taken for the above is very negligible and most probably below 1 millisecond.

11.2 Using == and equals() with Strings

The logical operator, in case of strings, compare whether both the string objects refer to the same location or not whereas equals() method compares whether both strings contain the same characters or not, but not their locations. It is a little bit confusing concept for a novice.

Using == is less expensive (overhead) than using equals() method. To increase the performance, it is better to check first with == and then equals(). If == turns to true, then equals() method is short-circuited(not executed) which sometimes works out.

	if (s1 == s2 || s1.equals(s2))
        {
	    // some code
	}

11.3 Using the length() of a String

Using length() method of a String once or while in a program is not a big botheration. But used in a loop, a number times, may decrease the performance.

public class StringLength
{
  public static void main(String args[])
  {                                        // create a big string first                                  
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < 1000000; i++)
    {
      sb.append('@');
    }
    String s1 = sb.toString();      // convert the StringBuffer into a String

                                    // using length() method
    int counter = 0;
    Clock c1 = new Clock();
    for(int i = 0; i < s1.length(); i++)
    {
      if(s1.charAt(i) == 'S')
	    counter++;
    }
    c1.printTime("Using length() method");

                                   // using a value equivalent to length()
    counter = 0;
    c1.setZero();
    int len = s1.length();
    for(int i = 0; i < len; i++)
    {
      if(s1.charAt(i) == 'S')
	counter++;
    }
    c1.printTime("Using an integer equivalent to length");
  }
}

Using length() method is more expensive than using a precomputed value. Avoid using length() in a tight loop.

11.4 Using toCharArray()

charAt() is a String method similar to length(), which can be expensive if called for every character in a long string. An alternative to this method is use of toCharArray(), which returns a copy of the string in a char[] array fashion.

public class CharAt
{
  public static void main(String args[])
  {	                                        // create a big string first                                  
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < 1000000; i++)
    {
      sb.append('@');
    }
    String s1 = sb.toString();                  // convert the StringBuffer into a String
				                // using charAt()
    int counter = 0;
    int len = s1.length();
    Clock c1 = new Clock();
    for(int i = 0;  i < len; i++) 
    {
      if (s1.charAt(i) == 'x')
         counter++;
    }
    c1.printTime("Using charAt()");
	                                         // using toCharArray()
    counter = 0;
    char ch[] = s1.toCharArray();   // convert the string into a character array
    int len1 = ch.length;
    c1.setZero();
    for (int i = 0; i < len1; i++) 
    {
      if (ch[i] == 'x')
	counter++;
    }
    c1.printTime("using toCharArray()");
  }
}

In other words, converting the characters of a string into a char[] array eliminates the method call overhead of charAt(). But char[] array takes more space. Moreover, length() method of String is more expensive than length variable of an array. First uses a method call and the later uses a runtime descriptor.

11.5 Converting strings and double to Wrapper objects

Suppose you have a string like "35.19" that represents a number, and you'd like to convert it to numeric form. How expensive is such an operation? One way to find out is by writing a small program that creates a Double (a wrapper class for double), from either a string or a number.

public class StringsToNumbers
{
  public static void main(String args[])
  {
    String s1 = "35.19";
    double d1 = 35.19;
    Double d;
            		                       // constructing a Double value from a string
    Clock c1 = new Clock();
    for(int i = 0; i < 100000; i++)
    {
      d = new Double(s1);
    }
    c1.printTime("Converting string to Double");

	                                   // constructing a Double value from a double value
    c1.setZero();
    for(int i = 0; i < 100000; i++)
    {
      d = new Double(d1);
    }
    c1.printTime("Converting double to Double");
  }
}

Constructing a Double from a string value is 60 times expensive to a double value.


12. I/O Streams

The best performance can be achieved by chaining the streams. The following code gives file copying using low level byte streams FileInputStream and FileOutputStream.

    FileInputStream fis = new FileInputStream("abc.txt");
    FileOutputStream fos = new FileOutputStream("def.txt");
    
    long start = System.currentTimeMillis();
    int k;
    while((k = fis.read() ) != -1)
    {
      fos.write(k); 
    }
    long end = System.currentTimeMillis();
    System.out.println("The file copy time is " + (end-start));

The above code takes around 1500 milliseconds to copy a source file of around 1.5 lakhs bytes. To increase the performance, chaining is done as in the following program.

12.1 Using Chaining (Buffering)

The FileInputStream is chained with BufferedInputStream as in the following code.

                   
    FileInputStream fis = new FileInputStream("abc.txt");            
    BufferedInputStream bis = new BufferedInputStream(fis);      

    FileOutputStream fos = new FileOutputStream("def.txt");
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    
    long start = System.currentTimeMillis();
    int k;
    while( ( k = bis.read() ) != -1 )
    {
      bos.write(k);
    }
    long end = System.currentTimeMillis();
    System.out.println("The file copy time is " + (end-start));

Chaining with Buffered streams increases a lot of performance and the time taken is just only 15 milliseconds compared to 1500 milliseconds of the previous code.

13. Formatting

We have seen reading data and how to increase the performance. Now, we will see the performance costs of converting data into different formats. Formatting costs relate to arranging characters in desired style and writing the formats to disk storage.

Different formats are used, in the following program where the same number is squared.

import java.text.*;
public class Squaring1
{
  public static void main(String args[])
  {                                             // without formatting
    Clock c1 = new Clock();
    for(int i = 0; i < 25000; i++)
    {
      String str = "Square of " + i + " is " + i * i;
    }
    c1.printTime("Without formatting");	
				              // formatting with pre-compiled code
    Object objarray[] = new Object[2];
    MessageFormat f = new MessageFormat("Square of {0, number} is {1, number}");
    c1.setZero();
    for (int i = 0; i < 25000; i++) 
    {
      objarray[0] = new Integer(i);
      objarray[1] = new Integer(i * i);
      String str1 = f.format(objarray);
    }
    c1.printTime("Formatting with pre-compiled code");
				              // formatting without pre-compilation code
    Object objarray1[] = new Object[2];
    String str2 = "Square of {0,number} is {1,number}";
    c1.setZero();
    for (int i = 0; i < 25000; i++) 
    {
      objarray1[0] = new Integer(i);
      objarray1[1] = new Integer(i * i);
      String str3 = MessageFormat.format(str2, objarray1);
    }
    c1.printTime("Formatting without pre-compilation code");
  }
}

In fact using message format is too slower than without using format patterns. Message formats can be used for internationalization of the applications and at the same time performance overhead is thought.

14. Getting File Information(Disk access)

Always disk access is very expensive because a language has to communicate with the underlying operating system. Disk access may be required to get the file information like size, reading and writing permissions, to know a directory or a file etc. Sometimes it may be required to know the size of the file, and at the same time, assign the value to a variable in the program. If required again, we can use the same variable without accessing again.

import java.io.*;
public class FileInfo
{
  public static void main(String args[])
  {
    File f1 = new File("abc.txt");
    Clock c1 = new Clock();
    for( int i=0; i<100; i++)
    {
      long len = f1.length();
    }
    c1.printTime("Time taken to know the file length");
  }
}

The length() does not get the size of the file directly. In turn, the JVM uses a system call to get the size of the file. Such system calls are comparatively expensive. One simple way is to avoid querying the same information twice(of course, the file size not getting changed).


15. Libraries

This section considers on some of the performance issues of using classes and methods from the standard Java API libraries.

15.1 System.arraycopy()

To copy an array into another other, the best style is using System.arraycopy() method and is very efficient. In the following program, array copying is done using three styles – using a loop, using arraycopy() method of System and using clone() method of Object class.

public class CopyArray
{
  public static void main(String args[]) 
  {
    int array1[] = new int[1000000];   // create two integer arrays
    int array2[] = new int[1000000];

    for (int i = 0; i < 1000000; i++)     // populate first array, array1
    {
      array1[i] = i;
    }
                      // copy array1 elements into array2 using loop
    Clock c1 = new Clock();
    for (int i = 0; i < 1000000; i++)
    {
      array2[i] = array1[i];
    }
    c1.printTime("Using loop");
			// copy using System.arraycopy()
    c1.setZero();
    System.arraycopy(array1, 0, array2, 0, 1000000);
    c1.printTime("Using arraycopy() method");
				// copy using Object.clone()
    c1.setZero();
    array2 = ( int[] ) array1.clone();
    c1.printTime("Using clone() method");
  }
}


The cloning takes a long time. In cloning, a new memory location is created, the data from the object variable-by-variable is copied into the location.

As JVM uses a JIT compiler, the difference between the loop and arraycopy() is very less.

15.2 java.util package

It is a very big package with a number data storage classes with different options.

15.3 Vector vs. ArrayList

java.util.Vector is used to represent a collection of objects, with a facility of growing the capacity.

JDK 1.2 comes with newer class ArrayList that can be used as a substiture of Vector. A few of the performance differences between Vector and ArrayList are:

1. Vector's methods are synchronized, ArrayList’s are not. This means that Vector is thread-safe, at big extra cost.

2. LinkedList is an alternative for ArrayList but with various performance bottlenecks.

3. When vector capacity gets exhausted with the addition of elements, the capacity increases by 10 every time. ArrayList does not have this constraint. So ArrayList is more conservative in its use of space.

4. It is always better to use collection classes as all are not synchronized. A synchronized version can be obtained using Collections class as follows:

List list = Collections.synchronizedList(new ArrayList());

The returned list is thread-safe but the original list(passed as parameter) is still not synchronized.

Also refer Vector vs ArrayList for more information.

15.4 Setting Initial Array Capacity

Collection classes like ArrayList now and then should grow their capacity of internal data structure to facilitate to add more elements. Addition of more capacity when required is automatic and thereby programmer need not bother and takes more performance overhead. If the number of elements to be added to arraylist is known earlier (at compile time itself), an arraylist with the specific number of elements can be created while creating the arraylist object itself. This increases a lot of performance and is illustrated in the following program.

import java.util.*;
public class ArrayListCapacity
{
  public static void main(String args[]) 
  {
    Object obj = new Object();
            	                          // wihout using initial capacity
    ArrayList list = new ArrayList();
    Clock c1 = new Clock();
    for (int i = 0; i <1000000; i++)
    {
      list.add(obj);
    }
    c1.printTime("Wihout using initial cacpacity");
                                       // Using initial capacity        
    list = new ArrayList(1000000);
    c1.setZero();
    for (int i = 0; i <1000000; i++)
    {
      list.add(obj);
    }
    c1.printTime("Using initial cacpacity");
  }
}
Output screen of ArrayListCapacity.java

If initial capacity is not set, the array list size is increased dynamically many times and is very expensive.

15.5 ArrayList vs. LinkedList

There comes two classes ArrayList and LinkedList to store objects. Internally, ArrayList stores elements as an array form and LinkedList stores elements in node form. Due to their internal style of storing the elements, they come with different performance heads depending the nature of action performed like addition and retrieval.

In the following program elements are inserted at 0 position always and actual 0 position elements are pushed down.

import java.util.*;
public class ArrayListLinkedList
{
  public static void main(String args[])
  {                                            // Using ArrayList
    ArrayList al = new ArrayList();
    Clock c1 = new Clock();
    for (int i = 0; i < 25000; i++)
    {
      al.add(0, new Integer(i));
    }
    c1.printTime("Using ArrayList");
 			                      // Using LinkedList
    LinkedList ll = new LinkedList();
    c1.setZero();
    for (int i = 0; i < 25000; i++)
    {
      ll.add(0, new Integer(i));
    }
    c1.printTime("Using LinkedList");
  }
}
Output screen of ArrayListLinkedList.java

Inserting elements at the beginning of an ArrayList requires that all existing elements be pushed down. This is a very costly overhead. But inserting at the beginning of LinkedList is cheap, because the elements of the structure are connected with each other via linked nodes. when an element is added, the two neighbour nodes are disturbed but not all nodes.

import java.util.*;
public class RandomLookup 
{
  public static void main(String args[]) 
  {
    Object obj;
  			                // Retrieving elements from ArrayList
    ArrayList al = new ArrayList();     // create an array list
    for (int i = 0; i < 25000; i++)     // populate the array list
    {
      al.add(new Integer(i));
    }
    Clock c1 = new Clock();
    for (int i = 0; i < 25000; i++)     // reading all the elements one by one
    {
      obj = al.get(i);
    }
    c1.printTime("Reading elements from an array list");
				        // Retrieving elements from LinkedList
    LinkedList ll = new LinkedList();  // create a linked list
    for (int i = 0; i < 25000; i++)    // populate the linked list
    {
      ll.add(new Integer(i));
    }
    c1.setZero();
    for (int i = 0; i < 25000; i++)   // reading all the elements one by one
    {
      obj = ll.get(i);
    }
    c1.printTime("Reading elements from a linked list");
  }
}
Output screen of RandomLookup.java

Accessing the elements with ArrayList is less expensive than accessing elements with LinkedList.

ArrayList LinkedList
Adding elements Expensive Cheaper
Retrieving elements Cheaper Expensive

To retrieve the elements, it is advised to use Iterators with LinkedList.

16. Programming in terms of Interfaces

The example in the previous section illustrates an important point, namely, that there may be more than one "right" way to represent data. You might, for example, decide that an ArrayList is the best choice for some application, but then later decide that a LinkedList would in fact be a better choice. One way you can make changeovers like this easier to program is by using interface types, instead of actual types of classes that implement the interface. For example, if you say:

	List list = new ArrayList();

instead of saying

        ArrayList list = new ArrayList();

and you do likewise when declaring method parameters, as in

		void display(List list) {    }

instead of

        void display(ArrayList list) {    }

then you can use List everywhere in your programming, and you don’t have to worry too much about whether list is really an ArrayList or a LinkedList. In other words, you program in terms of functionality offered by interface types, and you can switch the actual implementation easily at some later point, for reasons of efficiency.

17. Wrappers

The Java libraries have a lot of wrapper classes, classes used to hold a value of a primitive type such as int or double as an object. For example, you can say:

	Integer obj = new Integer(25);

to create a wrapper class instance. This instance can then be assigned to an Object reference, have its type tested using instanceof, used with collection classes like ArrayList, and so on. One drawback, however, is that wrapper classes have some overhead, both in time and space costs. It's less efficient to extract a value from a wrapper instance than it is to use the value directly. And there are costs in creating all those wrapper instances, both time costs in calling new and constructors, and space costs with the overhead that comes with class instances (a very rough estimate might be that an instance of a class takes 15 bytes, in addition to the size of the actual instance variables).

18. Garbage Collection

The Java language uses a form of automatic memory management known as garbage collection. Memory for objects and arrays is allocated using new, and then reclaimed when no longer in use. Garbage collection is a process of detecting when an object no longer has any references to it, with the object’s memory then reclaimed and made available as free space. Normally, you don't need to worry about garbage collection, but there are cases where it's possible to help the process along a bit. For example, suppose that you have a stack of object references, along with a current stack index, and you pop an entry off the stack:

	Object pop() 
	{
	  return stack[stackp--];
	}

This works pretty well, but what happens to the slot in the stack that was just popped? It still has a reference to some object type, and the object may be very large (like a big array). An identical reference has been passed back to the caller of pop(), but that caller may quickly process and discard the reference, and thus it’s possible that the object reference could be garbage collected, except for one thing – there's still a reference to the object on the stack. So a better way to write this code is:

	Object pop() 
        {
	  Object tmp = stack[stackp];
	  stack[stackp--] = null;
	   return tmp;
	}

In other words, the reference has been set to null. If this was the last reference to the object, then the object is made available for reclaiming by the garbage collector.

This idea can be generalized. If you have a reference to a large, no-longer-used object, and the reference is one that will stay in scope for some time to come, then you might want to clear the reference explicitly, by setting it to null. For example, you might have a class instance that persists throughout the life of your application, and one of the instance's variables references a large object, an object you are no longer using.