JavaUsing Java Generics to Leverage Refactoring

Using Java Generics to Leverage Refactoring

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Generic methods and classes are one of Java’s most powerful features for software reuse with compile time type safety. We can write a single generic method, which can be called with arguments of different data types, much like overloaded functions but in a more compact and convenient way. Generics are basically a reusable templates, whether implemented as a class or a method, with a goal to summarize business logic within a single unit. The uniqueness of its capability is in the metamorphosis. Like a fluid taking its form according to the container, Java generics not only enables us to create template classes to suit our immediate business needs but also boosts re-factoring in the long run. Inspired by C++ templates, Java imbibed this feature into the genesis (from J2SE 5.0) pretty well, but with much simpler syntax than its predecessor.

Generic Classes: Java Generic with a C++ Template

To appreciate its simplicity and get a hint of generic class implementation in Java, let’s compare it with C++. Imagine a situation where we need a Stack class that can be alchemized into an integer stack, string stack or any other object stack we want. The operational logic would be defined within the generic classes in such a manner that it will behave according to the chromatin of its creation. C++ templates are powerful, and so are Java generics. If we forget for a moment about the platform and performance issues, Java has an upper hand on the simplicity of code flow over C++; otherwise, they are quite similar.

Element name “E” (this is a convention; we may use any designation: A,B,C…) is the key to generics and, based on the types associated during object creation, the class’s personification is altered.

package org.generic.example;

public class MyStack<E> {
   private final int SIZE;
   private int tos;
   private E[] items;

   public MyStack() {
      this(10);
   }
   public MyStack(int s) {
      this.SIZE = s > 0 ? s : 10;
      tos = -1;
      items = (E[]) new Object[SIZE];
   }
   public void push(E value) {
      if (tos == SIZE - 1)
         System.err.println("Stack is full");
      else
         items[++tos] = value;
   }
   public E pop() {
      E item = null;
      if (tos == -1)
         System.err.println("Stack is empty");
      else
         item = items[tos--];
      return item;
   }
}

package org.generic.example;
public class TestStack {
   public static void main(String[] args) {
      MyStack<String> strStack=new MyStack<>(4);
      strStack.push("January");
      strStack.push("February");
      strStack.push("March");
      strStack.push("April");
      System.out.println(strStack.pop());
      System.out.println(strStack.pop());
      System.out.println(strStack.pop());
      System.out.println(strStack.pop());
      System.out.println(strStack.pop());   //stack is empty now
   }
}

Listing 1: Java implementation of minimal stack

This is the exact replica of the preceding Java code implemented in C++. It’s a little complex but fascinating, isn’t it? 🙂

#include <iostream>
using namespace std;
template <typename E>class MyStack{
private :
   int SIZE;
   int tos;
   E *items;
public:
   MyStack(int=10);
   ~MyStack(){ delete[] items;}
   void push(const E&);
   E pop();
};

template<typename E>MyStack<E>::
   MyStack(int s):SIZE(s>0?s:10),tos(-1),items(new E[SIZE]){}

template<typename E> void MyStack<E>::push(const E &value){
   if (tos == SIZE - 1)
      cout<<"Stack is full"<<endl;
   else
      items[++tos] = value;
}

template<typename E> E MyStack<E>::pop(){
   E item;
   if (tos == -1)
      cout<<"Stack is empty"<<endl;
   else
      item = items[tos--];
   return item;
}

int main(){
   MyStack<string> strStack(4);
   strStack.push("January");
   strStack.push("February");
   strStack.push("March");
   strStack.push("April");
   cout<<strStack.pop()<<endl;
   cout<<strStack.pop()<<endl;
   cout<<strStack.pop()<<endl;
   cout<<strStack.pop()<<endl;
   cout<<strStack.pop()<<endl;   //stack is empty now
   return 0;
}

Listing 2: C++ implementation of minimal stack

Generic Methods and Method Overloading

Compile time type safety enables programmers to catch invalid types at compile time. Take, for example, in the previous stack object (refer to Listing 1), we have created a string stack; now, the compiler would perform type checking to ensure that the stack stores elements of the same type. This capability enhances software reuse and refactoring. On occasion, your exact need is a generic method rather than overloaded methods; for example,

public void sortArrayAndPrint(Integer[] intArray) {
   Arrays.sort(intArray);
   for (Integer e : intArray)
      System.out.print(" " + e);
      System.out.println();
}

