Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics self-reference: is it safe?

I have this simple interface:

public interface Node<E extends Node<E>>
{
    public E getParent();

    public List<E> getChildren();

    default List<E> listNodes()
    {
        List<E> result = new ArrayList<>();

        // ------> is this always safe? <-----
        @SuppressWarnings("unchecked")
        E root = (E) this;

        Queue<E> queue = new ArrayDeque<>();
        queue.add(root);

        while(!queue.isEmpty())
        {
            E node = queue.remove();

            result.add(node);

            queue.addAll(node.getChildren());
        }

        return result;
    }
}

I see that this is always an instance of Node<E> (by definition).
But I can't imagine a case where this is not an instance of E...
Since E extends Node<E>, shouldn't Node<E> also be equivalent to E by definition??

Can you give an example of an object that's an instance of Node<E>, but it's not an instance of E??

Meanwhile, my brain is melting...


The previous class was a simplified example.
To show why I need a self-bound, I'm adding a bit of complexity:

public interface Node<E extends Node<E, R>, R extends NodeRelation<E>>
{
    public List<R> getParents();

    public List<R> getChildren();

    default List<E> listDescendants()
    {
        List<E> result = new ArrayList<>();

        @SuppressWarnings("unchecked")
        E root = (E) this;

        Queue<E> queue = new ArrayDeque<>();
        queue.add(root);

        while(!queue.isEmpty())
        {
            E node = queue.remove();

            result.add(node);

            node.getChildren()
                .stream()
                .map(NodeRelation::getChild)
                .forEach(queue::add);
        }

        return result;
    }
}

public interface NodeRelation<E>
{
    public E getParent();

    public E getChild();
}
like image 820
Michele Mariotti Avatar asked Nov 20 '18 19:11

Michele Mariotti


2 Answers

An easy example to illustrate the problem: a node of a different type of node:

class NodeA implements Node<NodeA> {
    ...
}

And:

class NodeB implements Node<NodeA> {
    ...
}

In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.

like image 67
ernest_k Avatar answered Sep 26 '22 03:09

ernest_k


Without <E extends Node<E>>, you could have either of these cases:

Node<Integer>

where the generic type isn't a Node at all, or

Node<DifferentNode>

where the generic bounds don't match.

That said, it's not typical to see a bound this way, as Node<E> is expected to be a node that contains some value of type E, and children would be a List<Node<E>>, not a List<E>.

like image 37
chrylis -cautiouslyoptimistic- Avatar answered Sep 26 '22 03:09

chrylis -cautiouslyoptimistic-