LoginSignup
2
0

More than 5 years have passed since last update.

java Generics T and ? difference

Last updated at Posted at 2018-08-23

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!

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0