I found a lot of posts about how to overcome this limitation, but none about why this limitation exists (except this one, which just mentions it has to do with type era
The shortest answer is that generic type parameters do not exist at runtime.
Generics were retrofitted into the Java language in release 5. In order to maintain backward compatibility with the existing code base, they were implemented by erasure.
Generic type parameters exist in your source code at compile-time, but nearly all evidence of them is removed in the byte code during compilation. This implementation of generics was chosen because it maintained inter-operability between pre-generics code and Java 5+ generic code. Type safety with generics is largely, therefore, a compile-time only phenomenon. If your generic code compiles without error and without warnings, then you are assured that your code is type safe.
Because of erasure, however, there are (as of Java 5) two kinds of types:
Reifiable. For example String
, Integer
, etc. A reifiable type has the same type information at compile-time as it has at run-time.
Non-reifiable. For example List<String>
, List<T>
, and T
. Non-reifiable types have less type information at run-time that at compile time. In fact, the run-time types of the above are List
, List
, and Object
. During compilation, the generic type information is erased.
You cannot use the new
operator with non-reifiable types because there is no type safe way at run-time for the JVM to generate an object of the correct type.
Source code:
T myObject = new T();
The above does not compile. At run-time, T
has been erased.
A strategy for circumventing some problems with type erasure and Java generics is to use type tokens. This strategy is implemented in the following generic method that creates a new T
object:
public <T> T newInstance(Class<T> cls) {
T myObject = cls.newInstance();
return myObject;
}
The generic method captures the type information from the Class
object that is passed as a parameter. This parameter is called a type token. Unfortunately, type tokens themselves must always be reifiable (because you can't get a Class
object for a non-reifiable type) which can limit their usefulness.
Short answer:
Java is a compiled programming language, which means that your bytecode is constant at runtime. It is impossible to generate bytecode for new E()
if E
is unknown.
Explanation: Generic information is erased in runtime:
public class Container<E> {
private E item;
public E getItem() {return item;}
}
class BoxWithPresent extends Container<Present> {
}
class SimpleBox extends Container {
}
In bytecode class BoxWithPresent
contains field item
of type Present
, but class SimpleBox
contains field item
of type Object
(because type E
was not specified).
Now you write abstract instantiation method:
public class Container<E> {
public <E> E createE() {
return new E(); // imagine if that was allowed
}
}
What bytecode should be generated here? .class
file is generated right now, at compilation time, but we have no idea what is E
type.
So.. can new T()
be replaced with new Object()
? Bad idea, class BoxWithPresent
won't like it, because it expects that E
is Present
.
Can it be replaced with class.newInstance()
? Again no, there is no class
variable in method scope.
That's why new E()
is impossible.
But there are workarounds with passing class
as parameter, or extracting generic information.
Creating a class from a generic. Do note that this relies on the class being parametrized. This returns the class object of the generic, through which you can perform further reflection on to create an object.
public static <T> Class<T> getClassFromGeneric(
Object parentObj,
int oridnalParamterizedTypeIndex) throws Exception{
Type[] typeArray = getParameterizedTypeListAsArray(parentObj);
return (Class<T>)typeArray[oridnalParamterizedTypeIndex];
}
public static <T> Type[] getParameterizedTypeListAsArray(Object parentObj){
try{
return ((ParameterizedType) parentObj.getClass()
.getGenericSuperclass())
.getActualTypeArguments();
}
catch(ClassCastException e){
logger.log(Level.SEVERE, "Most likely, somewhere in your inhetirance chain,"
+ "there is a class that uses a raw type and not the generic param."
+ "See: http://stackoverflow.com/questions/23074446/java-lang-classcastexception-java-lang-class-cannot-be-cast-to-java-lang-reflec"
+ " for more info",e);
throw e;
}
}
Usage:
public class GenericBaseClass<T>{}
public class GenericImpl extends GenericBaseClass<String>{
public static void main(String[] args){
new GenericImpl();
}
public GenericImpl(){
Class tClazz = getClassFromGeneric(this,0);
Constructor constructor = tClazz.getConstructor();
T newT = constructor.newInstance();
}
}
Contrary to popular belief, generic information at the class level is not "erased".
Remember that generic types are about compile-time safety. Compile-time checking of types allows the compiler to give you warnings/errors about issues with your code. This doesn't directly help your question, but it's important to keep the concepts of compile-time and runtime very clear.
You can't say, "return new T()
" because the compiler has no way of knowing how to do that. Without the specific type, the compiler can't know what constructor to call or even if it exists. Also, in order to "new up" an instance of type T
you need something to call. T
is just a symbol. It isn't a Class
or an Object
. Until you give information about what kind of instance T
is (eg List<String>
) the compiler has no way of doing what you are trying to do.
Typing is just a way of ensuring that the types given and returned will match correctly at compile-time. It's when you specify type specifics that you are able to create an instance of it. That's why you usually see completely open types on interfaces (eg List<T>
, Function<T,R>
etc). So implementers of the interface can specify what types will used.
Maybe it helps to think about generics like templates. Not the software development pattern, but the more abstract concept. The concept of a template says there's this structure without any internal details. If you want to create this thing that follows the template, use the template as your starting point, but you must fill in the details to make the thing. A generic type is sort of like that -- it allows you to structure something but doesn't give any details about what happens in that structure.
I know I struggled quite a bit with this when generics were introduced. At first I found it easiest to write the implementation for specific types first and then abstract it out using generics afterwards. After I got my head around generics, I now find it easier to start with a generified interface and then implement it.