Jessica
Jessica

Reputation: 771

Java question on runtime type checking in implemented method

I am designing some java objects to represent graphs and trees. For my use case I will be using both data types but I also want my graph algorithms to work on my trees.

import java.util.List;

public interface Node<T> {
    T getValue();
    List<? extends Node<T>> getNeighbors();
    void addNodes(List<? extends Node<T>> nodes);
}

public interface TreeNode<T> extends Node<T> {
    List<? extends TreeNode<T>> getChildren();
    void addChildren(List<? extends TreeNode<T>> treeNodes);

    @Override
    default List<? extends Node<T>> getNeighbors() {
        return getChildren();
    }

    @Override
    default void addNodes(List<? extends Node<T>> nodes) {
        if(nodes.getClass().isInstance(getChildren().getClass())) {
            addChildren((List<? extends TreeNode<T>>) nodes);
        } else {
            throw new RuntimeException("Type error!");
        }
    }
}

My question is about how I'm dealing with addNodes method in the Node interface in the TreeNode interface. The addNodes method has to be in the Node interface because I want to allow people to write code that can add nodes to graphs. However, I also don't want people to add arbitrary nodes to a tree node(for example adding a graph node to a tree node).

In order to prevent this, I'm checking the type of nodes at runtime and throwing an exception if the type is not right. I'm just wondering if this is the best way to accomplish what I want or if there is a better practice?

Thanks for helping :)

Upvotes: 0

Views: 335

Answers (2)

Sharon Ben Asher
Sharon Ben Asher

Reputation: 14328

The way I see it, Node is a container for some data. Tree and Graph are two ways to maintain relationships between Nodes. So perhaps three classes should be defined:

import java.util.List;

public class Node<T> {
    private T value;
    public Node(T value) { this.value = value; }
    T getValue() { return value; }
}

public abstract class Tree<T> {
    private Node<T> root;

    public abstract List<? extends Node<T>> getChildren();
    public abstract void addChildren(List<? extends Node<T>> nodes);

    public Tree(Node<T> root) { this.root = root; }
}

public abstract class Graph<T> {
    private Node<T> root;

    public abstract List<? extends Node<T>> getNeighbors();
    public abstract void addNeighbors(List<? extends Node<T>> nodes);

    public Graph(Node<T> root) { this.root = root; }
}

EDIT: If you want to have shared traversal algorithms, you can put them in separate classes and have the Tree and Graph use similar semantics like this:

// common semantics for node containers
public interface NodeContainer<T> {
    List<? extends Node<T>> getRelatedNodes();
}

public abstract class Tree<T> implements NodeContainer<T> {

    ...  // same as above

    @Override
    public List<? extends Node<T>> getRelatedNodes() {
        return getChildren();
    }
}

public class NodeContainerTraversal {

    public void bfs (NodeContainer<?> container) {
        ...
    }
}

Upvotes: 0

Daniel Pryden
Daniel Pryden

Reputation: 60957

If I understand correctly, what you want is (a variation on) the so-called curiously recurring template pattern. The Node type needs to be parameterized not only by its payload type (T) but also by the type of nodes it can be used with. So you want something like:

public interface Node<T, N extends Node<T, N>> {
    T getValue();
    List<N> getNeighbors();
    void addNodes(List<N> nodes);
}

public interface TreeNode<T> extends Node<T, TreeNode<T>> {
    List<TreeNode<T>> getChildren();
    void addChildren(List<TreeNode<T>> treeNodes);

    @Override
    default List<TreeNode<T>> getNeighbors() {
        return getChildren();
    }

    @Override
    default void addNodes(List<TreeNode<T>> nodes) {
        addChildren(nodes);
    }
}

Demo (shows compilation only): https://ideone.com/44qrmX

Upvotes: 2

Related Questions