When to Use Generic Methods and When to Use Wild-Card

When to use generic methods and when to use wild-card?

There are certain places, where wildcards, and type parameters do the same thing. But there are also certain places, where you have to use type parameters.

  1. If you want to enforce some relationship on the different types of method arguments, you can't do that with wildcards, you have to use type parameters.

Taking your method as example, suppose you want to ensure that the src and dest list passed to copy() method should be of same parameterized type, you can do it with type parameters like so:

public static <T extends Number> void copy(List<T> dest, List<T> src)

Here, you are ensured that both dest and src have same parameterized type for List. So, it's safe to copy elements from src to dest.

But, if you go on to change the method to use wildcard:

public static void copy(List<? extends Number> dest, List<? extends Number> src)

it won't work as expected. In 2nd case, you can pass List<Integer> and List<Float> as dest and src. So, moving elements from src to dest wouldn't be type safe anymore.
If you don't need such kind of relation, then you are free not to use type parameters at all.

Some other difference between using wildcards and type parameters are:

  • If you have only one parameterized type argument, then you can use wildcard, although type parameter will also work.
  • Type parameters support multiple bounds, wildcards don't.
  • Wildcards support both upper and lower bounds, type parameters just support upper bounds. So, if you want to define a method that takes a List of type Integer or it's super class, you can do:

    public void print(List<? super Integer> list)  // OK

    but you can't use type parameter:

     public <T super Integer> void print(List<T> list)  // Won't compile

References:

  • Angelika Langer's Java Generics FAQs

Differences between Wildcard and generic methods? [Java]

A wildcard in Java represents an unknown type and they can be used as return type. To quote the explanation given in the Oracle Java tutorial:

The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype. See this

Let's say you have a List<String> and you would try to pass it to method that accepts List<Object>, it will not compile (Java is trying to protect you from creating a runtime exception here). However, if you pass it to a method that accepts List<?> it will. This may give you a feeling about how wildcards can be useful.

The keyword extends is used for wildcards with an upper bound, e.g. List<? extends Object>. There is also the wildcard with a lower bound: List<? super String>.

Without wildcards, the whole topic of generics would be much less interesting because generics are treated as objects (type erasure). This means that there are not many methods available for them. Wildcards solve this by restricting the type (and thus a common interface, which includes a common set of methods that can be invoked on an object, is specified).

When to use wildcards in Java Generics?

The big difference between

public <T extends Animal> void takeThing(ArrayList<T> list)

and

public void takeThing(ArrayList<? extends Animal> list)

is that in the former method you can refer to "T" within the method as the concrete class that was given. In the second method you cannot do this.

Here a more complex example to illustrate this:

// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
Map<T, String> names = new HashMap<T, String>();
for (T animal : list) {
names.put(animal, animal.getName()); // I assume there is a getName() method
}
return names;
}

// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
Map<Animal, String> names = new HashMap<Animal, String>();
for (Animal animal : list) {
names.put(animal, animal.getName()); // I assume there is a getName() method
}
return names;
}

With the first method if you pass in an List of Cats you get a Map with Cat as key. The second method would always return a Map with general Animal key.

By the way this is not valid java syntax:

public <? extends Animal> void takeThing(ArrayList<?> list)

Using this form of generic method declaration you have to use a valid java identifier and not "?".

Edit:

The form "? extends Type" only applies to variable or parameter type declaration. Within a generic method declration it has to be "Identifier extends Type" as you are able to refer to the "Identifier" from within your method.

Difference between generic type and wildcard type

The first signature says: list1 is a List of Es.

The second signature says: list is a List of instances of some type, but we don't know the type.

The difference becomes obvious when we try to change the method so it takes a second argument, which should be added to the list inside the method:

import java.util.List;

public class Experiment {
public static <E> void funct1(final List<E> list1, final E something) {
list1.add(something);
}

public static void funct2(final List<?> list, final Object something) {
list.add(something); // does not compile
}
}

The first one works nicely. And you can't change the second argument into anything that will actually compile.

Actually I just found an even nicer demonstration of the difference:

public class Experiment {
public static <E> void funct1(final List<E> list) {
list.add(list.get(0));
}

public static void funct2(final List<?> list) {
list.add(list.get(0)); // !!!!!!!!!!!!!! won't compile !!!!!!!!!
}
}

One might as why do we need <?> when it only restricts what we can do with it (as @Babu_Reddy_H did in the comments). I see the following benefits of the wildcard version:

  • The caller has to know less about the object he passes in. For example if I have a Map of Lists: Map<String, List<?>> I can pass its values to your function without specifying the type of the list elements. So

  • If I hand out objects parameterized like this I actively limit what people know about these objects and what they can do with it (as long as they stay away from unsafe casting).

