What Java generics needs

I’ve been using Java 5’s generics a lot lately, and I’ve been thinking about what about the design works, and what needs improvement. Here’s a list of some things I think Java’s generics needs.

1. Cleaner syntax

My main problem with generics is that the syntax is so verbose. For example:

private Set<OutputPortValueListener<? super V>> listeners
    = new CopyOnWriteArraySet<OutputPortValueListener<? super V>>();

I think there needs to be a way to tell the compiler to figure out what the type arguments for some things must be, like this:

private Set<OutputPortValueListener<? super V>> listeners
    = new CopyOnWriteArraySet<...>();

The compiler could easily figure out what the “…” needed to be replaced with.

2. Stronger inference of generic type arguments

Java’s type inference is intentionally weaker than it could be. In fact, it is weaker than it was before 5.0’s release. Sun’s compiler team decided it would be too difficult to document the advanced type inference capabilities of javac in the Java Language Specification, so they simply removed them. Now, code like this won’t compile, when it would have with earlier versions of javac:

SortedSet<Map.Entry<Integer,Port<?>>> entries
         = new TreeSet<Map.Entry<Integer,Port<?>>>(KeyComparator.getInstance());
...
class KeyComparator<K extends Comparable<? super K>>
        implements Comparator<Map.Entry<K, ?>> {
    public static <K extends Comparable<? super K>> KeyComparator<K> getInstance() {
        return new KeyComparator<K>();
    }
    public int compare(Map.Entry<K, ?> entry, Map.Entry<K, ?> entry1) {
        return entry.getKey().compareTo(entry1.getKey());
    }
}

To compile, the underlined part needs to be replaced with “KeyComparator.<Integer>getInstance()”, because javac can’t figure out that it needs the <Integer> type argument. As I said, earlier versions of javac didn’t need the explicit type argument for this type of inference.

3. Inferred type bounds

As I’ve shown already, right now javac requires you to be verbose in your generics code in several ways. Another way is with generic type bounds. The following code will not compile:

<E> void doSomething(EnumSet<E> set, E val) { }

javac complains that you must specify bounds for <E>, because EnumSet<T> requires that T be a subtype of Enum<T>. So, you must insert the bounds like this to get it to work:

<E extends Enum<E>> void doSomething(EnumSet<E> set, E val) { }

The compiler should have been able to figure out this bound, but instead, you have to write it out, making your code longer and uglier, and not much, if any, clearer to developers who must use the method.

4. Inferred type arguments

Sometimes we want type arguments to be optional, when they could be inferred from the values other type arguments. For example, in this code, again using EnumSet:

class Thing<S extends EnumSet<E>, E extends Enum<E>> { }

The E type parameter could be inferred from the S type parameter, so a user of the Thing class should never need to explicitly type it in their programs.

There is also no syntax for defining type parameters whose values should only be inferred, and should not be specified by the user of the class. For example, maybe this syntax would work:

class Thing<X,Y extends X> uses <Z extends Y & Comparable<? super Z>> {
    public Thing(Z argument) { .. }
}

You might have noticed that this functionality is very similar to a feature used heavily with generics in C++: typedefs. Maybe instead of my “uses” syntax, you could do this:

class Thing<X,Y extends X> {
    public type Z extends Y & Comparable<? super Z>;
    public Thing(Z argument) { .. }
}

Thinking in terms of typedefs, another option might be:

public type ThingParam<T> extends T & Comparable<? super ThingParam<T>>;

class Thing<X,Y extends X> {
    public Thing(ThingParam<Y> argument) { .. }
}

This would allow us to create our own types without actually making classes, like this:

public type IdentifierMap extends Map<Integer,SomeObject>;

Then, we could start making our own code less verbose with these type definitions alone.

5. Support for backwards compatibility

Currently, if you write a class today that takes two type parameters, you are tied to those parameters forever, unless you want to break code that uses the class. If you want a third type parameter, you have to create a subclass or subinterface of the class which takes three parameters, and then tell the developers using the old class to switch over to the new class. (Or something like that, depending on your needs.)

In Java, we can overload methods with different numbers and types of parameters. I think we should be able to handle different type parameters just as easily. While I don’t know if this would be practical, we could at least take a small step forward by specifying default values for type parameters. The code might look like this:

public class Something<X,
        Y extends Comparable<? super Y>,
        Z extends Collection<? extends X> = List<? extends X>> { }

This would make the following two statements equivalent:

