JDK 5 introduced Java Generics with the aim of reducing bugs and adding an extra layer of abstraction over types.

What You Need

  • About 5 minutes
  • A favorite text editor or IDE
  • Java 8 or later

5. Miscellaneous

5.1 Basic types cannot be used as generic types

For example, we don’t have ArrayList<int>, only ArrayList<Integer>, why?

Because after type erasure, the original type of ArrayList becomes Object, but the Object type cannot store int values and can only reference Integer values.

We are able to use list.add(1) because of the automatic boxing and unboxing of basic types.

5.2 Generic types cannot be instantiated

T o = new T(); // Error

Because the generic parameterized type cannot be determined during Java compilation, since T is erased as Object, if new T() can be used, then it became new Object() and lost the original meaning of using generic type.

If we really need to instantiate a generic type, it can be achieved through reflection.

import java.lang.reflect.InvocationTargetException;

public class GenericsTest23 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        Person p = newInstanceOf(Person.class);
        p.name = "tom";
        p.age = 18;
        System.out.println(p);
    }

    private static <T> T newInstanceOf(Class<T> clazz) throws InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        T obj = clazz.getDeclaredConstructor().newInstance();
        return obj;
    }

    private static class Person {
        private String name;
        private int age;

        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }
}

The output of above code snippet is below :

Person [name=tom, age=18]

It is not possible to initiate a generic array neither.

T[] elements = new T[size]; // Error

But it is still possible to achieve the equivalent operation by using java.lang.reflect.Array#newInstance to initialize a generic array.

import java.lang.reflect.Array;

public class GenericsTest27 {
    public static void main(String[] args) {
        int size = 5;

        MyArray<Integer> arr = new MyArray<Integer>(Integer.class, size);

        for (int i = 0; i < size; i++) {
            arr.put(i, i);
        }

        for (int i = 0; i < size; i++) {
            System.out.println(arr.get(i));
        }
    }

    private static class MyArray<T> {
        private T[] array;

        public MyArray(Class<T> type, int size) {
            array = (T[]) Array.newInstance(type, size);
        }

        public void put(int index, T item) {
            array[index] = item;
        }

        public T get(int index) {
            return array[index];
        }
    }
}

Below is the output of above code snippet :

0
1
2
3
4

5.3 Static methods or variables of a generic class cannot use generic type parameters

Because the instantiation of generic parameters in a generic class is specified when an object of the generic class is defined, static variables and static methods do not need to be called using an object.

The object has not been created, so how to determine the type of this generic parameter, so of course it is wrong.

public class Container<T> {
    private static T value; // Error
    
    public static T get(){ // Error
    
    return value;
    
    }    
}

However, we should pay attention to distinguish the following situation.

Because this is a generic method, the T used in the generic method is the T defined in the method, not the T in the generic class.

public class GenericsTest24 {
    public static void main(String[] args) {
        System.out.println(Func.<Integer>apply(1));
        System.out.println(Func.<Float>apply(1f));
        System.out.println(Func.<Double>apply(1d));

        Func<String> f = new Func<>();
        System.out.println(f.apply(1));
        System.out.println(f.apply(1f));
        System.out.println(f.apply(1d));
    }

    private static class Func<T> {
        static <T> T apply(T value) {
            return value;
        }
    }
}

Below is the output of above code snippet :

1
1.0
1.0
1
1.0
1.0

5.4 Get the parameter type of a generic at runtime

Now that the type has been erased at compilation time, is it possible to get the parameter type of a generic at runtime ?

It is possible only when the parameter type is defined on the super class or interface.

In such case, we can use reflection to get ParameterizedType.

Parameter type defined on super class :

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class GenericsTest25 {
    public static void main(String[] args) {
        StringContainer c = new StringContainer();

        c.setValue("hello");

        System.out.println(c.getValue());

        ParameterizedType superClass = (ParameterizedType) c.getClass().getGenericSuperclass();

        System.out.println(superClass.getTypeName());
        System.out.println(superClass.getRawType());

        Type[] actualTypeArguments = superClass.getActualTypeArguments();

        for (int i = 0; i < actualTypeArguments.length; i++) {
            Type type = actualTypeArguments[i];
            System.out.println(type);
        }
    }

    private static class Container<T> {
        private T value;

        public T getValue() {
            return value;
        }

        public void setValue(T value) {
            this.value = value;
        }
    }

    private static class StringContainer extends Container<String> {

    }
}

The output of above code snippet is below :

hello
GenericsTest25$Container<java.lang.String>
class GenericsTest25$Container
class java.lang.String

Parameter type defined on interface :

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class GenericsTest26 {
    public static void main(String[] args) {
        StringFunc func = new StringFunc();

        System.out.println(func.apply("hello"));

        Type[] genericInterfaces = func.getClass().getGenericInterfaces();

        for (int i = 0; i < genericInterfaces.length; i++) {
            ParameterizedType type = (ParameterizedType) genericInterfaces[i];

            System.out.println(type.getTypeName());
            System.out.println(type.getRawType());

            Type[] actualTypeArguments = type.getActualTypeArguments();

            for (int j = 0; j < actualTypeArguments.length; j++) {
                System.out.println(actualTypeArguments[j]);
            }
        }
    }

    private static interface Func<T> {
        T apply(T value);
    }

    private static class StringFunc implements Func<String> {
        @Override
        public String apply(String value) {
            return value;
        }

    }
}

Below is the output of above code snippet :

hello
GenericsTest26$Func<java.lang.String>
interface GenericsTest26$Func
class java.lang.String

5.5 An exception cannot be generic

The compiler throws an error when defining an exception that inherits from Throwable with a generic type.

class MyException<T> extends Throwable {
    // The generic class MyException<T> may not subclass of java.lang.Throwable    
}

It is also not possible to use a parameterized type in a catch clause.

public <T extends Throwable> void doSomething(T exception) {
    try {
        // ...
    } catch(T e) {
        // Cannot use the type parameter T in a catch block
    }
}

5.6 An annotation cannot be generic

The definition of an annotation cannot be generic.

public @interface MyAnnotation<T> {
    // Syntax error, annotation declaration cannot have type parameters    
}

5.7 An enumeration cannot be generic

The definition of an annotation cannot be generic.

public enum MyEnumeration<T> {
    // Syntax error, enum declaration cannot have type parameters    
}