views:

216

answers:

4

I have a generic tree, the generic parameter is the data type stored by the nodes:

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

Then a visitor interface to use along with a tree transversal:

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

Some visitors can take advantage of generics:

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

But others don't, they would fit better in a raw class

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

But I don't know how implement this last case, the code above don't compile as I have to implement the generic interface not the raw one. I tried implementing

Visitor<?> 

with the same result. So my question is, I'm forced to use a generic type

NodeCounter<D> 

to implement the Visitor interface?.

Thanks.

A: 

In short - yes, you do need to give the generic interface a type argument.

What you probably should do, is implemenet a non-generic (and possibly empty) interface ITreeNode, that ITreeNode<D> inherits from. Any methods that don't need to be generic are declared in this intercace instead. Then, do the same thing for IVisitor, and NodeCounter can inherit the non-generic Visitor interface.

Short schematic:

ITreeNode
ITreeNode<D> implements TreeNode

IVisitor
IVisitor<D> implements IVisitor

NodeCounter implements IVisitor

(Note: I used the C# convention to prefix interfaces with I. NodeCounter is meant to be a class, while the others are interfaces...)

Tomas Lycken
+1  A: 

the code above don't compile

I tried compiling your example, and it works fine. I'm using Java 6. What was the compilation error you got?

This is what I successfully compiled:

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;
    }
}
Eli Acherkan
Oh, thaks for pointing that, I'm using a rahter old version of InelliJ that gave me this error. I will update everyting as I'm not very happy with doubling my classes
Pepin
I wouldn't be happy about it either :-)
Eli Acherkan
+1  A: 

Java generics are very powerful, and raw types should almost never be necessary.

You probably want to put some wildcards in. For instance, a visitor may need not know the exact generic argument of the TreeNodes it is visiting:

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

Perhaps better(?), a TreeNode may not need to know the exact type of visitor.

interface TreeNode<D> {
    void accept(TreeNodeVisitor<? super D> visitor);
}
Tom Hawtin - tackline
What is the practical difference between: void visit(TreeNode<? extends D> node); and just: void visit(TreeNode<D> node); I mean, in both cases the visit method would accpet the same types as argument. Or I'm wrong? Is there any practical advantage on using this more involved syntax?
Pepin
@Tom, can you give a working (i.e. a compiling) example of the classes in this example, generified as you suggest?
Avi
@Pepin, If you had a if `D` was `Object`, then using `? extends D` would allow, say, a `TreeNode<String>` as the argument.
Tom Hawtin - tackline
Do you mean having a TreeNode<String> instance and willing to visit it with a Visitor<Object> instance?. In this case I think there would be additional problems, as the method that makes the transversal is expecting a Visitor<String> as argumet.
Pepin
@Pepin Not if the method that makes the transversal is expecting a Vistor<? super String> as Tom Hawtin indicated.
ILMTitan
Hi ILMTitan, I tried modifying the transversals as well -in the line of the Tom's advice- and it seems it works OK. However I'm not sure if I should keep this version or going back to the simpler one. I'm not yet very used with some aspects of the generics sintax and I wonder if using the more complex sintax would provide some extra benefit. Let me explain m point: As far I can see the benefit of the Tom's suggestion would be to make the tree accept visitors that operate with supertypes of the parametric type of the tree nodes.
Pepin
(cont)But I can't see a case in which this feature would be ussefull. I only can imagine the obvious case of the tree accepting visitors that operates with the complete raw type TreeNode. This is precisely the case of the NodeCounter that originated this thread. But for this case you don't need generics at all (by the way, now it compiles OK in my new IDE).
Pepin
(cont) So I think I'll stick to the simpler implementation by the moment. But at least now I know how to change the code in case the extra functionallity will be handy some day. Thanks everybody for the comments, it helped me to dig a little more deeper in this complex subject.
Pepin
You understand the benefits correctly. The point is you should avoid raw types if you can help it. Instead of having NodeCounter operating with the raw type of TreeNode, it should operate with the wildcard type TreeNode<?>. Doing so will prevent you from mutating the TreeNode in a type unsafe way.
ILMTitan
+1  A: 

Java Generics are explicitly designed to be interoperable with raw types using a technique known as Erasure.

So the situation you are describing is directly supported and should compile fine:

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;
    }
}
Tendayi Mawushe
Thanks for looking on that. I'm updating my Java implemetation to see I can get rid of the reported error, maybe caused by and old IDE version.
Pepin