Powered by: newtelligence dasBlog 1.9.7067.0
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
© Copyright 2008 , Ted Neward
E-mail
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; } } }
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; } } }
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.)
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.
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.
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"?
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 } }
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"?
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.
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); }
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.
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.
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); } }
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.