Something<String,Integer> thing = new Something<String,Integer>();
Something<String,Integer,List<? extends String>> thing
        = new Something<String,Integer,List<? extends String>>();

Another possible syntax for this would be possible if you could add metadata annotations to type parameters, and use type literals as annotation properties:

public class Something<X,
        Y extends Comparable<? super Y>,
        @Default(List<? extends X>) Z extends Collection<? extends X>{ }

Conclusion

These are some ideas I’ve been thinking about. I’d like to hear if anyone else has had similar problems as mine, and what you think of my ideas for solutions.

10 Responses to “What Java generics needs”

  1. Anonymous Says:

    It amazes me how the Java team have managed to make generics so complex — there are so many special cases about where you can and cannot use generic types and parameter types. The FAQ on generics is several pages long. In comparison, look at generics in the Eiffel language. It can be summed up as: “any type can be parameterised by one or more types”. End of story. No special rules for generic types, no special cases where you can’t use generic types or parameter types. It just works.

  2. Keith Lea Says:

    I haven’t used Eiffel’s generics, but the complexity in Java’s implementation lets you do more advanced things with the type system that you probably can’t do in Eiffel.

    I think the only cases where you can’t use generic types in Java are with primitives, and in instanceof expressions.

  3. Anonymous Says:

    Actually Eiffel generics are more much powerful than Java generics. For example, you can have an array of parameterised types — did you know you can’t do that in Java? You can create new instances and arrays of types that are parameters of generic types — again, you can’t do that in Java. Type parameters can be “primitive” types — in fact there is no concept of primitive type in Eiffel; all types fit into the class hierarchy.

  4. Keith Lea Says:

    There’s no need to use arrays in Java, except for primitive types. You can use a List of parameterized types in Java just like arrays in Eiffel.

    When I said Java’s generics are probably more powerful, I was talking about things like variance, type bounds, and type inference. Being able to create new instances of types specified by generic type arguments is a feature that may be cool in Eiffel, but wouldn’t make sense in Java, because you never know what parameters a class’s constructor takes. C# generics has something called a “constuctor restriction” which takes care of this, but I think that’s the wrong solution to the problem. The solution in Java is probably to pass a generic Factory<T> to classes which need to instantiate T.

    I do consider it a limitation of Java’s generic types system that you can’t use primitive types as type arguments. However, there’s no functionality lost due to this, only performance, because you can still use the wrapper classes Integer, Byte, and so on.

  5. Peter Ahe Says:

    If the generics FAQ for Eiffel is as short as claimed, then it is missing an important
    aspect: generic types in Eiffel are covariant. This was later discovered to lead to
    type unsoundness. AFAIK, this was later resolved by some special rules that ought to
    be described in a FAQ on the language. So apparently, the Eiffel FAQ is hiding some
    complexity.

    However, there are some special rules in Java generics that we would rather have been
    without. These rules are mostly there because due erasue and/or compatibility reasons,
    see Neal
    Gafter’s Blog
    .

  6. Anonymous Says:

    First off, I don’t like generics because they’re part of the reason I preferred Java over C++ back in 1995. They had to mess up a good thing…

    Just because they don’t want developers to write a simple dynamic cast! Give me a break! There are way more important things to worry about… Like:

    - Getting in the right features into the product
    - Having a proper design because you involved the right stakeholders
    - Choosing to implement the right patterns
    - Isolating your implementation from your interfaces
    - Proper use of polymorphism
    - Not having getters and setters for everything (so everything needs to change because a data-type changed).
    - etc.

    I wouldn’t touch generics with a 10-foot pole!

    But that’s just me…

  7. Anonymous Says:

    Agreeing with the previous poster… JUST SAY NO!

  8. Anonymous Says:

    Agreed, Java generics seem to invigorate those who “create complexity for complexity sake”

    Nuff said. Can we go home now?

  9. Keith Lea Says:

    The last three comments (#6-8) were posted from the same IP address and e-mail address.

  10. Anonymous Says:

    generics suck.

    Trying to write a simple sorting class, takes an array of comparables, sorts them into a temp array.
    Comparable[] temp = new Comparable[10]; this gives
    The expression of type Comparable[] needs unchecked conversion to conform to type Comparable[] Warnings.

    change it to this
    Comparable[] temp = new Comparable[10]; and you get the error:
    Cannot create a generic array of Comparable.

    WTF???

    In Java 1.4 all this was just Comparable[] temp = new Comparable[10]; done, end of story. How hard does this have to be? And why?? Because some ass might put a String into a linked list of Longs or something. Please.

Leave a Reply