Follow-up on the Java Generics post

A number of folks emailed me with comments and ideas following the post on Java5's generics model. In no particular order...

John Spurlock wrote,

Interesting scenario, I wasn't able to come up with a warning-free solution either - but had some fun trying. I wonder if your compiler of choice makes a difference? I seem to remember Eclipse's JDT compiler having subtle differences from Sun's in regards to edge-case generics/casting scenarios (Sun's being more strict and giving more warnings).

The c# analogue is trivial, although the client code seems unnecessarily verbose (does not/will not infer the "item-type" afaik) In general, the c# compiler seems overly conservative in regards to type inference, forcing explicit type parameters far too often (anonymous parameterized delegates are the biggest offender). Also the fact that Type is not parameterized makes it impossible to pass standard arguments a la the java example.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      List<DateTime> listOfDates = GetSomethingOf<List<DateTime>, DateTime>();
      Console.WriteLine(listOfDates.Count);

      Collection<DateTime> collectionOfDates = 
        GetSomethingOf<Collection<DateTime>, DateTime>();
      Console.WriteLine(collectionOfDates.Count);

      LinkedList<DateTime> linkedListOfDates = 
        GetSomethingOf<LinkedList<DateTime>, DateTime>();
      Console.WriteLine(linkedListOfDates.Count);

      Dictionary<DateTime, DateTime> dictionaryOfDatePairs = 
        GetSomethingOf<Dictionary<DateTime, DateTime>, KeyValuePair<DateTime,DateTime>>();
      Console.WriteLine(dictionaryOfDatePairs.Count);

      List<List<String>> listofListsOfDates = 
        GetSomethingOf<List<List<String>>, List<String>>();
      Console.WriteLine(listofListsOfDates.Count);

      Console.ReadLine();
    }

    static C GetSomethingOf<C, T>()
      where C : ICollection<T>, new()
      where T : new()
    {
      C rt =  new C();
      rt.Add(new T());
      return rt;
    }
  }
}

Thanks, John.

Adam Vanderburg wrote:

In C# you'd add the "new()" constraint to the generic types (both the collection and item), then you can just "new T()" them. In fact, one of the frustrating things about C# 2.0 is that you can require a parameterless constructor, but you can't require Constructors with specifically typed parameters. (Rumor has it that the underlying IL supports it, just not the C# 2 compiler.)
Yep, Adam, that's exactly what John demonstrates above (in case any non-C# programmers were wondering what that "where T : new()" syntax was coming from). As to whether the IL supports constructors with specifically typed parameters, I have to admit I don't know the answer to that one, and don't have time at the moment to find out--maybe Serge Lidin will read this blog entry and email me with an answer that I can post in a future blog entry (or comment, once I get comments re-enabled after doing a dasBlog upgrade to prevent all the crap comment and pingback/trackback spam I've been getting on here).

Next, Matt Tucker wrote:

In regard to your article on Java5 generics, I had a couple comments for you:

First of all, I'm not disputing the contention that Java generics leave something to be desired. Cool as they are, there are clearly some bits missing from the implementation.

No arguments there, Matt, but mostly my argument is that Java's generics model leave something to be desired entirely because they support a model of type erasure, rather than persisting the parameterized type directly into the JVM bytecode level. Doing that would have complicated the JVM a fair bit, but would have (a) allowed other languages to take advantage of generics, (b) preserved type-safety even in the face of Reflection, and (c) allowed for JIT compiler optimizations given the known paramterized type. None of these are possible in a type-erasure-based model. Why did Sun choose this approach? I won't speak authoritatively here, but my guess is because it represented too drastic a change for the language/platform at this point in Java's lifetime. (Whether that's true or not, or a good decision to have made, is entirely up to you to judge for yourself.)
Secondly, the code you had posted in your blog (at 4:00p at least) wouldn't compile. The issue was in the "external" class, and I ended up changing it to:
public static class external { 
  public static <C, T extends Collection<C>> T getSomethingOf(Class<T> type, Class<C> contentType)
    throws Exception { 
    T result = type.newInstance(); 
    result.add(contentType.newInstance());
    return result;
  } 

