Generics bounded types, Subtyping and Type Erasure Tutorial with examples

There may be times when you want to restrict the types that can be used as type arguments in a parameterized type. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

bounded types in generics?

Bounded type parameters are key to the implementation of generic algorithms. Consider the following method that counts the number of elements in an array T[] that are greater than a specified element elem.
public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}


The implementation of the method is straightforward, but it does not compile because the greater than operator (>) applies only to primitive types such as short, int, double, long, float, byte, and char. You cannot use the > operator to compare objects. To fix the problem, use a type parameter bounded by the Comparable <T> interface:
public interface Comparable<T> {
    public int compareTo(T o);
}


The resulting code will be:
public static <T extends Comparable<T>> int 

countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

Generics and Inheritance in Java

As you already know, it is possible to assign an object of one type to an object of another type provided that the types are compatible. For example, you can assign an Integer to an Object, since Object is one of Integer's supertypes:
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // OK


In object-oriented terminology, this is called an "is a" relationship. Since an Integer is a kind of Object, the assignment is allowed. But Integer is also a kind of Number, so the following code is valid as well:
public void someMethod(Number n) { /* ... */ }
someMethod(new Integer(10));   // OK
someMethod(new Double(10.1));   // OK


The same is also true with generics. You can perform a generic type invocation, passing Number as its type argument, and any subsequent invocation of add will be allowed if the argument is compatible with Number:
Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK


Now consider the following method:
public void boxTest(Box<Number> n) { /* ... */ }


What type of argument does it accept? By looking at its signature, you can see that it accepts a single argument whose type is Box<Number>. But what does that mean? Are you allowed to pass in Box<Integer> or Box<Double>, as you might expect? The answer is "no", because Box<Integer> and Box<Double> are not subtypes of Box<Number>.

This is a common misunderstanding when it comes to programming with generics, but it is an important concept to learn.

Generic Classes and Subtyping in Java

We can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or interface and the type parameters of another are determined by the extends and implements clauses.

For example, ArrayList<E> implements List<E> that extends Collection<E>, so ArrayList<String> is a subtype of List<String> and List<String> is subtype of Collection<String>.

The subtyping relationship is preserved as long as we don’t change the type argument, below shows an example of multiple type parameters.
interface MyList<E,T> extends List<E>{ } 


The subtypes of List<String> can be MyList<String,Object>, MyList<String,Integer> and so on.

Type Erasure

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:
  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

For example if we have a generic class like below;
public class Test<T extends Comparable<T>> {       
 private T data;     
 private Test<T> next;       
 public Test(T d, Test<T> n) {         
  this.data = d;         this.next = n;     
  }       
 public T getData() { 
  return this.data; 
 } 
}


The Java compiler replaces the bounded type parameter T with the first bound interface, Comparable, as below code:
public class Test {       
 private Comparable data;     
 private Test next;       
 public Node(Comparable d, Test n) {         
  this.data = d;         
  this.next = n;     
 }       
 public Comparable getData() { 
  return data; 
 } 
} 


NEXT READ : Java Generics Wildcard Tutorial with Examples.

Generics bounded types, Subtyping and Type Erasure Tutorial with examples

Hope we are able to explain you basic details of Java Generics, if you have any questions or suggestions please write to us using contact us form.(Second Menu from top left).

Please share us on social media if you like the tutorial.