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 DomainNode
s?
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> {
}
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.