Java 8: Remedies for What cannot be done in Generics

Generic programming the way to reuse code for Objects of many different types. The unbounded wild card is "?" (which is actually "? extends Object"). You can read from it, but you cannot write. If your type variable is T then you can use only extends (never super) with T then, the upper-bounded wildcard is "? extends T". you can

  • define
  • read from
  • but can not add (only super support with wildcards)

The term covariant preserves the ordering of types from more specific to more general. Collections are covariant when they use extends wildcard.

The lower-bounded wildcard is "? super T". Collections are contravariant when they use super with a wildcard. The term contravariant preserves the ordering of types from more general to more specific. Here supplied element must be itself or above of the T. The rule of thumb is "producer => extends and consumer => super": PECS.

 As shown in the above, you can copy the elements from the left to write because extends is the supplier and the super is the consumer. However, in the same method if you need to read and write, generics will not support because extends support read and super supports only write.

The catalogue found from the Core Java video course Core Java 9/10 Fundamentals1.

Generics doesn't work for:

  • Primitive Types (use wrapper classes or added versions like 'Intconsumer').
  • the 'instanceof' (work only with raw types.)
  • The method 'getclass'(returns only raw type.)
  • instantiate a generic type.
  • as a variable in a static field or method
  • throw or catch object of generic type. But you can use throws T is the method signature.
  • name clashes after the erasure.

Lets see the above in the code:

package au.com.blogspot.ojitha.trainings;

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

public class GenericEx2 {

    public static void main(String[] args) {
        List<X> xes = new ArrayList<X>(){
            {add(new X()); add(new X());}
        };

        List<Y> yes = new ArrayList<Y>(){
            {add(new Y()); add(new Y());}
        };

        min(xes); // this is success because X implements Comparable
        // min(yes); // not valid because not compatible with Comparable

    }

    public static <T extends Comparable> T min(List<T> list){
        if (list == null && list.size() == 0) return null;
        T small = list.get(0);
        for (T item: list) {
            if (small.compareTo(item) >0) small = item;
        }
        return small;
    }


}

class X implements Comparable{

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

class Y {}

Java Generics can be used with the Arrays as follows:

package au.com.blogspot.ojitha.trainings;

public class GenericEx3 {
    public static void main(String[] args) {

        A<Number>[] an = new A[10];
        an[0] = new A();
        // an[1] = new AS(); //not possible to compile.
        // an[2] = new AI(); //not possible to compile.
        an[3] = new AN();

        A<String>[] as = new A[10];
        as[0] = new A();
        as[1] = new AS();
        // as[2] = new AI(); //not possible to compile.
        // as[3] = new AN(); //not possible to compile.

        A<Integer>[] ais = new A[10];
        ais[0] = new A();
        // ais[1] = new AS(); //not possible to compile.
        ais[2] = new AI();
        // ais[3] = new AN(); //not possible to compile.

        //----------------------------

        B<Number>[] bn = new B[10];
        bn[0] = new B();
        // bn[2] = new BI(); //not possible to compile.
        bn[3] = new BN(); //not possible to compile.

        B<Integer>[] bis = new B[10];
        bis[0] = new B();
        bis[2] = new BI();
        // bis[3] = new BN(); //not possible to compile.
    }
}

class A<T> {}
class AN extends A<Number>  {}
class AS extends A<String>  {}
class AI extends A<Integer> {}

//------------------------------

class B<T extends Number>     {}
class BN extends B<Number>    {}
class BI extends B<Integer>   {}

Without reflection how to create an instance passing instance of T as parameters as follows.

  ....
  public static <T> A<T> createInst(Supplier<T> c) {
        return new A(c.get());
    }
}

class A<T> {
    public A(T t){}
}

In the above code, line 3, c.get() returns the instance of type T. For example you can instantiate the instance as createInst(String::new): this is called constructor expression.

How to create generic array. There are two ways, either use reflection (lines 7-11) or constructor expression (lines 13-17).

package au.com.blogspot.ojitha.trainings;

import java.lang.reflect.Array;
import java.util.function.IntFunction;

public class GenericEx4 {
    public static <T extends Number> T[] createNumberArray(T... t){
        T[] arrayT =  (T[])Array.newInstance(t.getClass().getComponentType(), t.length);
        //...
        return arrayT;
    }

    public static <T extends Number> T[] createArray(IntFunction<T[]> c, T... t){
        T[] arrayT = c.apply(t.length);
        // ...
        return arrayT;
    }

    public static void main(String[] args) {
        Integer[] ints1 = createNumberArray(1,2,3);

        Integer[] ints2 = createArray(Integer[]::new, 1,2,0,3);

    }
}

Again reflections helps. As shown in the line 7, you have to do the casting because arrays doesn't support to intialize the generic array. The alternative lambda approach has been shown in the line 13.

If you are still confuse with the above, let's see the basics. As explain in the Java Generics and Collections2 book, from the Java collection framework,

 Subtyping is transitive. For example, ArrayList<E> is a subtype of the List<E>, Collection<E> and Iterable<E>. Importantly:

