Как проверить (общий (общий аргумент))?

Фон (о котором нам не нужно беспокоиться)

Это вопрос, полученный из Build A Generic Tree With Inheritance. Я открываю этот вопрос как отдельный вопрос, потому что это связано не только с проблемой дерева. Вместо этого это скорее общая и классовая проблема.

Вопрос

Чтобы лучше проиллюстрировать коды, у нас есть класс Tree, класс SubTree и класс WrongSubTree:

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> {
}

При создании объекта мы хотели бы проверить, равен ли общий аргумент классу самого объекта:

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

(Обратите внимание, что в 4 строках выше нет ни ошибок компиляции, ни исключений во время выполнения на данный момент.)

Моя пробная версия

Для этого мы пытаемся добавить параметр Class<> в конструкторы:

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);
    }
}

(Приведенные выше определения классов являются допустимыми кодами Java.)

Проблема

Но я не знаю, как вызвать этот конструктор:

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

Обе 2 строки выше вызывают ошибки компиляции.

Я думаю, это потому, что конструктор public Tree(Class<TREE> clazz) {} ожидает Class<Tree<?,?>>, но не Class<Tree>. Однако мы не можем сделать Tree<?,?>.class.

Причина в том, что я пытался изменить класс на:

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

Нет ошибки компиляции.

Однако следующее вызывает ту же ошибку компиляции:

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

Изменить №1

Основываясь на моем комментарии ниже, я попробовал это. Надеюсь, это поможет для вдохновения.

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"
}

Обратите внимание, что мы по-прежнему не можем получить "class java.lang.String" внутри SimpleClass.

Что еще более важно, если мы используем аргумент типа <T> для создания экземпляра объекта из другого класса, мы все равно не сможем получить из него параметр типа:

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>"
    }
}

Обратите внимание, что это означает, что аргумент типа AnotherClass не может быть раскрыт в SimpleClass, где он находится вне самого класса!

Насколько я понимаю, мы можем использовать анонимный подкласс и трюк getGenericSuperclass() только в том месте, где он уже знает ответ. Как и в main(), это место, где class java.lang.String действительно определяется как аргумент типа.

(IMO, если возможности этого трюка настолько ограничены, он вообще бесполезен.)


person midnite    schedule 02.09.2013    source источник
comment
Похоже, вы хотели бы использовать шаблон токена супертипа, например. Гуава TypeToken.   -  person Paul Bellora    schedule 03.09.2013
comment
@PaulBellora Спасибо за ваш ответ. Я все еще смотрю на TypeToken. Между тем, я нашел обходной путь для моего вопроса выше, а именно: Tree<Tree<?,?>, String> tree01a = new Tree<Tree<?,?>, String>((Class<Tree<?, ?>>) Class.forName(Tree.class.getName()));. Это очень долго, но это делает работу. Однако, что более важно, я обнаружил эту проверку класса можно взломать!!!   -  person midnite    schedule 03.09.2013
comment
Игнорируя проблематичные параметры рекурсивного типа, TypeToken кажется отвечающим всем требованиям. Если он (или другая реализация токенов супертипа) не работает, обновите вопрос, объяснив, почему.   -  person Paul Bellora    schedule 03.09.2013
comment
если вы измените свой конструктор на public Tree(Class<? extends Tree> clazz) {...}, вы сможете назвать его Tree<>(Tree.class)   -  person user902383    schedule 03.09.2013
comment
@ user902383, Да. Но оставьте его равным public Tree(Class<TREE> clazz) {...}, параметр типа TREE будет принудительно обновлен до подклассов соответственно. И все конструкторы Tree имеют эту проверку, опустите конструктор с нулевым аргументом. Таким образом, все подклассы Tree вынуждены вызывать суперконструктор и проходить эту проверку. Однако [эта проверка ненадежна] (stackoverflow.com/questions/18582360). Вдохновленный @Paul и TypeToken, я пытаюсь проверить возвращаемый тип общедоступного метода.   -  person midnite    schedule 03.09.2013
comment
Уважаемый @PaulBellora, насколько я понимаю, TypeToken играет с анонимным подклассом new IKnowMyType<String>() {}.type и, вероятно, использует getGenericSuperclass() и приводит его к ParameterizedType в своей реализации. На самом деле это восходит к вопросу Как получить параметр универсального типа? И TypeToken, и TypeReference работают вне сам класс. Но можем ли мы получить класс динамически назначаемого аргумента типа прямо внутри конструктора/метода класса? (многие говорили, что нельзя.)   -  person midnite    schedule 03.09.2013
comment
@PaulBellora: обновил мой вопрос выше. Не могли бы вы взглянуть? Большое спасибо!   -  person midnite    schedule 03.09.2013
comment
@midnite Вы можете использовать TypeToken Guava, используя new TypeToken<T>(getClass()) { }, но только в том случае, если этот вызов происходит в экземпляре, тип среды выполнения которого разрешил T, например class SimpleClassChild extends SimpleClass<String>.   -  person Paul Bellora    schedule 03.09.2013
comment
@PaulBellora, да. Только там, где разрешен тип времени выполнения, мы можем получить фактический (String) класс. Я провел всю ночь, изучая и пробуя TypeToken Гуавы, и теперь собираюсь отказаться от этого. я как бы согласен, что невозможно получить общий аргумент, где тип времени выполнения не разрешен. Спасибо за вашу любезную помощь!! На самом деле я создаю Дерево с наследованием, для которого требуется такая структура, как ваш ответ здесь. Я не откажусь от Дерева. Думаю, мне пора переосмыслить. :-)   -  person midnite    schedule 04.09.2013


Ответы (1)


Для этого воспользуйтесь TypeTools. Пример:

List<String> stringList = new ArrayList<String>() {};
Class<?> stringType = TypeResolver.resolveRawArgument(List.class, stringList.getClass());
assert stringType == String.class;
person Jonathan    schedule 26.10.2013