From: R. M. B. <rmb...@gm...> - 2007-05-09 21:22:26
|
Hello All, I'm not sure where we stand these days with interactions support for generics, but I thought I would bring this to your attention. I have found a strange NPE for certain input in the interactions window. The offending code looks a little strange, so let me talk a little first about the motivation for the code that produced the problem. Let's say we are implementing a traditional Factory Pattern around the java.util.Setinterface: ======================================= import java.util.*; public interface SetFactory<T> { public Set<T> makeSet(); } ======================================= import java.util.*; public class HashSetFactory<T> implements SetFactory<T> { public HashSetFactory() { } public Set<T> makeSet() { return new HashSet<T>(); } } ========================================== However, we then realize that HashSetFactory<T> is behavior only and thus is a good candidate for Singleton Pattern. We then apply the Singleton Pattern and rework the generic type declaration T around the fact that we will not know the binding of T when the Singleton is created: ======================================= import java.util.*; public interface SetFactory { public <T> Set<T> makeSet(); } ======================================= import java.util.*; public class HashSetFactory implements SetFactory { public static final HashSetFactory Singleton = new HashSetFactory(); private HashSetFactory() { } public <T> Set<T> makeSet() { return new HashSet<T>(); } } ========================================= Now we see we have a problem. Since HashSetFactory is now Singleton, we have removed any statements such as "new HashSetFactory<Dogs>();" from the invoking code that provided a binding for T. How then will T get bound since we have no way of (unlike C#, as my peers tell me) binding T to a type using a language construct at (syntactic) invocation? Well, the solution I came up with in my mind was to add a parameter of type T to makeSet. So it now appears as: ======================================= import java.util.*; public class HashSetFactory implements SetFactory { ... public <T> Set<T> makeSet(T example) // communicate to the compiler what T should be bound to by passing an example reference from the caller { return new HashSet<T>(); } } ========================================= This, in my mind, would give the compiler enough information from the invoker about the type that T represents to prove all the parameterized types match. And, in fact, this works: ========================================== SetFactory setFactory = HashSetFactory.Singleton; Set<Integer> nums= setFactory.makeSet(new Integer(1)); // note this argument is never actually used by the method, it just provides a binding for T nums.add(new Integer(2)); System.out.println(nums); ========================================== The above code behaves as expected when given to the VM directly, displaying "[2]". My first thought here though was what to do if you need to create a Set<Cat> but do not yet have a reference to a Cat needed to pass to the factory as an example? No problem, just make the declaration "Cat myCat = null;" and pass myCat to the factory. Because the example reference is never in fact used in the method body and is just their to help out during static analysis, you are fine. In fact, I took this one step further in the invoking code by trying a type that was itself parameterized. For example, consider the invoking class "public class SomeApp<E>". The usage: ========================================= public void foo() // method inside SomeApp<E> { E example = null; Set<E> someSet = HashSetFactory.Singleton.makeSet(example); } ========================================= compiles and executes as you would expect for the "some elsewhere defined" binding to E. So all is well and good if we execute the code directly on VM. However, consider what happens when we use the interactions window: Welcome to DrJava. Working directory is C:\Documents and Settings\G9053\Desktop > import java.util.*; > public class HashSetFactory { public HashSetFactory Singleton = new HashSetFactory(); private HashSetFactory(){} public <T> Set<T> makeSet(T example) { return new HashSet<T>(); } } > Set<Integer> nums= HashSetFactory.Singleton.makeSet(new Integer(1)); NullPointerException: at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(Unknown Source) at sun.reflect.UnsafeObjectFieldAccessorImpl.get(Unknown Source) at java.lang.reflect.Field.get(Unknown Source) > The odd part is that if all the class declarations happen in the declarations window, that third line of code will execute fine by itself. If we define the class in the interactions pane, it throws NPE. What do you all think? -- Matt |