Every type is subtype of itself

Above possibilities are shown in the following code. As shown in the line 21, List<Integer> is not a subclass of the Collection<Number> although Integer is a subclass of the Number.

package au.com.blogspot.ojitha.trainings;

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

public class GenericEx7 {
    public static void main(String[] args) {
        Collection<Number> cNum = new ArrayList<>();
        Collection<? extends Number> wcNum= new ArrayList<>();
        List<Integer> lInt = new ArrayList<>();
        List<Number> lNum = new ArrayList<>();

        //cNum = wcNum;
        wcNum = cNum;
        wcNum = lNum;
        //cNum = lInt;
        wcNum = lInt;
        cNum = lNum;
        //cNum = lInt;
        //lNum = lInt;

    }

}

Subtyping of extends and the super has described in the following drawing. Here B is a subtype of the A.

For example, List<Number> is subtype of the List<? super Integer> but List<Integer> is a subtype of the List<? extends Number>.

package au.com.blogspot.ojitha.trainings;

public class GenericEx7 {
    public static void main(String[] args) {
        MyNums<Number> ints = new MyNums<>();
        ints.put(1);

        MyNums<? extends Number> intsExtend = new MyNums<>();
        //intsExtend.put(1); //not possible

        MyNums<? super Number> intsSuper = new MyNums<>();
        intsSuper.put(1); //possible

    }
}

class MyNums<T>{
    private T t;

    public void put(T t){
        this.t = t;
    }
    
}

As shown in the above line 9, not possible to add an element because extends not allow to add, but line 12 is success because of super.

Arrays

In Java, arrays are covariant but generics are invariant. As well as, arrays doesn't support generic like contravariant subtyping.

Comparable

Two objects are equals:

x.equals(y) if and only if x.compareTo(y) == 0

This is strongly recommended in Java Generics and Collections. But this violate java.math.BigDecimal. In this case natural ordering is consistent with equals.

Comparison is anti-symmetric: reverse the order of arguments reverse the result as well.

sign of x.compareTo(y) = - sign of (y.compareTo(x)

Comparison is transitive :

if x.compareTo(y) > 0 y.compareTo(z) > 0 then x.compareTo(z) > 0

comparison is a congruence :

if x.compareTo(y) == 0 then sign of (x.compareTo(z) == sign of (y.compareTo(z))

comparison be reflexive :

x.compareTo(x)

Here is the bound recursive: T itself depends on T.

<T extends C<T,U>, U extends D<T,U>>$

for example, the min(...) method:

package au.com.blogspot.ojitha.trainings;

import java.util.Arrays;
import java.util.Collection;

public class Chapter3Ex1 {
    public static void main(String[] args) {
        Collection<Integer> integers = Arrays.asList(1,2,3,4);
        System.out.println(min(integers));
    }

    // T is bounded bu Comparable<T>
    public static <T extends Comparable<T>> T min(Collection<T> collection){
        T candidate = collection.iterator().next(); // read the first one
        for (T t: collection
             ) {
            if (t.compareTo(candidate) < 0) candidate = t;
        }
        return candidate;
    }
}

In the Lambda

Lambda is well married to Generics. For example, you can create a Finder as follows, The ElementFinder.find method finds the elements of the list of any type where the criteria is satisfied.

package au.com.blogspot.ojitha.trainings.fp;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class ElementFinder<E> {
    public static void main(String[] args) {
        //for Integers
        List<Integer> ints = Arrays.asList(1,2,3,4);
        ElementFinder<Integer> integerHello = new ElementFinder<>();
        List<Integer> integerList = integerHello.find(ints.iterator(), c -> c >2);
        integerList.forEach(e -> System.out.println(e));

        //for Strings
        List<String> strings = Arrays.asList("H","E","L","L","O");
        ElementFinder<String> stringHello = new ElementFinder<>();
        List<String> stringList = stringHello.find(strings.iterator(), c -> c.equals("L"));
        stringList.forEach(e -> System.out.println(e));

    }

    @FunctionalInterface
    interface Criteria<E> {
        boolean is(E e);
    }

    public List<E> find(Iterator<E> iterator, Criteria<E> criteria){
      List<E> ret = new ArrayList<>();
      while(iterator.hasNext()){
          E e = iterator.next();
          if (criteria.is(e)){
              ret.add(e);
          }
      }
      return ret;
    }
}

In the above program, ElementFinder.find has found elements of type of Integer as well as String. In the Line 13 and 19 lambda expressions of Criteria has been type inferenced.

accumulator pattern anti pattern

https://developer.ibm.com/articles/j-java-streams-2-brian-goetz/


  1. Core Java 9/10 Fundamentals, Cay S. Hostmann  

  2. Java Generics and Collections, Philip Wedler 

Comments

Popular posts from this blog

How To: GitHub projects in Spring Tool Suite

Spring 3 Part 7: Spring with Databases

Parse the namespace based XML using Python