java Generics T and ? difference


1) first we must know this fact: the actual type of an variable is uncertain. a parent class declared variable can actually be a child class type. a parent can represent a child, a child can not represent a parent, it only represent itself or the child of itself.

Test.java
package test;

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

public class Test<T> {

    public static void main(String[] args) {
        Object obj;
        Number num;
        Integer in = 10;
        System.out.println("in is " + in.getClass().getCanonicalName());

        num = in;
        System.out.println("num is " + num.getClass().getCanonicalName());
        // java.lang.Integer

        obj = in;
        System.out.println("obj is " + obj.getClass().getCanonicalName());
        // java.lang.Integer

        List<Object> objList = new ArrayList<Object>();
        List<Number> numList = new ArrayList<Number>();
        List<Integer> inList = new ArrayList<Integer>();

        inList.add(in);
        System.out.println("inList is " + inList.getClass().getCanonicalName());
        System.out.println("inList.get(0) is " + inList.get(0).getClass().getCanonicalName());

        numList.add(in);
        System.out.println("numList is " + numList.getClass().getCanonicalName());
        System.out.println("numList.get(0) is " + numList.get(0).getClass().getCanonicalName());

        objList.add(in);
        System.out.println("objList is " + objList.getClass().getCanonicalName());
        System.out.println("objList.get(0) is " + objList.get(0).getClass().getCanonicalName());
    }
}

in is java.lang.Integer
num is java.lang.Integer
obj is java.lang.Integer
inList is java.util.ArrayList
inList.get(0) is java.lang.Integer
numList is java.util.ArrayList
numList.get(0) is java.lang.Integer
objList is java.util.ArrayList
objList.get(0) is java.lang.Integer

2) should we let a List<Object> declared variable represent a List<Integer> type ? The answer is NO. let us think about this.

        List<Object> objList1 = new ArrayList<Object>();
        List<Object> objList2 = new ArrayList<Number>();//if it can be true here then next step will be error.
        objList2.add(new Object);//error! objList2 actually is a List< Number > !

3) so we have no choice! let List<Object> = new List<Object> is the only way to keep safe.

4) but we really want a syntax to let List<Parent> variable represent List<Child>, because there are too many children type. child1, child2, child3, .... we want a syntax to let variable represent one of List< child1> , List<child2>, List<child3>, ... be careful. we know that List<child1> and List<child2> can not represent each other. but we want to declare that List<? extends Parent> can represent List<child1> when it is = new List<child1>(). or it can represent List<child2> when it is = new List<child2>(). etc. when we see List<? extends Parent> g ,we know that g actually is a List<child1> or a List<child2> or a List<child3> or List< Parent >. one of them , not any of them! so we use the ? extends Parent instead of * extends Parent.

5) because List<? extends Parent> g actually is List<ChildX> , we can read from List<ChildX> use a Parent type variable. we can not put a Parent or a Child to its element position. because actually the element position may be Child1 type. maybe Child2 type... anyway, it is uncertain. what we know is it must look like a Parent type. we can read from the element position then use it as a Parent. but we can not replace it. if we do so ,we may break the rule to make Child <= Parent happens! or Child1 <= Child2. that must be forbidden!

6) then let us see List<? super Parent> g . it represent a List<P1>, or List<P2>, or List<P3>, ... which P1 is parent of Parent, P2 is parent of Parent, P3 is parent of Parent...

7) we can safely set a child of Parent to the g's element just like Parent p = new Child(). also we face the uncertainty. so we can not actually read from the element unless we use Object p = g.get(0).

8) when to use ? let us think about this scenario: we want a List<T> => List<T,T> convert

DoubleElement.java
package test;

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

public class DoubleElement<T> {
    public final T x;
    public final T y;

    public DoubleElement(T e) {
        this.x = e;
        this.y = e;
    }


    @Override
    public String toString() {
        return "{x:" + x + ", y:" + y + "}";
    }

    public static <T> List<DoubleElement<T>> toDoubleElementArray(List<T> list) {
        List<DoubleElement<T>> doubleElementArray = new ArrayList<DoubleElement<T>>(list.size());
        for (T e : list) {
            doubleElementArray.add(new DoubleElement<T>(e));
        }
        return doubleElementArray;
    }

