1.4-Software-Development-Principles

Advanced generics

Learning goals

Requirements: To understand this theory you must be familiar with basic generics.

Static methods and Generics

One final point of clarification. When defining static methods you often want to accept a generic type:

public static <Q> void myStaticMethod(List<Q> list) {
  // ... do something with the list.
}

The first thing to notice is the odd placing of the generic type <Q>. Somewhere between public static and void. This means that this method will use the generic type Q somewhere in the definition that follows.

A second important point. The static method may be defined within a class that accepts a generic type, but has nothing to do with it. A more complete example would be:

public class MyGenericClass<T> {
  private List<T> list;
  // insert additional stuff here.
  
  public static <Q> void myStaticMethod(List<Q> list) {
    // ... do something with the list.	  
  }
}

The static method may be used within the class itself, but may just as easily be called from the outside. That is why you should always use a different “generic type” when defining static methods.

Please note that the class that defines this method doesn’t even need to be generic:

public class MyUtilityClass {
  // insert additional stuff here.
  
  public static <Q> void myStaticMethod(List<Q> list) {
    // ... do something with the list.	  
  }
}

Wild card types

In other words: “But what if I really don’t care what is stored in that list.”

Really? Well if you’re sure. Please note that this Very Rarely happens. Let’s consider this functions:

public static String toString(List<?> myList) { ... }

We want to neatly print all the element in this list as a single String.

This syntax (with the ?) is actually allowed. This function says: “I accept any list, don’t really care what type the elements have.

When using this list all elements will appear to us as Object:

Object item = myList.get(i);

Since all reference types in Jave inherit from Object that works out. Additionally, through polymorphism, we can all the toString() on each element and get the correct representation for each object, even though this function doesn’t need to know the actual type.

If the function was defined like this, there would be problems.

public static String toString(List<Object> myList) { ... }

This requires the list to actually contain objects. So passing a list of Strings, Persons or any other type would not work.

Restricting generic types

Sometimes you may want to express the idea to store some kind of “Person” for instance.

Lets take this diagram:

classDiagram
    Person <|-- Student
    Person <|-- Teacher

Now we can of course define a list than can contain either of these classes: List<Teacher> or List<Student>.

The only alternative so far is List<Person> which can contain both.

Consider this definition:

class MyClass<T extends Person> {
	private List<T> list;
	public T getPerson(int index);
}

To use this class we can instantiate it in three ways:

The restriction T extends Person will not allow you to do this: new MyClass<Chicken> because Chicken will likely not extend Person.

This class can function with either only Students or only Teachers. The ability to work with a list of all possible types of Person of course remains available.

Restricting wildcards

<? extends Person>

This construction is used in the situation where you don’t care about actual type since you are not going to refer to any methods implemented by descendants of the Person class in this case.

Note: If at any point in the code you start using instance of, you clearly should not have chosen a generic solution.

The difference with just a <?> is that in this case all the Person methods are available to you. Not just the methods from Object.

The user of your method can supply instances of Person and any decendant, but never Strings or other reference types.

Referencing earlier types.

This construction is used when your code requires a type definition that has a restriction that requires knowledge of that type.

Referencing yourself

<T extends Comparable<T>>

In this example we accept any type T, as long as that type can be compared against itself. It must implement the compareTo(T other) method.

In order to accept Person as an acceptable type, it should have been defined as:

public class Person implements Comparable<Person> {
    // ...
    @Override
    public boolean compareTo(Person other) {
        // ...
    }
}

If Person does not implement the Comparable<Person> interface, then it may not be used as a type for T in this situation.

Referencing a previous type

This construction is used, when two different types are required but the second type requires knowledge of the first type:

<T, L extends Iterable<T>>

This example requires an instance of T and any type of Iterable<T> I could write a very generic contains method:

public static <T, L extends Iterable<T>> boolean contains(T needle, L haystack) {
    for (T item:haystack) {
        if (item.equals(needle)) {
            return true;
        }
    }
    return false;
}

Please note that this example is a bit farfetched, since I could have easily used:

public static boolean contains(Object obj, Iterable<?> iterable) {
	// ...
}

Keep in mind that a simple solution is always preferable.