Some weeks ago a cowoker of mine asked me for help. He wanted to write a class with generic parameter type and create a new object of this generic type inside the class. I.E. he wanted to write something like this in Java:
There are some reasons why this code can't compile. Most evident reason is that T could be an interface and you can't instantiate an interface. And you have to be aware generics are boiling down to one rule : Generics are a double-check on compile time but at runtime the type parameter will be replaced by java.lang.Object and casts to the parameter type. This effect is called type erasure.
public class WrongSyntax<T> {
public void someMethod() {
T object = new T(); //this line does not compile
}
}
If you use java.lang.String as parameter type (and the compiler would not reject the marked line) this class would be replaced by:
public class WrongSyntax<T> {
public void someMethod() {
String object = (String) new Object();
}
}
You would get a ClassCastExcetion on runtime, therefore the compiler does not allow it.
Some days ago I read an article by Angelika Langer & Klaus Kreft regarding this problem in the German Java SPEKTRUM magazin. I'm afraid you won't find it online but there is an excellent english FAQ about Java Generics on Langer's site.
In this said article they discuss the problem of array and simple type instantiation for type parameters. Even though they brought up several solutions for arrays they came up with only one for simple types: Using reflection.
public class UsingReflection<T> {
public static <T> void createInstance(Class<T> clazz) {
T instance;
try {
instance = clazz.newInstance(); // does not work for every class
} catch (InstantiationException e) {
//...
} catch (IllegalAccessException e) {
//...
}
//...
}
}
While this will work for some classes it is obvious that it will not work for interfaces, abstract classes and classes without a default constructor.
Here are two examples that will pass the compiler but fail on runtime:
// throws java.lang.InstantiationException
new UsingReflection().createInstance(Serializable.class);
// throws java.lang.IllegalAccessException
new UsingReflection().createInstance(AbstractCollection.class);
To my mind there is a reason why instantiating type parameters is so difficult. In the first place generics are a double-check for programmers to avoid class cast exceptions. But there is more to it. Using generics brings you the benefit of decoupling an algorithm from the type it works on. But if you are trying to create a new instance of this type your generic code isn't indepent from this type anymore.
Fortunately there is a solution to the problem known from one of the most referenced books in software engineering - the GOF-Book. It's called Factory Pattern. And with generics the factory pattern is even more powerful. Your factories only need to implement the generic Factory interface
public interface Factory<T> {
public T create();
}
and your generic algorithm can trustfully rely on it.
public class UsingFactory<T> {
Factory<T> factory; // or maybe use Factory<? extends T> factory;
public UsingFactory(Factory<T> factory) {
this.factory = factory;
}
public void someMethod() {
T instance = factory.create();
//...
}
}
Object creation and object using are now decoupled, while the algorithm and the type it works on are decoupled too.
Now you can even implement a class GenericFactory<T> which uses reflection for instantiation. But you don't have to. You aren't forced to use a class with default constructor for T. But generics still work for you and the compiler will complain if the factory type parameter and the type parameter of your algorithm do not match.
// SimpleString creates Strings just like everybody does
Factory<String> simpleStringFactory = new SimpleStringFactory();
UsingFactory<String> useSimple = new UsingFactory<String>(simpleStringFactory);
// instantiates a factory that creates Strings via reflection
Factory<String> stringFactory = new GenericFactory<String>(String.class);
UsingFactory<String> useString = new UsingFactory<String>(stringFactory); // this works like expected
UsingFactory<Long> useLong = new UsingFactory<Long>(stringFactory); //type params do not match; does not compile