2

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

Community
  • 1
  • 1
midnite
  • 4,919
  • 7
  • 34
  • 51
  • It seems like you would want to use a "super type token" pattern, e.g. [Guava's `TypeToken`](https://code.google.com/p/guava-libraries/wiki/ReflectionExplained). – Paul Bellora Sep 02 '13 at 23:42
  • @PaulBellora Thanks for your reply. I am still looking into the `TypeToken`. Meanwhile, i found a workaround for my question above, which is: `Tree, String> tree01a = new Tree, String>((Class>) Class.forName(Tree.class.getName()));`. It is very long, but it does the job. However, more importantly, i discovered [this class checking can be hacked](http://stackoverflow.com/questions/18582360/generic-type-argument-checked-by-class-parameter-can-be-hacked-any-better-ways)!!! – midnite Sep 03 '13 at 00:25
  • Ignoring the problematic recursive type parameters, `TypeToken` seems to fit the bill. If it (or another implementation of super type tokens) doesn't work, please update the question explaining why. – Paul Bellora Sep 03 '13 at 01:33
  • if you change your constructor to `public Tree(Class extends Tree> clazz) {...}` you should be able to call it `Tree<>(Tree.class)` – user902383 Sep 03 '13 at 08:24
  • @user902383, Yes. But keep it to `public Tree(Class clazz) {...}`, the Type Parameter `TREE` will be forced to update to the sub-classes respectively. And all the constructors of `Tree` are having this check, omit the zero-argument constructor. So all sub-classes of `Tree` are forced to call the super constructor and go through this check. However, [this check is not reliable](stackoverflow.com/questions/18582360). Inspired by @Paul and the [TypeToken](https://code.google.com/p/guava-libraries/wiki/ReflectionExplained), i am trying to check by the return type of a public method. – midnite Sep 03 '13 at 09:31
  • Dear @PaulBellora, to my understanding, the `TypeToken` is playing with the anonymous sub-class `new IKnowMyType() {}.type` thing and probably using `getGenericSuperclass()` and cast it to `ParameterizedType` in its implementation. It actually goes back the question [How to get the Generic Type Parameter?](http://stackoverflow.com/questions/18337530) Both `TypeToken` and `TypeReference` works **outside** the class itself. But can we get the dynamically assigned Type Argument's class **right inside the class's constructor/method**? (many people said can't though.) – midnite Sep 03 '13 at 10:39
  • @PaulBellora: Updated my question above. Would you please have a look? Thanks a lot! – midnite Sep 03 '13 at 11:13
  • @midnite You can with Guava's `TypeToken` by using [`new TypeToken(getClass()) { }`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/reflect/TypeToken.html#TypeToken\(java.lang.Class\)), but only if that call happens in an instance whose runtime type has resolved `T`, for example `class SimpleClassChild extends SimpleClass`. – Paul Bellora Sep 03 '13 at 15:09
  • @PaulBellora, Yes. Only at where the runtime type has resolved, we can get the actual (String) class. I have spent all the night studying n trying on Guava's `TypeToken` and about to give up on this now. i kind of accept it is impossible to get the Generic Argument at where the runtime type is not resolved. Thanks for your kindly helps!! In fact i am building a [Tree with inheritance](http://stackoverflow.com/questions/18399922) - which requires structure like [your answer there](http://stackoverflow.com/a/7355094/697449). I won't give up on the Tree. I guess it's time for me to re-think. :-) – midnite Sep 04 '13 at 04:52

1 Answers1

0

Check out TypeTools for this. Example:

List<String> stringList = new ArrayList<String>() {};
Class<?> stringType = TypeResolver.resolveRawArgument(List.class, stringList.getClass());
assert stringType == String.class;
Jonathan
  • 5,298
  • 33
  • 46