Saturday, July 19, 2014
Generics is a key element in the Java programming language and a solid understanding is essential for any Java programmer. This article provides an introduction to the concepts around Generics in Java.

Generics Introduction

Generics provide the ability to create classes, methods and interfaces to be used with different types of data in a type-safe manner. Generics by adding type safety to the code enables bugs to be detected at compile time rather than at run time. This provides stability to the implementation. The Collections Framework is a great example for understanding Generics. Collections classes like Lists and Maps can used in a type-safe manner by using Generics.

Generalized classes with Object type

Java has the capability to create generalized classes, method and interfaces by using Object types. Let us consider a quick example of creating a stack class using Object type. In this example, we use an ArrayList of objects to hold the data.

package com.sourcetricks.generics;

import java.util.ArrayList;
import java.util.List;

public class ObjectStack {
   private List<Object> data = new ArrayList<Object>();
 
   public void push(Object item) {
     data.add(item);
   }
 
   public Object pop() {
     if ( data.size() > 0 ) {
        Object val = data.get(data.size()-1);
        data.remove(data.size()-1);
        return val;
     }
     return null;
   }
}

To use this stack our main would something like below. Please note that we need to do the type casting every time we pop an element from the stack. Assuming the intent is to create a stack of String if someone my mistake puts in a integer type then it would result in a ClassCastException. Note that it is not possible to detect these mistakes at compile time. Run-time issues are hard to debug for large projects.

public class JavaGenerics {
   public static void main(String[] args) {
     ObjectStack st = new ObjectStack();
     st.push("string");
     st.push(10);
     int i = (Integer) st.pop();
     String s = (String) st.pop();   
  }
}

Generalized classes with Generic types

Generic types add the type safety that is lacking in the previous example. There is also no need to cast an Object type into the actual data type. Let us redo the previous example using generic types. Note that we have replaced Object type with type parameter T in this example.

public class Stack<T> {
    private List<T> data = new ArrayList<T>();
 
    public void push(T item) {
       data.add(item);
    }
 
    public T pop() {
      if ( data.size() > 0 ) {
        T val = data.get(data.size()-1);
        data.remove(data.size()-1);
        return val;
      }
      return null;
    }
}

To use this stack our main would something like below. Please note when we instantiate the class we specify the concrete type to be used. If we try to add to the stack any type other than String then we would get a compile time error.

public class JavaGenerics {
   public static void main(String[] args) {
     Stack<String> st = new Stack<String>();
     st.push("string1");
     st.push("string2");
     String s1 = st.pop();
     String s2 = st.pop();   
   }
}

Raw types

A raw type is the name of a generic class or interface without any type arguments. We can rewrite our main as below. This approach again makes the code unsafe and using of raw types should be avoided.

public class JavaGenerics {
   public static void main(String[] args) {
     Stack st = new Stack();
     st.push("string1");
     st.push("string2");
     String s1 = (String) st.pop();
     String s2 = (String) st.pop();     
   }
}

The Diamond

In Java 7, we can replace the type arguments required to invoke the constructor with empty <>. The compiler infers the type from the context. Using <> is called diamond. We can rewrite the previous example as below.

public class JavaGenerics {
   public static void main(String[] args) {
     Stack<String> st = new Stack<>();
     st.push("string1");
     st.push("string2");
     String s1 = st.pop();
     String s2 = st.pop();   
   }
}

Generic methods

Generics can be used with methods as well. We can declare generic methods that uses one or more type parameters.

public static <T> boolean myfunc(T a, T b) {
  if ( a == b ) return true;
  return false;
}

Bounded type parameters

When specifying a type parameter we can specify an upper bound from which all type arguments should be derived.

The previous example doesn't make sense for String type. We can restrict the method to be limited to Number type by specifying the superclass as Number.

public static <T extends Number> boolean myfunc(T a, T b) {
  if ( a == b ) return true;
  return false;
}

Generic Wildcards

In generic code, the question mark (?) referred as wildcard represents an unknown type.

  • Upper bounded wildcards are used to relax the restrictions on a variable. To declare an upper bounded wildcard, use the wildcard(?) followed by the extends keywords and the upper bound.
  • The unbounded wildcard type is specified using the wildcard character (?).
  • Lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type. To declare a lower bounded wildcard is expressed using the wildcard character(?), followed by the super keyword, followed by its lower bound.

The Java documentation provides guidelines on when to use upper bound and lower bound wildcards. Please refer this page.

package com.sourcetricks.generics;

import java.util.ArrayList;
import java.util.List;

public class JavaGenerics {
 
 // Upper bounded wildcard
 public static double addMyList(List<? extends Number> list) {
  double sum = 0.0;
  for ( Number elem : list ) {
   sum += elem.doubleValue();
  }
  return sum;
 }
 
 // Unbounded wildcard
 public static void printMyList(List<?> list) {
  for ( Object elem : list ) {
   System.out.println(elem);
  }
 }
 
 // Lower bounded wildcard
 public static void fillMyList(List<? super Integer> list) {
  for ( int i = 1; i <= 5; i++ )
   list.add(i);
 }
 
 // Lower bounded wildcard
 public static void main(String[] args) {
  List<Integer> list1 = new ArrayList<>();
  fillMyList(list1);
  System.out.println(addMyList(list1));
  printMyList(list1);
 }
}

0 comments :

Post a Comment

Contact Form

Name

Email *

Message *

Back to Top