Search code examples
javagenericsbounded-wildcardunbounded-wildcard

In Java, what can a wild card do that regular generics cannot do?


I am new to Java. In this document they give this as a use case for using wildcard:

static void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

This is their solution:

static void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

But I could do the same without a wild card:

static <T> void printCollection(Collection<T> c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

Can someone show me a simple use case where regular generics won't work but a wild card will?

Update: The answers over here When to use wildcards in Java Generics? do NOT tell us the need for wildcard. In fact its the other way around.


Solution

  • One thing wildcards allow us to do is declare types that are agnostic towards a particular type parameter, for example a "list of any kind of list":

    List<List<?>> listOfAnyList = ...;
    
    listOfAnyList.add( new ArrayList<String>() );
    listOfAnyList.add( new ArrayList<Double>() );
    

    This is impossible without a wildcard:* because the element lists may have different types from each other.

    And if we try to capture it, we will find that we can't:

    static <E> void m(List<List<E>> listOfParticularList) {}
    
    m( listOfAnyList ); // <- this won't compile
    

    Another thing wildcards allow us to do that type parameters cannot is set a lower bound. (A type parameter can be declared with an extends bound, but not a super bound.**)

    class Protector {
        private String secretMessage = "abc";
    
        void pass(Consumer<? super String> consumer) {
            consumer.accept( secretMessage );
        }
    }
    

    Suppose pass was instead declared to take a Consumer<String>. Now suppose we had a Consumer<Object>:

    class CollectorOfAnything implements Consumer<Object> {
        private List<Object> myCollection = new ArrayList<>();
    
        @Override
        public void accept(Object anything) {
            myCollection.add( anything );
        }
    }
    

    The problem is: we can't pass it to a method accepting Consumer<String>. Declaring Consumer<? super String> means that we can pass any consumer which accepts a String. (Also see Java Generics: What is PECS?.)

    Most of the time, wildcards just let us make tidy declarations.

    If we don't need to use a type, we don't have to declare a type parameter for it.


    * Technically also possible with a raw type, but raw types are discouraged.

    ** I don't know why Java doesn't allow super for a type parameter. 4.5.1. Type Arguments of Parameterized Types may hint that it has something to do with a limitation of type inference:

    Unlike ordinary type variables declared in a method signature, no type inference is required when using a wildcard. Consequently, it is permissible to declare lower bounds on a wildcard […].