Java Generics

Evolution of Java


14 Jan 2017 View Comments
#java #generics #programming #computer

Generics
Generics

Introduction

Generics in Java were introduced as one of the features in JDK 5. It was one of the major changes in Java.

In Java 4, we had raw types only:

List newArray = new ArrayList();

Java 5 introduced generics:

List<Integer> newArray = new ArrayList<Integer>();

As you can see, the generics would imply the type of the List. In Java, Generic types or methods differ from raw types in the sense that they have type parameters such as above example. You may pass in the Type or wildcard instead of “Integer” in the above example. According to the JLS (4.8 Raw Types):

“The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.”

History

Sun decided to start Java development in 1990 led by James Gosling. Since then, there had been numerous changes to get to Generics which was introduced in J2SE 5.0 in 2004. J2SE 5.0 consists of a bunch of other Evolutionary Changes. There had been a couple of other changes after J2SE 5.0 was introduced. However, J2SE 5.0 is probably the most notable change in the Java history to be mentioned.

Why Generics?

Wikipedia says, Generics provides compile-time (static) type safety for collections and eliminates the need for most typecasts (type conversion) (specified by JSR 14). You might be thinking so what? Let’s take a look at the benefits.

Code that uses generics has many benefits over the non-generic code. Here are 3 points to address this:

  1. Stronger type checks at compile time: Probably the most important point of all. Avoiding runtime cast exception!
  2. Elimination of casts: Before Java 5, we had to cast Objects in order for it to be an explicit type. Such as (String) list.get(0). Now we can replace that by enforcing the list to be String, List<String> list = new ArrayList<String>();
  3. Enabling programmers to implement generic algorithms: You can have a generic type instead of one-specific (i.e. String) type.

Basically, the most important point above is probably type checking of parameters in your code at the compile time, automatically! This is very important as Casting was a big burn as most of the errors were bypassing the compiling until runtime exception rises in the production. Generics basically added stability to your code by making more of your bugs detectable at the compile time (Checked exception).

How Generics works in Java

There are 2 important terms to discuss the works of Generics in Java:

:one: Type Safety

This is basically saying as long as types are all passed compiler with Generics, ClassCastException should not occur at runtime. This is guaranteeing cast error-prone at the runtime as I discussed as the most important point in “Why Generics”.

Example of Type Safety:

List<Integer> list = new ArrayList<Integer>();
 
list.add(1000); //works and add 1000 to the list
 
list.add("testing"); //compile time error; Error: java: no suitable method found for add(java.lang.String)

:two: Type Erasure

This is basically saying that any of these type information added using Generics in the code will be removed in compiled bytecode. This is required to support old Java as Java itself is always backward compatible. Byte-level code is basically kept using old Java syntax which wouldn’t use the Generics.

Here is an example of Type Erasure. Basically above Generics Type Safety example is converted like this:

List list = new ArrayList();
 
list.add(1000);    

It is worth to note that the String insertion will not be at the bytecode level as that code block will never bypass Compiler (cast checked exception)

Also, Type Erasure introduces a problem like following:

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass()) { // evaluates to true
    System.out.println("Equal");
}

What this compares at the end of a day is ArrayList, because Types would have been Erased. There are other limitations to Generics. See end of this page, you will find examples of them.

Type Parameter Naming Conventions

There are conventions Java developers like to follow when it comes to using Generics. By convention, type parameter names are single, uppercase letters. This follows Java Naming Convention that most of Java developers would know about. Naming conventions make the code clean when everyone is following the same convention.

Commonly used type parameter names follow:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

You’ll see these names used throughout the Java SE API.

Generics with Wildcards

The question mark (?) is used as the wildcard in Generics, which represents an unknown type. A wildcard parameterized type is an instantiation of a generic type where at least one type argument is a wildcard.

