|
Replies:
7
-
Last Post:
Sep 26, 2008 8:30 AM
by: pietblok
|
Threads:
[
Previous
|
Next
]
|
|
|
|
|
|
Generics and getting the actual type programmatically
Posted:
Sep 23, 2008 6:23 AM
|
|
|
Hi,
I want to somehow at runtime get the actual class type for a generic class type.
The problem: I have a class that needs to construct an array of its generic type.
I tried to understand the java.lang.reflect classes that cover generics, but without much succes.
I am interrested in one interface in particular: ParameterizedType because it has a getActualTypeArguments() method.
Strangely enough however, there is no public API method that returns a ParameterizedType. In fact, the use page is completely empty (as for some other classes and interfaces as well).
After a lot of experimenting, I found that Class..getGenericSuperclass() may return a ParameterizedType (depending on the implementation?)
I eventually found a way to achieve what I wanted, but it is so clumsy and probably depending on luck, that I am not very happy with it.
And it only works if I dummy subclass my class.
Two questions:
1) Is there a more reliable way to obtain the actual class?
2) Why isn't there a straightforward API that allows a programmer to get the actual class?
Below my experiment.
Thanks
Piet
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
public class GetTypeClass {
public static class Generic<T extends Number, X extends Map<?, ?>> {
private final Class<T> tClass = getType();
@SuppressWarnings("unchecked")
public final T[] createEmptyArray(int size) {
return (T[]) Array.newInstance(tClass, size);
}
@SuppressWarnings("unchecked")
private final Class<T> getType() {
System.out
.println("============== getType start ================");
Class<T> rv = null;
Type genericSuperClass = this.getClass().getGenericSuperclass();
System.out.println("genericSuperClass.toString(): "
+ genericSuperClass.toString());
System.out.println("genericSuperClass.getClass().getName(): "
+ genericSuperClass.getClass().getName());
if (genericSuperClass instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperClass;
for (Type actual : parameterizedType.getActualTypeArguments()) {
System.out.println("actual.toString(): "
+ actual.toString());
if (actual instanceof Class) {
System.out.println("((Class<?>) actual).getName(): "
+ ((Class<?>) actual).getName());
if (rv == null) {
rv = (Class<T>) actual;
}
} else {
System.out.println("actual.getClass().getName(): "
+ actual.getClass().getName());
}
}
}
if (rv == null) {
rv = (Class<T>) Number.class;
}
System.out.println("============== getType end ================");
return rv;
}
public void displayInfo() {
System.out
.println("============== displayInfo start ================");
TypeVariable<?>[] typeVariables = this.getClass()
.getTypeParameters();
for (TypeVariable<?> typeVariable : typeVariables) {
System.out.println("typeVariable.getName(): "
+ typeVariable.getName());
Type[] bounds = typeVariable.getBounds();
System.out.println("\ttypeVariable.getBounds(): " + bounds);
for (Type bound : bounds) {
System.out.println("\t\tbound: " + bound);
}
System.out.println("\ttypeVariable.getGenericDeclaration(): "
+ typeVariable.getGenericDeclaration());
System.out.println("\ttypeVariable.getClass().getName(): "
+ typeVariable.getClass().getName());
}
System.out
.println("============== displayInfo end ================");
}
}
public static void main(String[] args) {
new GetTypeClass().test();
}
public void test() {
try {
Integer[] array = new Generic<Integer, Map<String, Object>>() {
}.createEmptyArray(100);
System.out.println(array);
} catch (Throwable t) {
t.printStackTrace(System.out);
}
try {
Integer[] array = new Generic<Integer, Map<String, Object>>()
.createEmptyArray(100);
System.out.println(array);
} catch (Throwable t) {
t.printStackTrace(System.out);
}
new Generic<Double, Map<String, Object>>().displayInfo();
}
}
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 25, 2008 3:10 AM
in response to: pietblok
|
 |
Helpful |
|
|
Information on generics or, more specifically, instances of types is not available at runtime. You may try to work one way or the other, but the information is simply not there at runtime; generics are purely a tool for the compiler to help you limit the number of casts and possible programming errors.
This leads to some curiosities. Collection<String> and Collection<Object> represent the exact same class. And a Collection<K> is clueless as to what this K is, exactly; the compiler did static analysis of the code and enforced that only Ks could be supplied, and automagically added casts when you iterate over the collection's contents.
An implication is that type information must be explicitly provided if you want to access it at runtime.
Map<Thread.State, V> mapping = new EnumMap<Thread.State, V>(Thread.State.class);
EnumMap needs to know what enum you're actually using, even if it's obvious to the compiler. This is because EnumMap needs to access the enum universe, which is bound to the enum class itself.
But what about EnumSet? You don't need to supply a class there, and it knows what type the keys are. That's correct in most cases, but that's also because you either supply specific instances of enums (which implicitly carry their class information) or you do call with the class itself: EnumSet.noneOf(Class<E>).
As a rule, assume that the runtime is not aware of generics.
Hope this helps, Jonathan
|
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 25, 2008 4:25 AM
in response to: tarbo
|
|
|
Thank you Jonathan,
Yes, probably you are quite right. Everything I read so far on the subject confirms what you are saying.
Nevertheless, it leaves me a quite unsatisfied. I strongly have a feeling that somehow, somewhere, the information that I need is available. See the example where I, under rather limiting circumstances, can find it.
But thanks anyway.
Piet
|
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 25, 2008 9:23 AM
in response to: pietblok
|
 |
