Я создаю общий класс Tree<T>
, который поддерживает наследование поддеревьев. Но я столкнулся с некоторыми проблемами. Не могли бы вы помочь мне?
Описание
Давайте определим класс Tree
и класс BlueTree
, где BlueTree extends Tree
.
Давайте определим класс Leaf
и класс RedLeaf
, где RedLeaf extends Leaf
. Они используются как «данные», содержащиеся в Деревьях.
Tree<Leaf>
означает дерево типа Tree
, а его "данные" имеют тип Leaf
.
Для наследования (это не правильное наследование Java):
- a
Tree<Leaf>
can have child of typeTree<Leaf>
,Tree<RedLeaf>
,BlueTree<Leaf>
, andBlueTree<RedLeaf>
.
.
- a
Tree<RedLeaf>
can have child of typeTree<RedLeaf>
, andBlueTree<RedLeaf>
,- но не
Tree<Leaf>
илиBlueTree<Leaf>
.
.
- a
BlueTree<Leaf>
can have child of typeBlueTree<Leaf>
, andBlueTree<RedLeaf>
,- но не
Tree<Leaf>
илиTree<RedLeaf>
.
.
- a
BlueTree<RedLeaf>
can have child of typeBlueTree<RedLeaf>
,- но не
Tree<Leaf>
,Tree<RedLeaf>
илиBlueTree<Leaf>
.
*Здесь «ребенок» означает ветви/листья Дерева.
(немного сложно, поэтому я разделяю строки.)
Код
(Если у вас есть решение, возможно, вам не нужно читать подробную иллюстрацию моих попыток ниже. Если вы хотите найти решение вместе, мой код может подсказать вам некоторые идеи или запутать их.)
Первая пробная версия: (простая)
// This is the focus of this question, the class signature
public class Tree<T> {
// some fields, but they are not important in this question
private Tree<? super T> mParent;
private T mData;
private ArrayList<Tree<? extends T>> mChildren;
// This is the focus of this question, the addChild() method signature
public void addChild(final Tree<? extends T> subTree) {
// add the subTree to mChildren
}
}
Эта структура класса отвечает большинству требований в описании. Кроме того, это позволяет
class BlueTree<T> extends Tree<T> { }
class Leaf { }
class RedLeaf extends Leaf { }
Tree<Leaf> tree_leaf = new Tree<Leaf>();
BlueTree<Leaf> blueTree_leaf = new BlueTree<Leaf>();
blueTree_leaf.addChild(tree_leaf); // should be forbidden
что нарушает
BlueTree<Leaf>
не может иметь дочерний элемент типаTree<Leaf>
.
Проблема в том, что в BlueTree<Leaf>
его сигнатура метода addChild()
по-прежнему
public void addChild(final Tree<? extends Leaf> subTree) {
// add the subTree to mChildren
}
В идеальном случае сигнатура метода BlueTree<Leaf>.addChild()
изменяется (автоматически, при наследовании) на
public void addChild(final BlueTree<? extends Leaf> subTree) {
// add the subTree to mChildren
}
(Обратите внимание, что этот метод не может переопределить указанный выше метод путем наследования, так как типы параметров различаются.)
Есть обходной путь. Мы можем добавить проверку наследования класса и для этого случая бросить RuntimeException
:
public void addChild(final Tree<? extends Leaf> subTree) {
if (this.getClass().isAssignableFrom(subTree.getClass()))
throw new RuntimeException("The parameter is of invalid class.");
// add the subTree to mChildren
}
Но делать это ошибкой времени компиляции гораздо лучше, чем ошибкой времени выполнения. Я хотел бы применить это поведение во время компиляции.
Вторая пробная версия
Проблема в первой пробной структуре заключается в том, что тип параметра Tree
в методе addChild()
не является параметром универсального типа. Таким образом, он не будет обновляться при наследовании. На этот раз давайте также попробуем сделать его параметром универсального типа.
Во-первых, определите общий класс Tree
.
public class Tree<T> {
private Tree<? super T> mParent;
private T mData;
private ArrayList<Tree<? extends T>> mChildren;
/*package*/ void addChild(final Tree<? extends T> subTree) {
// add the subTree to mChildren
}
}
Затем TreeManager
, который управляет объектом Tree
.
public final class TreeManager<NodeType extends Tree<? super DataType>, DataType> {
private NodeType mTree;
public TreeManager(Class<NodeType> ClassNodeType) {
try {
mTree = ClassNodeType.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
public void managerAddChild(final NodeType subTree) {
mTree.addChild(subTree);
// compile error: The method addChild(Tree<? extends capture#1-of ? super DataType>)
// in the type Tree<capture#1-of ? super DataType>
// is not applicable for the arguments (NodeType)
}
// for testing
public static void main(String[] args) {
@SuppressWarnings("unchecked")
TreeManager<Tree <Leaf> , Leaf> tm_TreeLeaf_Leaf = new TreeManager<Tree <Leaf>, Leaf> ((Class<Tree <Leaf>>) new Tree <Leaf> ().getClass());
TreeManager<Tree <RedLeaf>, RedLeaf> tm_TreeRedLeaf_RedLeaf = new TreeManager<Tree <RedLeaf>, RedLeaf>((Class<Tree <RedLeaf>>) new Tree <RedLeaf>().getClass());
TreeManager<BlueTree<Leaf> , Leaf> tm_BlueTreeLeaf_Leaf = new TreeManager<BlueTree<Leaf>, Leaf> ((Class<BlueTree<Leaf>>) new BlueTree<Leaf> ().getClass());
TreeManager<BlueTree<RedLeaf>, RedLeaf> tm_BlueTreeRedLeaf_RedLeaf = new TreeManager<BlueTree<RedLeaf>, RedLeaf>((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
System.out.println(tm_TreeLeaf_Leaf .mTree.getClass()); // class Tree
System.out.println(tm_TreeRedLeaf_RedLeaf .mTree.getClass()); // class Tree
System.out.println(tm_BlueTreeLeaf_Leaf .mTree.getClass()); // class BlueTree
System.out.println(tm_BlueTreeRedLeaf_RedLeaf.mTree.getClass()); // class BlueTree
@SuppressWarnings("unchecked")
TreeManager<Tree <Leaf> , RedLeaf> tm_TreeLeaf_RedLeaf = new TreeManager<Tree <Leaf>, RedLeaf>((Class<Tree <Leaf>>) new Tree <Leaf> ().getClass());
TreeManager<BlueTree<Leaf> , RedLeaf> tm_BlueTreeLeaf_RedLeaf = new TreeManager<BlueTree<Leaf>, RedLeaf>((Class<BlueTree<Leaf>>) new BlueTree<Leaf> ().getClass());
System.out.println(tm_TreeLeaf_RedLeaf .mTree.getClass()); // class Tree
System.out.println(tm_BlueTreeLeaf_RedLeaf .mTree.getClass()); // class BlueTree
// the following two have compile errors, which is good and expected.
TreeManager<Tree <RedLeaf>, Leaf> tm_TreeRedLeaf_Leaf = new TreeManager<Tree <RedLeaf>, Leaf> ((Class<Tree <RedLeaf>>) new Tree <RedLeaf>().getClass());
TreeManager<BlueTree<RedLeaf>, Leaf> tm_BlueTreeRedLeaf_Leaf = new TreeManager<BlueTree<RedLeaf>, Leaf> ((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
}
}
TreeManager
инициализируется без проблем; хотя очереди немного длинные. Он также соответствует правилам в описании.
Однако возникает ошибка компиляции при вызове Tree.addChild()
внутри TreeManager
, как показано выше.
Третья пробная версия
Чтобы исправить ошибку компиляции во втором испытании, я попытался изменить сигнатуру класса (на еще более длинную). Теперь mTree.addChild(subTree);
компилируется без проблем.
// T is not used in the class. T is act as a reference in the signature only
public class TreeManager3<T, NodeType extends Tree<T>, DataType extends T> {
private NodeType mTree;
public TreeManager3(Class<NodeType> ClassNodeType) {
try {
mTree = ClassNodeType.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
public void managerAddChild(final NodeType subTree) {
mTree.addChild(subTree); // compile-error is gone
}
}
И я протестировал его с очень похожим кодом, что и во втором испытании. Создает без проблем, как и второй триал. (Только еще дольше.)
(Можно пропустить приведенный ниже блок кода, так как он просто логически повторяется.)
public static void main(String[] args) {
@SuppressWarnings("unchecked")
TreeManager3<Leaf , Tree <Leaf> , Leaf> tm_TreeLeaf_Leaf = new TreeManager3<Leaf , Tree <Leaf>, Leaf> ((Class<Tree <Leaf>>) new Tree <Leaf> ().getClass());
TreeManager3<RedLeaf, Tree <RedLeaf>, RedLeaf> tm_TreeRedLeaf_RedLeaf = new TreeManager3<RedLeaf, Tree <RedLeaf>, RedLeaf>((Class<Tree <RedLeaf>>) new Tree <RedLeaf>().getClass());
TreeManager3<Leaf , BlueTree<Leaf> , Leaf> tm_BlueTreeLeaf_Leaf = new TreeManager3<Leaf , BlueTree<Leaf>, Leaf> ((Class<BlueTree<Leaf>>) new BlueTree<Leaf> ().getClass());
TreeManager3<RedLeaf, BlueTree<RedLeaf>, RedLeaf> tm_BlueTreeRedLeaf_RedLeaf = new TreeManager3<RedLeaf, BlueTree<RedLeaf>, RedLeaf>((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
System.out.println(tm_TreeLeaf_Leaf .mTree.getClass()); // class Tree
System.out.println(tm_TreeRedLeaf_RedLeaf .mTree.getClass()); // class Tree
System.out.println(tm_BlueTreeLeaf_Leaf .mTree.getClass()); // class BlueTree
System.out.println(tm_BlueTreeRedLeaf_RedLeaf.mTree.getClass()); // class BlueTree
@SuppressWarnings("unchecked")
TreeManager3<Leaf , Tree <Leaf> , RedLeaf> tm_TreeLeaf_RedLeaf = new TreeManager3<Leaf , Tree <Leaf>, RedLeaf>((Class<Tree <Leaf>>) new Tree <Leaf> ().getClass());
TreeManager3<Leaf , BlueTree<Leaf> , RedLeaf> tm_BlueTreeLeaf_RedLeaf = new TreeManager3<Leaf , BlueTree<Leaf>, RedLeaf>((Class<BlueTree<Leaf>>) new BlueTree<Leaf> ().getClass());
System.out.println(tm_TreeLeaf_RedLeaf .mTree.getClass()); // class Tree
System.out.println(tm_BlueTreeLeaf_RedLeaf .mTree.getClass()); // class BlueTree
// the following two have compile errors, which is good and expected.
TreeManager3<RedLeaf, Tree <RedLeaf>, Leaf> tm_TreeRedLeaf_Leaf = new TreeManager3<RedLeaf, Tree <RedLeaf>, Leaf> ((Class<Tree <RedLeaf>>) new Tree <RedLeaf>().getClass());
TreeManager3<RedLeaf, BlueTree<RedLeaf>, Leaf> tm_BlueTreeRedLeaf_Leaf = new TreeManager3<RedLeaf, BlueTree<RedLeaf>, Leaf> ((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
}
Однако возникает проблема, когда я пытаюсь позвонить TreeManager3.managerAddChild()
.
tm_TreeLeaf_Leaf.managerAddChild(new Tree<Leaf>());
tm_TreeLeaf_Leaf.managerAddChild(new Tree<RedLeaf>()); // compile error: managerAddChild(Tree<RedLeaf>) cannot cast to managerAddChild(Tree<Leaf>)
tm_TreeLeaf_Leaf.managerAddChild(new BlueTree<Leaf>());
tm_TreeLeaf_Leaf.managerAddChild(new BlueTree<RedLeaf>()); // compile error: managerAddChild(BlueTree<RedLeaf>) cannot cast to managerAddChild(BlueTree<Leaf>)
Это понятно. TreeManager3.managerAddChild(NodeType)
означает TreeManager3.managerAddChild(Tree<T>)
, и в типе параметра нет подстановочного знака Tree<? extends T>
, как Tree.addChild(final Tree<? extends T> subTree)
в первом испытании.
Умоляю тебя о помощи...
У меня уже закончились идеи. Я шел в неправильном направлении, чтобы решить эту проблему? Я потратил много времени на написание этого вопроса и изо всех сил старался сделать его более читабельным, понятным и понятным. Я должен извиниться, что он все еще очень длинный и многословный. Но не могли бы вы помочь, если знаете, как это сделать, или, пожалуйста, дайте мне какие-нибудь идеи, которые у вас есть? Каждый ваш вклад высоко ценится. Большое спасибо!
Изменить № 1 (для комментария ниже)
Основываясь на первой пробной версии, разрешить изменение mChildren
только с помощью addChild()
(и других методов с проверкой isAssignableFrom()
), так что даже разрешение пользователям наследования Tree
и переопределение addChild()
не нарушит целостность Дерева.
/developer/util/Tree.java
package developer.util;
import java.util.ArrayList;
public class Tree<T> {
private Tree<? super T> mParent;
private final ArrayList<Tree<? extends T>> mChildren = new ArrayList<Tree<? extends T>>();
public int getChildCount() { return mChildren.size(); }
public Tree<? extends T> getLastChild() { return mChildren.get(getChildCount()-1); }
public void addChild(final Tree<? extends T> subTree) {
if (this.getClass().isAssignableFrom(subTree.getClass()) == false)
throw new RuntimeException("The child (subTree) must be a sub-class of this Tree.");
subTree.mParent = this;
mChildren.add(subTree);
}
}
/user/pkg/BinaryTree.java
package user.pkg;
import developer.util.Tree;
public class BinaryTree<T> extends Tree<T> {
@Override
public void addChild(final Tree<? extends T> subTree) {
if (getChildCount() < 2) {
super.addChild(subTree);
}
}
}
/Main.java
import user.pkg.BinaryTree;
import developer.util.Tree;
public class Main {
public static void main(String[] args) {
Tree<Integer> treeOfInt = new Tree<Integer>();
BinaryTree<Integer> btreeOfInt = new BinaryTree<Integer>();
treeOfInt.addChild(btreeOfInt);
System.out.println(treeOfInt.getLastChild().getClass());
// class user.pkg.BinaryTree
try {
btreeOfInt.addChild(treeOfInt);
} catch (Exception e) {
System.out.println(e);
// java.lang.RuntimeException: The child (subTree) must be a sub-class of this Tree.
}
System.out.println("done.");
}
}
Что вы думаете?
public class Tree<Tree<T>, T> { }
не компилируется, к сожалению. - person midnite   schedule 23.08.2013public class Tree<TREE extends Tree<?,?>,LEAF extends Leaf> { }
- person user902383   schedule 23.08.2013addChild()
и методаsetParent()
. Я не могу дождаться, чтобы скоро построить весь класс Tree! Для справки я использовал подписиclass Tree2<TREE extends Tree2<?,?>, LEAF> { }
,class BlueTree2<BT extends BlueTree2<?,?>,L> extends Tree2<BT,L> { }
,Tree2<? super TREE, ? super LEAF> mParent;
иvoid addChild(Tree2<? extends TREE, ? extends LEAF> subTree) {}
. - person midnite   schedule 23.08.2013class Tree<TREE extends Tree<?,?>, DATA> {}
и у него есть подклассыBigBlueTree extends BlueTree extends Tree
. При создании объекта можно фактически выполнитьTree<BigBlueTree<?,?>, String> tree = new Tree<BigBlueTree<?,?>, String>();
, что сделаетBigBlueTree
аргументом типаTree<>
и (может) вызвать ошибки в методах. Как убедиться, что пользователи передают именноTree
в аргументе типаTree<>
? - person midnite   schedule 31.08.2013class Tree<TREE equals Tree<?,?>, DATA> {}
(ноequals
не является правильным ключевым словом). я пробовалclass Tree<TREE extends Tree<TREE,?>, DATA> {}
, но не могу (или не знаю как) создать объект из этого класса. Большое спасибо!! - person midnite   schedule 31.08.2013Tree<BigBlueTree<?,?>, String> tree
вместоBigBlueTree<BigBlueTree<?,?>, String> tree
? - person user902383   schedule 02.09.2013Class<TREE>
в конструктор для проверки (но это усложнит конструктор) или (2) разрешить модифицировать только сам узел и дочерние узлы в общедоступном API, сделать все методы о родитель, как иattachParent()
частный, я думаю такой, что мы можем обеспечить целостность структуры древовидной иерархии. - person midnite   schedule 02.09.2013Tree.class
в конструктор может быть правильным путем. во-вторых, как я понимаю, под пользователем вы подразумеваете программиста, и вы разрабатываете библиотеку. в этом случае, я думаю, вы можете предположить, что он знает, что делает. - person user902383   schedule 02.09.2013Class<>
в конструктор для проверки. Но при этом остается проблема. я поставил это в отдельный вопрос. Не могли бы вы взглянуть? stackoverflow.com/questions/18581788 - person midnite   schedule 03.09.2013Tree<Tree<?,?>, DATA>
очень хрупкая, как при определении подклассов, так и при создании объектов. Не говоря уже о том, что пользователи могут неправильно использовать или взломать его, даже мне сложно сделать неправильный синтаксис (точно так же, как ответ Пола< /а> сказал). я собираюсь вернуться к моей самой первой пробной версии с проверкойisAssignableFrom()
. Как вы думаете, это хорошая идея? - person midnite   schedule 04.09.2013addChild
, чтобы избежать проверки. Просто быстрый вопрос, хотите ли вы разрешить пользователям создавать свои собственные деревья? если да, ваше решение может запретить им создавать дерево, которое принимает любое дерево, но не синее. Если вы не хотите, чтобы они позволяли создавать собственные деревья, возможно, вам следует рассмотреть шаблон строителя? - person user902383   schedule 05.09.2013mChildren
приватным, поэтому подклассы должны вызывать черезsuper.addChild()
при добавлении дочернего элемента. Я думаю, что это может привести к проверке. Пожалуйста, взгляните на мои добавленные коды выше. Спасибо. - person midnite   schedule 05.09.2013