    public static void main(String[] args) {
        List<Integer> inList = Arrays.asList(new Integer[] { 1, 2, 3 });
        List<Number> numList = Arrays.asList(new Number[] { 1, 2.5f, 3 });
        List<Float> floatList = Arrays.asList(new Float[] { 1.1f, 2.2f, 3.3f });

        List<DoubleElement<Integer>> doubleInList = DoubleElement.toDoubleElementArray(inList);
        System.out.println(doubleInList);
        List<DoubleElement<Number>> doubleNumList = DoubleElement.toDoubleElementArray(numList);
        System.out.println(doubleNumList);
        List<DoubleElement<Float>> doubleFloatList = DoubleElement.toDoubleElementArray(floatList);
        System.out.println(doubleFloatList);

        //we want to constraint this because we only want to use Number and its Child to work.
        List<String> strList = Arrays.asList(new String[] { " I ", " am ", " evil !" });
        List<Object> objList = Arrays.asList(new Object[] { " I ", " am ", " evil !", " too !" });

        List<DoubleElement<String>> doubleStrList = DoubleElement.toDoubleElementArray(strList);
        System.out.println(doubleStrList);

        List<DoubleElement<Object>> doubleObjList = DoubleElement.toDoubleElementArray(objList);
        System.out.println(doubleObjList);
    }
}
[{x:1, y:1}, {x:2, y:2}, {x:3, y:3}]
[{x:1, y:1}, {x:2.5, y:2.5}, {x:3, y:3}]
[{x:1.1, y:1.1}, {x:2.2, y:2.2}, {x:3.3, y:3.3}]
[{x: I , y: I }, {x: am , y: am }, {x: evil !, y: evil !}]
[{x: I , y: I }, {x: am , y: am }, {x: evil !, y: evil !}, {x: too !, y: too !}]

T works fine. but we want to add a constraint to T. only List<Number> and its Child List<Float> List<Integer> ... can use this method. how to ?

public static <T> List<DoubleElement<Number>> toDoubleElementArray(List<Number> list) {
//this will well done with List<Number> but not List<Float> or List<Integer> or ...

yes! it is showtime of List<? extends Number> !

DoubleElement.java
package test;

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

public class DoubleElement<T> {
    public final T x;
    public final T y;

    public DoubleElement(T e) {
        this.x = e;
        this.y = e;
    }

    @Override
    public String toString() {
        return "{x:" + x + ", y:" + y + "}";
    }

    public static List<DoubleElement<? extends Number>> toDoubleElementArray(List<? extends Number> list) {
        List<DoubleElement<? extends Number>> doubleElementArray = new ArrayList<DoubleElement<? extends Number>>(
                list.size());
        for (Number e : list) {
            doubleElementArray.add(new DoubleElement<Number>(e));
        }

        return doubleElementArray;
    }

    public static void main(String[] args) {
        List<Integer> inList = Arrays.asList(new Integer[] { 1, 2, 3 });
        List<Number> numList = Arrays.asList(new Number[] { 1, 2.5f, 3 });
        List<Float> floatList = Arrays.asList(new Float[] { 1.1f, 2.2f, 3.3f });

        List<DoubleElement<? extends Number>> doubleInList = DoubleElement.toDoubleElementArray(inList);
        System.out.println(doubleInList);
        List<DoubleElement<? extends Number>> doubleNumList = DoubleElement.toDoubleElementArray(numList);
        System.out.println(doubleNumList);
        List<DoubleElement<? extends Number>> doubleFloatList = DoubleElement.toDoubleElementArray(floatList);
        System.out.println(doubleFloatList);

        //we want to constraint this because we only want to use Number and its Child to work.
        List<String> strList = Arrays.asList(new String[] { " I ", " am ", " evil !" });
        List<Object> objList = Arrays.asList(new Object[] { " I ", " am ", " evil !", " too !" });

        //this will compile error!
        List<DoubleElement<String>> doubleStrList = DoubleElement.toDoubleElementArray(strList);
        System.out.println(doubleStrList);
        //this will compile error!
        List<DoubleElement<Object>> doubleObjList = DoubleElement.toDoubleElementArray(objList);
        System.out.println(doubleObjList);

    }
}

9) at last. let us see an example of <? super T>

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

 */
package java.util.function;

import java.util.Objects;

/**
 * Represents a function that accepts one argument and produces a result.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object)}.
 *
 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

    /**
     * Returns a composed function that first applies the {@code before}
     * function to its input, and then applies this function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of input to the {@code before} function, and to the
     *           composed function
     * @param before the function to apply before this function is applied
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     * @throws NullPointerException if before is null
     *
     * @see #andThen(Function)
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     *
     * @see #compose(Function)
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     * Returns a function that always returns its input argument.
     *
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
TestFunction.java
package test;

import java.util.function.Function;

public class TestFunction {

    public static void main(String[] args) {

        Function<Integer, Float> func = e -> {
            return e + 5.5f;
        };

        Function<Number, Number> func2 = e -> {
            return Math.round(e.floatValue());
        };

        //OK round( e+5.5 )
        Number result = func.andThen(func2).apply(5);
        System.out.println(result);

        //this will constraint you because the <? super Float>
        Function<Integer, Number> func3 = e -> {
            return Math.round(e.floatValue());
        };

        //compile error!
        Number result3 = func.andThen(func3).apply(5);
        System.out.println(result);
    }
}

10) that's all. let us have a good weekend!