Background (that we don't really need to worry about)
This is a question derived from Build A Generic Tree With Inheritance . I open this one as a separate question because this is not only related to a Tree problem. This is more a Generic and Class problem instead.
Question
To be better illustrated by codes, we have a Tree
class, a SubTree
class, and a WrongSubTree
class:
class Tree<TREE extends Tree<?,?>, DATA> {
}
class SubTree<STREE extends SubTree<?,?>, DATA> extends Tree<STREE, DATA> {
}
class WrongSubTree<WSTREE extends Tree<?,?>, DATA> extends Tree<WSTREE, DATA> {
}
While object creation, we would like to check if the generic argument equals to the class of the object itself:
Tree<Tree<?,?>, String> tree01 = new Tree<Tree<?,?>, String>(); // equals : OK
Tree<SubTree<?,?>, String> tree02 = new Tree<SubTree<?,?>, String>(); // (!) not equals
SubTree<SubTree<?,?>, String> tree03 = new SubTree<SubTree<?,?>, String>(); // equals : OK
WrongSubTree<Tree<?,?>, String> tree04 = new WrongSubTree<Tree<?,?>, String>(); // (!) not equals
(Note that the 4 lines above have no compile errors and no runtime exceptions, for now.)
My trial
To do so, we try to add a Class<>
parameter in the constructors:
class Tree<TREE extends Tree<?,?>, DATA> {
public Tree(Class<TREE> clazz) {
System.out.println(this.getClass());
System.out.println(clazz);
System.out.println();
if (this.getClass() != clazz)
throw new RuntimeException();
}
}
class SubTree<STREE extends SubTree<?,?>, DATA> extends Tree<STREE, DATA> {
public SubTree(Class<STREE> clazz) {
super(clazz);
}
}
class WrongSubTree<WSTREE extends Tree<?,?>, DATA> extends Tree<WSTREE, DATA> {
public WrongSubTree(Class<WSTREE> clazz) {
super(clazz);
}
}
(The above class definitions are valid java codes.)
Problem
But i don't know how to call that constructor:
Tree<Tree<?,?>, String> tree01a = new Tree<Tree<?,?>, String>(Tree.class);
// The constructor Tree<Tree<?,?>,String>(Class<Tree>) is undefined
Tree<Tree<?,?>, String> tree01b = new Tree<Tree<?,?>, String>(Tree<?,?>.class);
// Syntax error, insert "Dimensions" to complete ArrayType
Both 2 lines above cause compile-errors.
i guess it is because the constructor public Tree(Class<TREE> clazz) {}
is expecting Class<Tree<?,?>>
, but not Class<Tree>
. However, we cannot do Tree<?,?>.class
.
The reason is i tried to change the class to:
class Tree<TREE extends Tree<?,?>, DATA> {
public Tree(Class<Tree> clazz) { // changed
System.out.println(this.getClass());
System.out.println(clazz);
System.out.println();
if (this.getClass() != clazz)
throw new RuntimeException();
}
}
Tree<Tree<?,?>, String> tree01a = new Tree<Tree<?,?>, String>(Tree.class);
There is no compile-error.
However, the following causes the same compile-error:
class Tree<TREE extends Tree<?,?>, DATA> {
public Tree(Class<Tree<?,?>> clazz) { // changed
System.out.println(this.getClass());
System.out.println(clazz);
System.out.println();
if (this.getClass() != clazz)
throw new RuntimeException();
}
}
Tree<Tree<?,?>, String> tree01a = new Tree<Tree<?,?>, String>(Tree.class);
// The constructor Tree<Tree<?,?>,String>(Class<Tree>) is undefined
Edit #1
Based on my comment below, i tried this one. Hope it helps for some inspirations.
static class SimpleClass<T> {
private SimpleClass(Object dummy) {
// dummy constructor, avoid recursive call of the default constructor
}
SimpleClass() {
SimpleClass<T> myself = new SimpleClass<T>(new Object()) {};
System.out.println(((ParameterizedType) myself.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
// prints "T"
TypeReference<SimpleClass<T>> typeRef = new TypeReference<SimpleClass<T>>() {};
System.out.println(typeRef.getType());
// prints "Main.Main$SimpleClass<T>"
}
void func() {
SimpleClass<T> myself = new SimpleClass<T>(new Object()) {};
System.out.println(((ParameterizedType) myself.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
// prints "T"
TypeReference<SimpleClass<T>> typeRef = new TypeReference<SimpleClass<T>>() {};
System.out.println(typeRef.getType());
// prints "Main.Main$SimpleClass<T>"
}
}
public static void main(String[] args) {
SimpleClass<String> simpleObj = new SimpleClass<String>();
simpleObj.func();
SimpleClass<String> outsideSimpleClass = new SimpleClass<String>(){};
System.out.println(((ParameterizedType) outsideSimpleClass.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
// prints "class java.lang.String"
}
Note we still cannot get "class java.lang.String" inside SimpleClass
.
More importantly, if we use the Type Argument <T>
to instantiate an object from another class, we still cannot get the type parameter from it:
static class AnotherClass<T> {
private AnotherClass(Object dummy) {}
}
static class SimpleClass<T> {
SimpleClass() {
AnotherClass<T> another = new AnotherClass<T>(new Object()) {};
System.out.println(((ParameterizedType) another.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
// prints "T"
TypeReference<AnotherClass<T>> anotherTypeRef = new TypeReference<AnotherClass<T>>() {};
System.out.println(anotherTypeRef.getType());
// prints "Main.Main$AnotherClass<T>"
}
void func() {
AnotherClass<T> another = new AnotherClass<T>(new Object()) {};
System.out.println(((ParameterizedType) another.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
// prints "T"
TypeReference<AnotherClass<T>> anotherTypeRef = new TypeReference<AnotherClass<T>>() {};
System.out.println(anotherTypeRef.getType());
// prints "Main.Main$AnotherClass<T>"
}
}
Note that this means the Type Argument of AnotherClass
cannot be revealed in SimpleClass
, where it is a place outside the class itself!
To my understanding, we can only use the anonymous sub-class & getGenericSuperclass()
trick at a place where it actually already know the answer. Such as in main()
, here is the place where class java.lang.String
is really defined as the Type Argument.
(IMO, if the ability of this trick is so restricted, it is not really useful at all.)