Как проверить (универсальный (универсальный аргумент))?

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

Это вопрос, полученный из Построить родовое дерево с наследованием. Я открываю этот вопрос как отдельный вопрос, потому что это связано не только с проблемой дерева. Это скорее общая проблема и проблема класса.

Вопрос

Чтобы лучше проиллюстрировать коды, у нас есть 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"
}

Обратите внимание, что мы все еще не можем получить "класс 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, если способность этого трюка настолько ограничена, это не очень полезно вообще.)

1 ответ

Проверьте TypeTools для этого. Пример:

List<String> stringList = new ArrayList<String>() {};
Class<?> stringType = TypeResolver.resolveRawArgument(List.class, stringList.getClass());
assert stringType == String.class;
Другие вопросы по тегам