public void sortArrayAndPrint(Double[] dblArray) {
   Arrays.sort(dblArray);
   for (Double e : dblArray)
      System.out.print(" " + e);
      System.out.println();
}

We can replace above overloaded methods with one generic method to achieve the same effect.

public <E> void sortArrayAndPrint(E[] elements) {
   Arrays.sort(elements);
   for (E e : elements)
      System.out.print(" "+e);
      System.out.println();
}

In the case of overloaded methods, on call, the compiler attempts to locate a method declaration that has the same method name and parameters that match the argument types provided in the method call. In the generic method, the element type is given a name, “E”, and is based on the types of the arguments passed; the compiler handles each method call appropriately.

Points to be noted:

  • Every element that generics works with must be an object of a class or interface type.
  • When the compiler translates a generic method into Java byte code, it removes the type parameter section and replaces the type parameter with actual types.
  • Generics types are, by default, replaced with type Object. (That is the reason why sometimes when we do not provide any type, still generic classes or methods works. This is not wrong, but is discouraged.)

Now, if we want to find the maximum value of array elements, there is a problem. Generic array elements are objects of some type or, by default, an Object type and a relational operator such as ‘>’, as we know, do not work for reference types. But, there is a way out; we can compare two objects of the same class if that class implements the generic interface Comparable<E>. All the wrapper classes of Java API implements this interface. It has a compareTo() method that can be used appropriately as follows.

public static <E extends Comparable<E>> E findMax(E[] elements) {
   E max = elements[0];
   for (E e : elements)
      if (e.compareTo(max) > 0)
         max = e;
   return max;
}

Can We Overload Generic Methods?

Yes, we can. In that case, two or more generic methods that have same method name will have different method parameters. But, what about overloading a generic method by non-generic overloaded methods that have the same method name and number of parameters? Yes, that is also possible. What actually happens here is, when the compiler encounters a method call, it searches for a method declaration that most precisely matches the method name and argument types specified by the call. Once found, it invokes the method and, if no such method is found, the compiler determines whether there is an inexact but applicable matching method to invoke and take appropriate action.

Wildcards in a Generic Method

A wildcard is a very interesting feature associated with Java generics. Let’s elaborate this with an example. Suppose we want to write a generic function that will add an ArrayList of Numbers that contains both Integer and Double objects. (Recall that Number is the parent class of all numeric wrapper classes such as Integer, Double, Float,… and so forth). So, the following code works fine.

Number[] nos = { 1.3, 1.5, 2.9, 3, 4.5, 66 };
ArrayList<Number> numList = new ArrayList<>();
for (Number n : nos)
   numList.add(n);
System.out.println(addAll(numList));

public static Number addAll(ArrayList<Number> list) {
   double total = 0;
   for (Number n : list)
      total += n.doubleValue();
   return total;
}

Because Number is the parent class of the Integer and Double classes, the addAll() function must also work for

ArrayList<Integer> intList = new ArrayList<>();

or

ArrayList<Double> dblList = new ArrayList<>();

But no; the compiler doesn’t consider parametrized type ArrayList<Number> to be a supertype of either ArrayList<Integer> or ArrayList<Double>. Otherwise, the addAll() function would hold good, considering Double and Integer a subtype of Number. This is a very intriguing aspect of Java where the subtype relationship does not hold. However, in such a scenario, the wildcard comes to the rescue. Let’s reformulate the addAll() method with a wildcard.

public static double addAll(ArrayList<? extends Number> list) {
   double total = 0;
   for (Number n : list)
      total += n.doubleValue();
   return total;
}

In this case, the question mark (?) denotes the wildcard or unknown type and extends Number. This means that the arguments passed to the method must be either Number or a subclass of it and Number is the upper bound of the unknown type. However, the wildcard (?) does not mean any type. We cannot replace Number with (?) in the for loop.

Conclusion

Generics in Java is quite a powerful feature and the code gives a hint on how you really can formulate your logic into a generalized structure so that the code can be more reused than re-written. The article tried to touch upon some of its nooks and corners. The Collection framework of the Java API library is strewn with generic data structures and algorithms that manipulate the elements of those data structures. Harness this capability of Java, and try to implement it in your code and see for yourself how repetitive code becomes compact and simple.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories