loading...

Effective Java! Use Bounded Wildcards to Increase API Flexibility

kylec32 profile image Kyle Carter ・7 min read

Effective Java Review (36 Part Series)

1) Effective Java Tuesday! Let's Consider Static Factory Methods 2) Effective Java Tuesday! The Builder Pattern! 3 ... 34 3) Effective Java Tuesday! Singletons! 4) Effective Java Tuesday! Utility Classes! 5) Effective Java Tuesday! Prefer Dependency Injection! 6) Effective Java Tuesday! Avoid Creating Unnecessary Objects! 7) Effective Java Tuesday! Don't Leak Object References! 8) Effective Java Tuesday! Avoid Finalizers and Cleaners! 9) Effective Java Tuesday! Prefer try-with-resources 10) Effective Java Tuesday! Obey the `equals` contract 11) Effective Java Tuesday! Obey the `hashCode` contract 12) Effective Java Tuesday! Override `toString` 13) Effective Java Tuesday! Override `clone` judiciously 14) Effective Java Tuesday! Consider Implementing `Comparable` 15) Effective Java Tuesday! Minimize the Accessibility of Classes and Member 16) Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields 17) Effective Java Tuesday! Minimize Mutability 18) Effective Java Tuesday! Favor Composition Over Inheritance 19) Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It. 20) Effective Java Tuesday! Prefer Interfaces to Abstract Classes 21) Effective Java! Design Interfaces for Posterity 22) Effective Java! Use Interfaces Only to Define Types 23) Effective Java! Prefer Class Hierarchies to Tagged Classes 24) Effective Java! Favor Static Members Classes over Non-Static 25) Effective Java! Limit Source Files to a Single Top-Level Class 26) Effective Java! Don't Use Raw Types 27) Effective Java! Elminate Unchecked Warnings 28) Effective Java! Prefer Lists to Array 29) Effective Java! Favor Generic Types 30) Effective Java! Favor Generic Methods 31) Effective Java! Use Bounded Wildcards to Increase API Flexibility 32) Effective Java! Combine Generics and Varargs Judiciously 33) Effective Java! Consider Typesafe Hetergenous Containers 34) Effective Java! Use Enums Instead of int Constants 35) Effective Java! Use Instance Fields Instead of Ordinals 36) Effective Java! Use EnumSet Instead of Bit Fields

Today we take on a bit of a complicated topic but one that, when done correctly, can really make your code much more flexible in its usage. Previously we have talked about how parameterized types are invariant. This means that for two types, TypeA and TypeB, Collection<TypeA> can not be either a subtype or super type of Collection<TypeB>. Making this more concrete, List<String> is not a subtype of List<Object>. This is because any object can go into List<Object> but not any object can go into List<String>. While this may follow logically, sometimes we want/need more flexibility and that's what the chapter we are reviewing today covers.

Let's consider a previous example we looked at.

public class Stack<E> {
  public Stack();
  public void push(E e);
  public E pop();
}

Above is the currently exposed API. Let's say we want to add a new method to add many items to the stack at once. We may consider writing it as such:

public void pushAll(Iterable<E> newItems) {
  for (E e : newItems) {
    push(e);
  }
}

This will compile and work fine but we do lose some flexibility that we may want. Let's consider an example where this may lead to frustration:

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);

While the above feels like it should work (Integer being a subtype of Number) it actually won't compile because of the invariants. This is where our bounded wildcard types come in to save the day. If we simply change the function signature to the following the above code will work.

public void pushAll(Iterable<? extends E> newItems) {
  for (E e : newItems) {
    push(e);
  }
}

So what does the above signature, ? extends E, mean? It basically means it can take any subtype of E (as well as E itself which can feel a little strange with the extends keyword). This indeed fits exactly into what we were after.

Now let's consider a similar but different case. Let's create the cousin to the pushAll function and create a popAll function. How this one will work is we will give the function a collection and it will pop all the contents of the Stack into it. Personally I'm not a huge fan of using parameters as output from a function but it serves to illustrate this point well. So our initial implementation may be something like:

public void popAll(Collection<E> destination) {
  while(!isEmpty()) {
    destination.add(pop());
  }
}

Once again this will compile with no issues and is usable but isn't as usable as it could be. Also again we get in trouble with a very similar example that we ran into with the pushAll function.

Collection<Number> numberCollection = ...;
Stack<Integer> integerStack = ...;
integerStack.popAll(numberCollection);

This feels like it should work just fine but it doesn't because again we get bit by the invariants. The difference with this one is rather than looking for a subtype of E we are wanting a collection passed in that is type of E or a supertype of E. Java provides a way to do this like the following:

public void popAll(Collection<? super E> destination) {
  while(!isEmpty()) {
    destination.add(pop());
  }
}

With this new signature the client code with compile and execute fine as well as the Stack code.

The rule of thumb that we are invited to follow is as follows. If an input parameter is something that we will be reading values from or writing to we should consider the use of wildcard types.

In the above two examples we used extends in one and super in another. You may ask, when should I use which one. This is indeed a great question. The mnemonic that the book suggests is PECS - producer-extends, consumer-super. Honestly this mnemonic doesn't really work for me as the producer/consumer terminology is a bit weird for me in this case. The way that I think of it is GEPS - get-extends, put-super. As in, if I'm getting values from it, I should use extends and if I'm putting values into it I should use super. Use whatever mnemonic works for you or come up with your own! So let's apply this to our examples above. In pushAll we were getting values out of the provided parameter to put somewhere else, so we used extends. In popAll we were putting values into the provided parameter, therefore super. This can take some practice to get the hang of thinking about. Let's go through some examples from previous examples in our generic section of the book.

First up, the Chooser function originally written as public Chooser(Collection<T> choices);. This takes in a selection of choices to be chosen from. Stop reading and thinking about which keyword should we use? Did you say extends? Great! Since we will be getting values from the choices collection (or in other words, if it's a producer) we use the extends keyword.

Next, public static <E> Set<E> union(Set<E> s1, Set<E> s2); which takes two sets in and provides a set that is the union of both the sets together. What keywords would we use for our parameters here? Did you say extends again? Perfect! Again we are grabbing values out of the provided Sets and therefore we want to use extends so the new function would look like public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2); But wait, what do we do with the return type, it still has no wildcards, is that OK? Indeed it is, in fact, it's preferred to be that way. Return types shouldn't use wildcards as it forces the use of wildcards on the receiver of those values. As a rule of thumb, if the user of your function needs to think about the wildcards you are using, you are likely doing it wrong. With the above changes we can now write code like:

Set<Integer> integerSet = Set.of(1,3,5);
Set<Double> doubleSet = Set.of(2.0,4.0,6.0);
Set<Number> numberSet = union(integerSet, doubleSet);

The above code is great and quite clean in that the compiler is capable of inferring the type that we want. While there were great steps in improving the type inference of Java in Java 8 and beyond we still do run into cases where the compiler is still unable to infer the types for us. In these cases we will get fairly hairy error messages but, thankfully, there is a way to help the compiler along via an explicit type argument. Let's see what our above union call would need to look like prior to Java 8.

Set<Number> numberSet = Union.<Number>union(integerSet, doubleSet);

Definitely not the prettiest code I have ever seen but at least it's possible and is something you can put in the back of your mind if you ever do run into this type of issue.

Ok, let's return from that detour to our practice game of Super or Extends. Let's consider the max function from the previous chapter. public static <T extends Comparable<T>> T max(List<T> list). It's a workout even to type that thing but let's think about how we would write this when in search of greater flexibility. Consider each type parameter individually. What we end up with is public static <T extends Comparable<? super T>> T max(List<? extends T> list); That has to be up there with one of the more complicated type signatures I have ever written. Let's consider the two parts. With our Comparable part we are providing a value to something there so in that area we are going to want to use super whereas in the list parameter we are grabbing items from that List and thus we want to use extends. Rule of thumb is that Comparables always are consumers and thus will always use super. This added complexity doesn't come without it's benefits though. We are now able to find the max of types such as ScheduledFuture which doesn't implement Comparable<ScheduledFuture> but instead implements Comparable<Delayed> the super type of ScheduledFuture.

There is one last thing to consider. When to use wildcards, and when to use type parameters. Consider the following two function signatures that implement a swap function:

public static <E> void swap(List<E> list, int from, int to);
public static void swap(List<?> list, int from, int to);

So which one should be preferred? Effective Java says a rule to follow is, if a type parameter only shows up in a method signature once that we should go with the wildcard option (the second option). This also does seem like the simpler type signature. However, before we jump to that solution let's consider how the implementation would work.

public static void swap(List<?> list, int from, int to) {
  list.set(from, list.set(to, list.get(from));
}

This actually won't compile because the only thing allowed to be put into a wildcard collection is null. There is a way around this though, with a helper function that can capture the type. So if we change it to the following it will work.

public static void swap(List<?> list, int from, int to) {
  swapHelper(list, from, to);
}

private static <E> void swapHelper(List<E> list, int from, int to){
  list.set(from, list.set(to, list.get(from));
}

Because the swapHelper function can capture the type of E and verify that the operation is safe to do this will work. However, take a look at it again, didn't we just write the first option? We did indeed, so in this case I would definitely vote in favor of going with the type parameter option rather than the wildcard option here.

This has been a fairly heavy chapter. It will likely take more practice and exposure to these wildcard parameters to really get your mind around them, I know it did for me and I continue to get confused about them at times. As you use these in the wild you will likely find that you use the extends option a lot more than you use the super option. This is just the nature of the fact that we usually pass parameters in to read from more than we pass them in to put values into. As we use these capabilities of bounded wildcard parameters in our code we can indeed make our code much more flexible and this is very much something that I think is worth the effort.

Effective Java Review (36 Part Series)

1) Effective Java Tuesday! Let's Consider Static Factory Methods 2) Effective Java Tuesday! The Builder Pattern! 3 ... 34 3) Effective Java Tuesday! Singletons! 4) Effective Java Tuesday! Utility Classes! 5) Effective Java Tuesday! Prefer Dependency Injection! 6) Effective Java Tuesday! Avoid Creating Unnecessary Objects! 7) Effective Java Tuesday! Don't Leak Object References! 8) Effective Java Tuesday! Avoid Finalizers and Cleaners! 9) Effective Java Tuesday! Prefer try-with-resources 10) Effective Java Tuesday! Obey the `equals` contract 11) Effective Java Tuesday! Obey the `hashCode` contract 12) Effective Java Tuesday! Override `toString` 13) Effective Java Tuesday! Override `clone` judiciously 14) Effective Java Tuesday! Consider Implementing `Comparable` 15) Effective Java Tuesday! Minimize the Accessibility of Classes and Member 16) Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields 17) Effective Java Tuesday! Minimize Mutability 18) Effective Java Tuesday! Favor Composition Over Inheritance 19) Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It. 20) Effective Java Tuesday! Prefer Interfaces to Abstract Classes 21) Effective Java! Design Interfaces for Posterity 22) Effective Java! Use Interfaces Only to Define Types 23) Effective Java! Prefer Class Hierarchies to Tagged Classes 24) Effective Java! Favor Static Members Classes over Non-Static 25) Effective Java! Limit Source Files to a Single Top-Level Class 26) Effective Java! Don't Use Raw Types 27) Effective Java! Elminate Unchecked Warnings 28) Effective Java! Prefer Lists to Array 29) Effective Java! Favor Generic Types 30) Effective Java! Favor Generic Methods 31) Effective Java! Use Bounded Wildcards to Increase API Flexibility 32) Effective Java! Combine Generics and Varargs Judiciously 33) Effective Java! Consider Typesafe Hetergenous Containers 34) Effective Java! Use Enums Instead of int Constants 35) Effective Java! Use Instance Fields Instead of Ordinals 36) Effective Java! Use EnumSet Instead of Bit Fields

Posted on by:

kylec32 profile

Kyle Carter

@kylec32

Backend Architect at MasterControl

Discussion

markdown guide