Having a wildcard can mean different things in different context:

  1. Collection<?> denotes all instantiations of the Collection interface regardless of the type argument.
  2. List<? extends Number> denotes all list types where the element type is a subtype of Number.
  3. Comparator<? super String> denotes all instantiations of the Comparator interface for type argument types that are supertypes of String.

Examples of correct usage of wildcard:

Collection<?> list = new ArrayList<String>(); 
//OR
List<? extends Number> list = new ArrayList<Long>(); 
//OR
Pair<String,?> pair = new Pair<String,Integer>();

Examples of incorrect usage of wildcard:

List<? extends Number> list = new ArrayList<String>();  //String iscannot be a subclass of Number; error
//OR
Comparator<? super String> cmp = new RuleBasedCollator(new Integer(100)); //Integer is not superclass of String

If you look closely to the usage of generics in the diamond brackets, the object is being defined using extends or super. There is a rule to define how and when to use extends vs super.

PECS stands for Producer-Extends, Consumer-Super. Let’s take an example of how they are used:

  1. You want to go through the collection and do things with each item. Then the list is a producer, so you should use a Collection<? extends Thing>. The reasoning is that a Collection<? extends Thing> could hold any subtype of Thing, and thus each element will behave as a Thing when you perform your operation. (You actually cannot add anything to a Collection<? extends Thing>, because you cannot know at runtime which specific subtype of Thing the collection holds. To genericfy this, you would do Collection<? extends T>

  2. You want to add things to the collection. Then the list is a consumer, so you should use a Collection<? super Thing>. The reasoning here is that unlike Collection<? extends Thing>, Collection<? super Thing> can always hold a Thing no matter what the actual parameterized type is. Here you don’t care what is already on the list as long as it will allow a Thing to be added; this is what? super Thing guarantees.

Also, to note, wildcard should not be used as a return type.

Limitations to Generics

:one: Primitives are not allowed as part of Type.

class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    // ...
}

Above code example cannot be used like this:

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

It is to be used like this with values being autoboxed:

Pair<Integer, Character> p = new Pair<>(8, 'a');

:two: Static fields are not allowed

public class GenericsExample<T>;
{
   private static T member; //This is not allowed
}

:three: Creating an instance of Type is not allowed

public class GenericsExample<T>
{
   public GenericsExample(){
      new T();
   }
}

:four: Exceptions cannot be generic-fied

// causes compiler error
public class GenericException<T> extends Exception {}

This is because Runtime exception type has no generics information. For more detail, look into JLS 11. It has the note, “subclass of Throwable must not be generic (§8.1.2.”

:five: Cannot be used as Casts or instanceof parameter

public static <E> test rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    } else if (list instanceof LinkedList<Integer>) {  // ERROR
        System.out.print("LinkedList");
    }
}

Above can be used with a wildcard like so:

public static void test(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        System.out.print("ArrayList");
    } else if (list instanceof LinkedList<?>) {  // OK; instanceof requires a reifiable type
        System.out.print("LinkedList");
    }
}

:six: You cannot create an array of parameterized types

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

:seven: You cannot have the same method signature of erasure form

class GenericsExample {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

Both result in Same strSet in the bytecode which results in a compile error, java: name clash: print(java.util.Set) and print(java.util.Set) have the same erasure

Conclusion

Generics enable the use of stronger type-checking, the elimination of casts, and the ability to develop generic algorithms. Without generics, many of the features that we use in Java today would not be possible.

Do not use Raw types. Use Generics. Also, use Generics (types or methods) when you can. It is preferred way in modern Java. Also, upon using Generics, there cases where it is inevitable to avoid the unchecked warning. In that case, use @SuppressWarnings(“unchecked”) annotation and explain why it is safe to ignore this error.

Share this post

Me

I am a passionate software developer working in Downtown, Vancouver. I strongly believe in art of algorithms and together with it to write clean and efficient software to build awesome products. If you would like to connect with me, choose one from below options :) You can also send me an email at