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