Реализация универсального интерфейса с необработанным типом

У меня есть общее дерево, общий параметр - это тип данных, хранящийся в узлах:

class TreeNode<D>{  
    public D data;  
    .....
}

Затем интерфейс посетителя для использования вместе с пересечением дерева:

interface Visitor<D> {
    void visit(TreeNode<D> node);
}

Некоторые посетители могут воспользоваться дженериками:

class DataListCreator<D> implements Visitor<D> {
    List<D> dataList = new ArrayList<D>();
    public void visit(TreeNode<D> node) {
         dataList.add(node.data);
    }
    public List<D> getDataList() {
        return dataList;
    }

Но другие этого не делают, они бы лучше подошли к сырому классу

class NodeCounter implements Visitor {
    private int nodeCount = 0;
    public void visit(TreeNode node) {
        nodeCount++;
    }
    public int count() {
        return nodeCount;
    }

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

Visitor<?> 

с тем же результатом. Итак, мой вопрос: я вынужден использовать общий тип

NodeCounter<D> 

реализовать интерфейс посетителя ?.

Спасибо.


person Community    schedule 31.01.2010    source источник


Ответы (4)


приведенный выше код не компилируется

Я попытался скомпилировать ваш пример, и он отлично работает. Я использую Java 6. Какая у вас ошибка компиляции?

Вот что я успешно скомпилировал:

class TreeNode<D>{  
    public D data;  
}

interface Visitor<D> {
    void visit(TreeNode<D> node);
}

class NodeCounter implements Visitor {
    private int nodeCount = 0;
    public void visit(TreeNode node) {
        nodeCount++;
    }
    public int count() {
        return nodeCount;
    }
}
person Eli Acherkan    schedule 31.01.2010
comment
О, спасибо, что указали на это, я использую более старую версию InelliJ, которая выдавала мне эту ошибку. Я буду обновлять все, так как я не очень доволен удвоением моих классов - person ; 31.01.2010
comment
Я бы тоже этому не обрадовался :-) - person Eli Acherkan; 31.01.2010

Дженерики Java очень эффективны, и необработанные типы почти никогда не понадобятся.

Вероятно, вы захотите добавить некоторые символы подстановки. Например, посетителю может не потребоваться знать точный общий аргумент TreeNodes, который он посещает:

interface TreeNodeVisitor<D> {
    void visit(TreeNode<? extends D> node);
}

Возможно, лучше (?) TreeNode может не знать точный тип посетителя.

interface TreeNode<D> {
    void accept(TreeNodeVisitor<? super D> visitor);
}
person Tom Hawtin - tackline    schedule 31.01.2010
comment
В чем практическая разница между: void visit (TreeNode ‹? Extends D› node); и просто: недействительный визит (узел TreeNode ‹D›); Я имею в виду, что в обоих случаях метод посещения будет принимать те же типы в качестве аргумента. Или я ошибаюсь? Есть ли практическое преимущество в использовании этого более сложного синтаксиса? - person ; 31.01.2010
comment
@Tom, не могли бы вы привести рабочий (т.е. компилируемый) пример классов в этом примере, обобщенных, как вы предлагаете? - person Avi; 31.01.2010
comment
@Pepin, если бы у вас было if D было Object, то использование ? extends D позволило бы, скажем, TreeNode<String> в качестве аргумента. - person Tom Hawtin - tackline; 31.01.2010
comment
Вы имеете в виду наличие экземпляра TreeNode ‹String› и желание посетить его с экземпляром Visitor ‹Object› ?. В этом случае, я думаю, возникнут дополнительные проблемы, так как метод, выполняющий переход, ожидает посетителя ‹String› в качестве аргумента. - person ; 31.01.2010
comment
@Pepin Нет, если метод, который делает пересечение, ожидает Вистора ‹? super String ›как указал Том Хотин. - person ILMTitan; 01.02.2010
comment
Привет, ILMTitan, я попытался также изменить трансверсали - в строке совета Тома - и, похоже, все работает нормально. Однако я не уверен, следует ли мне сохранить эту версию или вернуться к более простой. Я еще не очень хорошо знаком с некоторыми аспектами синтаксиса дженериков, и мне интересно, принесет ли использование более сложного синтаксиса дополнительную пользу. Позвольте мне объяснить точку m: насколько я понимаю, преимущество предложения Тома состоит в том, чтобы заставить дерево принимать посетителей, которые работают с супертипами параметрического типа узлов дерева. - person ; 01.02.2010
comment
(продолжение) Но я не вижу случая, в котором эта функция была бы полезной. Я могу только представить очевидный случай, когда дерево принимает посетителей, которое работает с полным необработанным типом TreeNode. Это как раз тот случай, когда NodeCounter инициировал этот поток. Но в этом случае вам вообще не нужны дженерики (кстати, теперь он нормально компилируется в моей новой IDE). - person ; 01.02.2010
comment
(продолжение) Так что я думаю, что сейчас буду придерживаться более простой реализации. Но, по крайней мере, теперь я знаю, как изменить код, если дополнительная функциональность когда-нибудь пригодится. Спасибо всем за комментарии, это помогло мне глубже покопаться в этой сложной теме. - person ; 01.02.2010
comment
Вы правильно понимаете преимущества. Дело в том, что вам следует избегать сырых типов, если вы можете с этим справиться. Вместо того, чтобы NodeCounter работал с необработанным типом TreeNode, он должен работать с подстановочным знаком TreeNode ‹?›. Это предотвратит изменение TreeNode небезопасным для типа способом. - person ILMTitan; 12.02.2010

Короче говоря - да, вам нужно дать универсальному интерфейсу аргумент типа.

Что вам, вероятно, следует сделать, так это реализовать неуниверсальный (и, возможно, пустой) интерфейс ITreeNode, который ITreeNode<D> наследуется от. Вместо этого в этом интерфейсе объявляются все методы, которые не должны быть универсальными. Затем сделайте то же самое для IVisitor, и NodeCounter сможет унаследовать неуниверсальный Visitor интерфейс.

Краткая схема:

ITreeNode
ITreeNode<D> implements TreeNode

IVisitor
IVisitor<D> implements IVisitor

NodeCounter implements IVisitor

(Примечание: я использовал соглашение C # для добавления к интерфейсам префикса I. NodeCounter должен быть классом, а остальные - интерфейсами ...)

person Tomas Aschan    schedule 31.01.2010
comment
Это соглашение C # должно умереть в огне. - person Lorne Laliberte; 01.05.2015

Обобщения Java явно предназначены для взаимодействия с необработанными типами с использованием метода, известного как Стирание.

Таким образом, ситуация, которую вы описываете, напрямую поддерживается и должна нормально компилироваться:

class TreeNode<D>{
    public D data;  
}

interface Visitor<D> {
    void visit(TreeNode<D> node);
}

class NodeCounter implements Visitor {
    private int nodeCount = 0;
    public void visit(TreeNode node) {
        nodeCount++;
    }
    public int count() {
        return nodeCount;
    }
}
person Tendayi Mawushe    schedule 31.01.2010
comment
Спасибо, что посмотрели на это. Я обновляю свою реализацию Java, чтобы увидеть, как я могу избавиться от сообщенной ошибки, возможно, вызванной старой версией IDE. - person ; 31.01.2010