3
votes

I am having a problem with the Visitor pattern and generics. I have some abstract class whose children are to be visited. Look at this code:

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);
}

So the idea is: I have some class hierarchy (e.g. Element is a subclass of SomeSuperClass). I have got some generic Visitor interface to visit this hierarchy. Now in the middle of this hierarchy is the Element class, which is abstract and has it's own subclasses.

Now I want Element to accept all visitors of its subclasses, which is why I put this line:

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

But now I am receiving error:

The method visit (capture#1-of ? extends Element) in the type Visitor<capture#1-of ? extends Element> is not applicable for the arguments (Element).

I understand that ? extends Element is not Element. My question is: Can I express my idea in a different way? Or I have just missed the idea of generics in this case?

4
Is this a compiler error or runtime error?Roman C
@RomanC This is most definitely a compile-time error.Marko Topolnik

4 Answers

3
votes

That cannot work - the visitors of ? extends Element may need to be able to access data (attributes / methods, ...) that Element doesn't have or know about.

You can't get a visitor that's supposed to visit objects that extends Element to necessarily be able to visit something that's a straight Element or even another, completely separate, subclass of Element.

3
votes

I don't think that what you're trying to do makes much sense. Making Visitor generic is useless: the accept() method must take a specific visitor interface as argument, so that subclasses of Element may be able to call specific overloads of 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) {}
}

Note that the Visitor interface must be aware of all the classes in the Element hyerarchy that need a custom visit() implementation.

2
votes

Note that T in <T extends SomeSuperClass> can be a type completely unrelated to Element and the compiler must ensure for the general case that visit(T t) will work for every possible T.

The code you have calls Visitor.visit(Element e), but the visitor in question could be Visitor<SubElement>. That wouldn't make sense.

I think that the requirement "Element must accept all visitors of its subclasses" doesn't make sense: the visitor must at least be able to visit Element and all its subclasses. That would be a Visitor<Element>.

The construct accept(Visitor<? extends Element> v) means that v can be any such Visitor<T> that T extends Element. It does not mean that the visitor itself will be of the type Visitor<? extends Element>. In fact, no such thing even exists in Java. Every Visitor will have a specific type parameter associated with it, not a wildcard.

2
votes

The most general way to write it would be:

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

That way, even a Visitor<Object> would work (why shouldn't it).

Remember PECS (producer extends, consumer super). The visitor is a consumer, so it should be super.