Дженерики и шаблон посетителя

У меня проблема с шаблоном посетителя и дженериками. У меня есть некоторый абстрактный класс, чьи дети должны быть посещены. Посмотрите на этот код:

public abstract class Element extends SomeSuperClass {
    public void accept(Visitor<? extends Element> v) {
        v.visit(this);
    }
}

public interface Visitor<T extends SomeSuperClass> {
    void visit(T element);
}

Итак, идея такова: у меня есть некоторая иерархия классов (например, Element является подклассом SomeSuperClass). У меня есть общий Visitor интерфейс для посещения этой иерархии. Теперь в середине этой иерархии находится класс Element, который является абстрактным и имеет свои собственные подклассы.

Теперь я хочу, чтобы Element принимал всех посетителей своих подклассов, поэтому я поставил эту строку:

public void accept(Visitor<? extends Element> v)

Но теперь я получаю сообщение об ошибке:

Метод visit (capture#1-of ? extends Element) в типе Visitor<capture#1-of ? extends Element> неприменим для аргументов (Element).

Я понимаю, что ? extends Element не Element. У меня вопрос: могу ли я выразить свою мысль по-другому? Или я просто пропустил идею дженериков в этом случае?


person Michal    schedule 24.08.2012    source источник
comment
Это ошибка компилятора или ошибка времени выполнения?   -  person Roman C    schedule 24.08.2012
comment
@RomanC Это определенно ошибка времени компиляции.   -  person Marko Topolnik    schedule 24.08.2012


Ответы (4)


Обратите внимание, что T в <T extends SomeSuperClass> может быть типом, совершенно не связанным с Element, и компилятор должен гарантировать, что в общем случае visit(T t) будет работать для всех возможных T.

Код, который у вас есть, вызывает Visitor.visit(Element e), но посетитель, о котором идет речь, может быть Visitor<SubElement>. Это не имеет смысла.

Я думаю, что требование "Element должно принимать всех посетителей своих подклассов" не имеет смысла: посетитель должен, по крайней мере, иметь возможность посетить Element и все его подклассы. Это будет Visitor<Element>.

Конструкция accept(Visitor<? extends Element> v) означает, что v может быть любым таким Visitor<T>, что и T extends Element. Это не означает, что сам посетитель будет иметь тип Visitor<? extends Element>. На самом деле такого даже в Java не существует. С каждым посетителем будет связан параметр определенного типа, а не подстановочный знак.

person Marko Topolnik    schedule 24.08.2012
comment
Но это s why I wrote method public void accept(Visitor‹? extends Element› v)`, чтобы убедиться, что посетитель не может быть Visitor<SubElement>. Во всяком случае, я понял, что хочу быть слишком гибким. Спасибо. - person Michal; 24.08.2012
comment
Тогда я думаю, что вы неправильно понимаете семантику. accept(Visitor<? extends ELement>) — это именно та конструкция, которая позволяет Visitor<SubElement>. Он допускает любые Visitor<T> такие, что T extends Element. - person Marko Topolnik; 24.08.2012
comment
Да, теперь я понял, наконец. - person Michal; 24.08.2012
comment
Привет @MarkoTopolnik, если у вас есть несколько минут, не могли бы вы взглянуть на мой связанный вопрос stackoverflow.com/questions/31869706/. Спасибо. - person Dmitry Minkovsky; 10.08.2015

Это не сработает - посетителям ? extends Element может потребоваться доступ к данным (атрибутам/методам,...), которых Element нет или о которых он не знает.

Вы не можете сделать так, чтобы посетитель, который должен посетить объекты, расширяющие Element, обязательно мог посетить что-то, что является прямым Element или даже другим, совершенно отдельным подклассом Element.

person Romain    schedule 24.08.2012

Я не думаю, что то, что вы пытаетесь сделать, имеет большой смысл. Делать Visitor универсальным бесполезно: метод accept() должен принимать в качестве аргумента конкретный интерфейс посетителя, чтобы подклассы Element могли вызывать определенные перегрузки visit().

interface Visitor {
  void visit(Element e);
  void visit(SubElement e);
}

class Element {
  public void accept(Visitor v) {
    v.visit(this);
  }
}

class SubElement { 
  public void accept(Visitor v) {
    v.visit(this);
  }
}

class ElementVisitor implements Visitor {
  public void visit(Element e) {}
  public void visit(SubElement e) {}
}

Обратите внимание, что интерфейс Visitor должен знать обо всех классах в иерархии Element, которые нуждаются в пользовательской реализации visit().

person Nicola Musatti    schedule 24.08.2012

Наиболее общий способ записи:

public void accept(Visitor<? super Element> v) {
    v.visit(this);
}

Таким образом, даже Visitor<Object> будет работать (почему бы и нет).

Вспомните PECS (производитель extends, потребитель super). Посетитель является потребителем, поэтому он должен быть super.

person newacct    schedule 24.08.2012