These two make sense when I combine them: List<? extends T>. For example consider a method List<T> merge(List<? extends T>, List<? extends T>), which merges the two input lists to a new result list. Sure you could introduce two more type parameters, but why would you want to? It would be over specifying things.

  • finally wildcards can have lower bounds, so with lists you can make the add method work, while get doesn't give you anything useful. Of course that triggers the next question: why don't generics have lower bounds?

For a more in depth answer see: When to use generic methods and when to use wild-card? and http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ203

Java generics: wildcard ? vs type parameter E ?

Your approach of using a generic method is strictly more powerful than a version with wildcards, so yes, your approach is possible, too. However, the tutorial does not state that using a wildcard is the only possible solution, so the tutorial is also correct.

What you gain with the wildcard in comparison to the generic method: You have to write less and the interface is "cleaner" since a non generic method is easier to grasp.

Why the generic method is more powerful than the wildcard method: You give the parameter a name which you can reference. For example, consider a method that removes the first element of a list and adds it to the back of the list. With generic parameters, we can do the following:

static <T> boolean rotateOneElement(List<T> l){
return l.add(l.remove(0));
}

with a wildcard, this is not possible since l.remove(0) would return capture-1-of-?, but l.add would require capture-2-of-?. I.e., the compiler is not able to deduce that the result of remove is the same type that add expects. This is contrary to the first example where the compiler can deduce that both is the same type T. This code would not compile:

static boolean rotateOneElement(List<?> l){
return l.add(l.remove(0)); //ERROR!
}

So, what can you do if you want to have a rotateOneElement method with a wildcard, since it is easier to use than the generic solution? The answer is simple: Let the wildcard method call the generic one, then it works:

// Private implementation
private static <T> boolean rotateOneElementImpl(List<T> l){
return l.add(l.remove(0));
}

//Public interface
static void rotateOneElement(List<?> l){
rotateOneElementImpl(l);
}

The standard library uses this trick in a number of places. One of them is, IIRC, Collections.java

Generic method vs wildcard - compilation error

Confusions do come when you deal with multi-level wildcard syntax. Let's understand what those types exactly mean in there:

  • List<List<?>> is a concrete parameterized type. It is a heterogenous collection of different types of List<E>. Since List<?> represent a family of all the instantiation of List, you can't really pass an ArrayList<List<String>> to List<List<?>>. Because, nothing stops you from adding a List<Integer> to it inside the method, and that will crash at runtime, had compiler allowed it.

  • List<? extends List<?>> is a wildcard parameterized type. It represents a family of different types of List<E>. Basically, it might be a List<ArrayList<String>>, List<LinkedList<Date>>, so on. It can be a list of any type that extend from a List<?>. So, it will be safe to pass a ArrayList<List<String>> to it, the reason being, you won't be allowed to add anything, but null to the list. Adding anything to the list will be a compile time error.

  • As for List<List<T>>, it is again a concrete parameterized type. And since you're dealing with a generic method now, the type parameter will be inferred to be the type that is passed for it. So, for an ArrayList<List<String>>, type T will be inferred as T. A generic method deals with the types that are declared with it. So, there is only a single type T here. All the lists you get out of List<List<T>> will certainly be a List<T> for any type T. So, it's a homogenous collection of that type of List. Inside the method, you can not add any arbitrary List<E> to the List<List<T>>, because the compiler doesn't know whether that type E is compatible with T or not. So, it is safe invocation.


Related:

  • Multiple wildcards on a generic methods makes Java compiler (and me!) very confused
  • Java HashMap nested generics with wildcards
  • What are multi-level wild cards? Confusion in syntax
  • When to use generic methods and when to use wild-card?

Generic wildcard types should not be used in return parameters

The main benefit of using wildcard types, say in method formal parameter, is to provide flexibility to the user to pass, say any type of Collection, or List or anything that implements Collection (assuming that the collection is declared like Collection<?>). You would often find yourself using wildcard types in formal parameters.

But ideally you should avoid using them as return type of your method. Because that way, you would force the user of that method to use wildcard types at the caller end, even if they didn't want to. By using wildcard types, you're saying that, hey! this method can return any type of Collection, so it's your job to take care of that. You shouldn't do that. Better to use bounded type parameter. With bounded type parameter, the type will be inferred based on the type you pass, or the target type of the method invocation.

And here's a quote from Effective Java Item 28:

Do not use wildcard types as return types. Rather than providing
additional flexibility for your users, it would force them to use
wildcard types in client code.

Properly used, wildcard types are
nearly invisible to users of a class. They cause methods to accept the
parameters they should accept and reject those they should reject. If
the user of a class has to think about wildcard types, there is
probably something wrong with the class’s API.

Generic Wildcard Bounded Type vs Generic Bounded Type Parameter

No difference in this case. For more complex signatures, you may need to reuse the defined type so defining it is needed. Something like:

public static <T extends Number> void foo(T bar, List<T> myList)...

This guarantees not only that both bar and the elements of myList extend Number, but they are of the same type. You could not positively enforce that with the other syntax.



Related Topics



Leave a reply



Submit