Helpful |
|
|
The information you see is the result of solidifying a class through extension.
One of the reasons, I imagine, for the class format from Java 1.5 to be incompatible with the classes of 1.4.x and earlier is that some support needed to be added on the class level, but this support was not added on the instance level.
Suppose you have the following class hierarchy:
Object
|
+--- Collection<T>
|
+--- StringCollection
interface StringCollection extends Collection<String> {}
Further suppose that you do not have the Java source available, only the compiled class files. The compiler must be aware of Collection's type parameter somehow, so the class representing Collection must have fields indicating it has a single type parameter with no bounds.
When you ask for type parameters from Collection<String>, all you'll get out of it is that it takes a single parameter and is unbounded. When you ask a StringCollection that extends from Collection<String> about type parameters, it will reply "I have none".
But when you retrieve StringCollection's superclass, you'll find it's not just Collection, but Collection<String>. Yes, it's the same class, but StringCollection must know the value of this type parameter for it to have a non-generic API. This is why Collection<String> doesn't know it holds Strings, but a StringCollection does, and this is also why you have the getGenericSuperclass() method.
Which is also the reason, I suspect, that you can retrieve information if you dummy subclass your generified class. Your dummy subclass does not have type parameters, its parent does, so it needs to know what to conform to.
I haven't fact-checked this; it's just a hypothesis. It may be so cruelly wrong that no-one has the will to protest. 
Anyway, my two cents, Jonathan
|
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 25, 2008 3:27 PM
in response to: tarbo
|
|
|
Tarbo,
in your situation you can use the Class.getGenericInterfaces() method (or getGenericSuperclass() method if it was an extension of a generic abstract class) - this returns a Type instead of a Class. For an interface that extends a generic interface and specifies its type parameter (like in the case of your StringCollection specifying String for generic parameter T in Collection), the Types returned will implement ParameterizedType. You can then use the getActualTypeArguments() or getRawType() to work out what type StringCollection specified for Collection's T (getRawType() I think always returns a Class object).
|
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 25, 2008 4:31 PM
in response to: pietblok
|
 |
Correct |
|
|
Hi, Piet
Generics are implemented by erasure, this means thta they enforce their type constraints only at compile time and discard their type information at runtime. This allows generic code to interoperate with the legacy code that doesn't use generics.
On the other hand arrays in contrast to generics are reified - arrays known and ensure their element types at runtime - an attempt to insert an instance of incompatible class to an array will result in the ArrayStoreException. That's why Java prohibites creation of the generic arrays (contract of an array will be violated). This means that you must know the class of the array elements to create new array cleanly/safely (without compilation warnings or possible problems at runtime).
So the obvious solution to your problem is to provide type information explicitly (approach taken by core Java libraries). Fortunately there is common idiom of using class literal (Class object) to pass such information. When you use such class literal among methods it is called type token. Once you have added this token you have full information that is needed to create an array:
public <T> T[] createEmptyArray(Class<T> type, int size){
return (T[]) Array.newInstance(type, size);
}
or
public T[] createEmptyArray(int size){
return (T[]) Array.newInstance(type, size);
}
if you will pass type token to the constructor/factory method and save it in the instance field.
If for any reason it is not possible consider following possibilites:
- Use List instead of array - this will allow you to create new List without actual type of the formal parameter and you will also ommit compiler warning.
public <T> List<T> createEmptyList(int initialSize){ return new ArrayList<T>(initialSize); } - If and only if you need an array anyway and there is no way to provide type information explictly. You can benefict from the facts that arrays are covariant. This means that if type A is a subtype of type B then the array type A[] is subtype of B[] (e.g. Integer[] is a subtype of Number[] which is subtype of Object[]). With this in mind you can fake array creation by creating "super" array and then cast it to the type T[]. Ofcourse compiler will generate warning which you can suppress, but this already indicates that the operation is not type safe and the result array may not fullfil contract of the array in certain circumstances (e.g. it will be possible to insert wrong objects into array without ArrayStoreException). Which "super" array to create depends on whether your type parameter is bounded or unbounded. In the later case you will have to use Object[] since T can stand for any type. This is the worst case because it will be possible to store any object in the result array:
GenericClass<String> stringHolder=new GenericClass<String>(); String[] strArr = stringHolder.createEmptyArray(1); // Creates new Object[] array Object[] objArr = strArr; // Always legal, arrays are covariant objArr[0] = Boolean.TRUE; // Succeeds In the case of bounded type parameter you should create array of the bound type (e.g. in your example this would be Number[] since parameter T has type Number as the upper bound). This approach will be a bit safer and at least it will fail in some cases at runtime: GenericClass<Integer> stringHolder=new GenericClass<Integer>(); Integer[] intArr = stringHolder.createEmptyArray(1); // Creates new Number[] array Number[] numArr = intArr; // Always legal, arrays are covariant numArr[0] = Boolean.TRUE; // Fails at runtime with ArrayStoreException numArr[0] = Double.NaN; // Succeeds
Conclusions: avoid arrays. If you need them provide type information explicitly when array must be created. If you can't provide such information you can create array of Object[] or array of the "bound" type and then return it by casting to the formal parameter array (e.g. T[]).
I hope this information will be helpful for you!
P.S. You can find more information on the topic here:
|
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 26, 2008 3:42 AM
in response to: gezr
|
|
|
Thank you all for the valuable responses.
I need some time to study them closely.
Meanwhile, for my own understanding, I started to code a small analysis tool that dumps all information pertaining to generic info into an xml file. That makes studying hopefully a little easier.
When done I'll let you know.
Thanks
Piet
|
|
|
|
|
|
|
|
Re: Generics and getting the actual type programmatically
Posted:
Sep 26, 2008 8:30 AM
in response to: pietblok
|
|
|
Hi,
If anyone is interested: I wrote a very very tiny blog on the subject. Source is available that may help to visualize generic information available in a Class.
http://www.pbjar.org/blogs/generics/
But It didn't help me much 
Thanks for your input
Piet
|
|
|
|
|