  public static Set<Date> getSetOfDate() 
    throws Exception { 
    return getSomethingOf(HashSet.class, Date.class); // warning 
  } 
}
This still shows a warning on the line in question, but dispenses with the cast of the HashSet class, and rearranges the generic specification a bit.

My next point, which isn't a major one, is that in all of the examples you're doing collection.addAll(Arrays.asList(<single genericized item>)), which causes Java to complain that it's trying to dynamically create an array for an unknown (generic) type. Since that array has one item in it, and the array itself is going to be thrown away, why not do collection.add(contentType.newInstance()) and dispense messing with arrays entirely? If you're worried about doing it in a loop, does it really make sense to create and fill an entire array in a loop, add all its contents to the collection in one operation, and then throw it away?

As for the issue itself, I'd say that while none of the implementations are clean, the last one ("internal") is probably the best from the standpoint that it's at least providing a clean API. Sure, there's some casting and warnings going on inside there, but at least users of the code (ie, getSetOfDate) don't have to mess with it. And the warnings are there to remind you that you need to be careful about what you're doing. Since you have a collection that's only supposed to hold C's, and since you're only creating C's and putting them in the collection, it theoretically *should* be fine. Of course, what's the point of having to deal with a statically typed language if all you can get out of it is "should"?

All are viable points, Matt, and unfortunately I can't answer any questions regarding the intent of the code or why the choice for arrays; as I mentioned, this was code presented to me by an attendee at an NFJS conference, looking for an answer to get rid of the warnings generated by the various options. As to why the code wouldn't compile, that's likely a typo on my end--while we were working with it, we were in Eclipse at the time, and no compiler errors were reported at the time, so I have to assume I fat-fingered the code somewhere along the way.

Then, Bob Lee a.k.a. crazybob wrote to say:

The problem is you're trying to create an instance of a generic type from a Class. Class instances can only represent raw types. For example, List<?>, List<String> and List all share the same Class instance.

Your options are A) use a callback instead of a class instance:

interface CollectionFactory> {
  C newInstance();
}

static <T, C extends Collection<T>> C 
  getSomethingOf(CollectionFactory<C> collectionFactory, Class<T> elementType)
  throws Exception {
  C c = collectionFactory.newInstance();
  c.addAll(Arrays.asList(elementType.newInstance ()));
  return c;
}

public static Set<Date> getSetOfDate() throws Exception {
  return getSomethingOf(new CollectionFactory<Set<Date>>() {
    public Set<Date> newInstance() { 
      return new HashSet<Date>();
    }
  }, Date.class);
}
Or B) suppress the warning.

In the example above, we eliminated the warnings when we got rid of the Class representing the collection type, but we left the Class representing the element type in. This isn't a problem in the example above, but if we called getSomethingOf() with a generic element type, the code won't compile. Again, our only options would be to live with warnings or use a callback.

Thanks, Bob, though I'm not sure I like the solution. Bob also pointed out (over IM) that Angelika Langer has a great FAQ on generics off of her website; were I not such a lazy person, I'd link to it directly from here (and will later, when I'm online), but for now, Google on "Langer generics FAQ" and you're feeling lucky....

Finally, Rafael de F. Ferreira wrote:

Hello. I came up with the following ugly hack:
import java.util.*;
import org.junit.Test;

public class WithNewClass {
  public static <T, C extends Collection<? super T>> 
    C getSomethingOf(Class<C> type, Class<T> contentType) 
    throws Exception
  {
    C res = type.newInstance();
    res.add(contentType.newInstance());
    return res;
  }

  public static Set<Date> getSetOfDate() 
    throws Exception 
  {
    class HSD extends HashSet<Date> {};
    Class<? extends Set<Date>> cls = HSD.class;
    return getSomethingOf(cls, Date.class);
  }

  @Test public void printSetOfDate() throws Exception {
    Set<Date> newset = getSetOfDate();
    System.out.println(newset);
  }

}
It compiles without warnings in Eclipse, but I hope someone knows a better solution. Creating a class just to capture type arguments seems like a kludge.
It is, Rafael, but it's an interesting and useful kludge, nonetheless.

Thanks to all five of you for your comments; much as I dislike the generics system that Java ended up with, the sooner we learn to work with it and account for its... quirks, shall we say... the better.