1

I want to use a domain specific tree DomainTree consisting of Domain specific Nodes DomainNode, but keep all generic functions in template classes Tree and Node. First I started with the templates Tree<T> and Node<T> (where T is the type of a nodes data). The DomainTree was then working with the Node<T> interface, which was not what I wanted. It should work on DomainNode objects instead.

To cope with that, I changed the generic tree's template parameter to Tree<N extends Node<?>> (the implementation below). Now I can work with the DomainNode by instantiating the tree as DomainTree<DomainNode>.

Still, I get a compilation error at (1) because getChildren() returns a list of Node<T>, which doesn't seem to be convertible to a list of N, though I made sure that N extends Node<?>.

Why is this not working and how can I design it, so that the DomainTree can work with DomainNodes?

Generic Tree

import java.util.ArrayList;
import java.util.List;

class Tree<N extends Node<?>> {

    public N rootElement;

    public List<N> toList() {
        List<N> list = new ArrayList<N>();
        walk(rootElement, list);
        return list;
    }  

    private void walk(N element, List<N> list) {
        list.add(element);
        List<N> children = element.getChildren(); // (1) Cannot convert from List<Node<T>> to List<T>
        for (N data : children) {
            walk(data, list);
        }
    }
}

class Node<T> {

    public T data;
    public List<Node<T>> children;

    public List<Node<T>> getChildren() {
        if (this.children == null) {
            return new ArrayList<Node<T>>();
        }
        return this.children;
    }

    public void addChild(Node<T> child) {
        if (children == null) {
            children = new ArrayList<Node<T>>();
        }
        children.add(child);
    }
}

Problemspecific Tree

class DomainTree extends Tree<DomainNode> {

    public void build() {
            for (DomainNode node : toList()) {
                // process...
            }
        }
}

class DomainNode extends Node<String> {

}
Jochen
  • 13
  • 4

2 Answers2

0

generics hold some surprises if you are not really into it. At first keep in mind that there is a type erasure and the compiler and the runtime thus see different things. Roughly spoken this also limits the compiler abilities in analyzing source code.

Note that there is really a difference between a List<Node<N>> and a List<N>. Hence even with N extends Node<?> the assignment 'List children = element.getChildren();' is inherently broken.

Furthermore with your declaration of Tree<N extends Node<?>>, you would expect that you can write something like List<Node<?>> l2 = element.getChildren();. Unfortunately this does not work due to some subleteties of generics. For example if you change your code to class Tree<N extends Node<N>> (this is probably not what you intended) you can write List<Node<N>> l2 = element.getChildren();.

I recommend to study the Sun Certified Java Programmer Study Guide for Java 6 (or a newer version) or something similar which is really helpful about generics.

From your code I got the impression that you mix different abstraction layers as there is the T data in the Node<T> class and in the for each loop the element is called N data. However in the loop you have a node N extends Node<?> which would be completely different from the T data. Hence the intention for your code remains a bit unclear for me. A working draft if your code as fixed version is here (Eclipse Luna, JDK 6)

package generics.tree;

import java.util.ArrayList;
import java.util.List;

class Tree<T> {

    public Node<T> rootElement;

    public List<Node<T>> toList() {
        List<Node<T>> list = new ArrayList<Node<T>>();
        walk(rootElement, list);
        return list;
    }  

    private void walk(Node<T> element, List<Node<T>> list) {
        list.add(element);
        List<Node<T>> children = element.getChildren(); // (1) Cannot convert from List<Node<T>> to List<T>
        for (Node<T> data : children) {
            walk(data, list);
        }
    }
}

class Node<T> {

    public T data;
    public List<Node<T>> children;

    public List<Node<T>> getChildren() {
       if (this.children == null) {
           return new ArrayList<Node<T>>();
       }
       return this.children;
    }

    public void addChild(Node<T> child) {
        if (children == null) {
           children = new ArrayList<Node<T>>();
        }
        children.add(child);
    }
}

class DomainTree extends Tree<String> {

    public void build() {
        for (Node<String> node : toList()) { // changed!
            // process...
        }
    }
}

class DomainNode extends Node<String> {
}
Sebastian
  • 395
  • 2
  • 7
  • thanks for your help. I will check out the guide you mentioned. Your working code is equivalent to my first version. The problem I find with that is, that `DomainTree` is working with `Node` but I want it to work with `DomainNode`. Any suggestions on that? – Jochen Feb 09 '15 at 13:00
  • For a more powerfuil solution see Bohemian's answer. The principle of self-referencing generic types seems strange at a first glance but is really common. For example check the Comparable interface and implementing classes such as java.lang.String or java.lang.Integer. – Sebastian Feb 10 '15 at 10:03
0

The problem with the code as it stands is that for a given Node<T>, the compiler has no way of knowing that the type of the List returned from toList() is the same Node<T> as the class itself.

What you need is a self-referencing generic type:

class Node<T, N extends Node<T, N>> {

    public T data;
    public List<N> children;

    public List<N> getChildren() {
        return children == null ? Collections.<N>emptyList() : children;
    }

    public void addChild(N child) {
        if (children == null) {
            children = new ArrayList<N>();
        }
        children.add(child);
    }
}

Now the type returned from toList() is the same type as the type itself.

Then DomainNode becomes:

class DomainNode extends Node<String, DomainNode> {
    //
}

And the signature of of Tree changes slightly to become:

class Tree<N extends Node<?, N>> {

And your usage example now compiles:

class DomainTree extends Tree<DomainNode> {
    public void build() {
        for (DomainNode node : toList()) {
            // process...
        }
    }
}

I added in a couple of other efficiencies too.

Bohemian
  • 412,405
  • 93
  • 575
  • 722