|
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
|