The Parameterless Generic Method Antipattern

A very interesting question was posted to Stack Overflow and reddit just recently about Java generics. Consider the following method:

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

While the unsafe cast seems a bit wonky, and you might guess there’s something wrong here, you can still go ahead and compile the following assignment in Java 8:

Integer x = getCharSequence();

This is obviously wrong, because Integer is final, and there is thus no possible Integer subtype that can also implement CharSequence. Yet, Java’s generic type system doesn’t care about classes being final final, and it thus infers the intersection type Integer & CharSequence for X prior to upcasting that type back to Integer. From a compiler perspective, all is fine. At runtime: ClassCastException

While the above seems “obviously fishy”, the real problem lies elsewhere.

It is (almost) never correct for a method to be generic on the return type only

There are exceptions to this rule. Those exceptions are methods like:

class Collections {
    public static <T> List<T> emptyList() { ... }
}

This method has no parameters, and yet it returns a generic List<T>. Why can it guarantee correctness, regardless of the concrete inference for <T>? Because of its semantics. Regardless if you’re looking for an empty List<String> or an empty List<Integer>, it is possible to provide the same implementation for any of these T, despite erasure, because of the emptiness (and immutable!) semantics.

Another exception is builders, such as javax.persistence.criteria.CriteriaBuilder.Coalesce<, which is created from a generic, parameterless method:

<T> Coalesce<T> coalesce();

Builder methods are methods that construct initially empty objects. Emptiness is key, here.

For most other methods, however, this is not true, including the above getCharSequence() method. The only guaranteed correct return value for this method is null

<X extends CharSequence> X getCharSequence() {
    return null;
}

… because in Java, null is the value that can be assigned (and cast) to any reference type. But that’s not the intention of the author of this method.

Think in terms of functional programming

Methods are functions (mostly), and as such, are expected not to have any side-effects. A parameterless function should always return the very same return value. Just like emptyList() does.

But in fact, these methods aren’t parameterless. They do have a type parameter <T>, or <X extendds CharSequence>. Again, because of generic type erasure, this parameter “doesn’t really count” in Java, because short of reification, it cannot be introspected from within the method / function.

So, remember this:

It is (almost) never correct for a method to be generic on the return type only

Most importantly, if your use-case is simply to avoid a pre-Java 5 cast, like:

Integer integer = (Integer) getCharSequence();

Want to find offending methods in your code?

I’m using Guava to scan the class path, you might use something else. This snippet will produce all the generic, parameterless methods on your class path:

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.stream.Stream;

import com.google.common.reflect.ClassPath;

public class Scanner {

    public static void main(String[] args) throws Exception {
        ClassPath
           .from(Thread.currentThread().getContextClassLoader())
           .getTopLevelClasses()
           .stream()
           .filter(info -> !info.getPackageName().startsWith("slick")
                        && !info.getPackageName().startsWith("scala"))
           .flatMap(info -> {
               try {
                   return Stream.of(info.load());
               }
               catch (Throwable ignore) {
                   return Stream.empty();
               }
           })
           .flatMap(c -> {
               try {
                   return Stream.of(c.getMethods());
               }
               catch (Throwable ignore) {
                   return Stream.<Method> of();
               }
           })
           .filter(m -> m.getTypeParameters().length > 0 && m.getParameterCount() == 0)
           .sorted(Comparator.comparing(Method::toString))
           .map(Method::toGenericString)
           .forEach(System.out::println);
    }
}

One thought on “The Parameterless Generic Method Antipattern

  1. …so, at the end, for legacy(?!?) code, there few options(or i’m wrong?what else?):

    1) make class generic:

    class GenericProblem

    2) using var-arg trick:

    public X getCharSequence(Class … ignore)

    THX…

    i’m in a hurry, i’m going save the kittens ;-P

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s