0

(TL;DR: Skip to the bold.)

I am trying to build a computer simulation of a modified Hydra -- in this version, each Hydra head can have more Hydra heads coming out of it. I figured this was very node-like, and so I first built a general-purpose Node class. Each Node object has an ArrayList<Node> of children, each of which (being a Node also) can have its own children.

While the structure of the Hydra's heads is the same as Node, the behavior is different. (Eg. A Node should be able to simply remove its child, whereas removing a head from a Hydra needs to also regrow some heads.) So I built HydraNode extends Node and added methods like cutHead() (which removes a head (node) but then adds clones). A Hydra is the "body" and has HydraNodes for heads.

The problem is, because all of the child nodes are stored as an ArrayList<Node>, I can do

Hydra hydra = new Hydra(1,1,1) // Makes a Hydra with 3 levels (1 head, which has 1 head, which has 1 head).
Node nodeAtLevel1 = hydra.getChildren.get(0); // Has to be declared as Node, not HydraNode, because getChildren() is a Node method, and returns an ArrayList<Node> of children.

, but each of its children are actually nodes. This leads to the problem in main(), where I try running nodeAtLevel1.cutHead() but can't, because cutHead() is a HydraNode method.

In the case where an object contains itself, how can I add functionality to the class? Extending the object into a subclass doesn't seem to work, because retrieving the contained object will return an object of type superclass, not subclass. And I can't cast it downward. What can I do?

Node.java

public class Node {
    // Member variables
    private ArrayList<Node> children = new ArrayList<>(); // Holds "children" nodes.
    private int hierarchyLevel; // Where in the hierarchy is it?
    private int childCount = 0; //How many "child" nodes does this node have?

    // Constructors
    public Node(int hierarchyLevel) {this.hierarchyLevel = hierarchyLevel}
    public Node(int hierarchyLevel, int... nodesPerLevel) {this(hierarchyLevel;} //Adds children to this node, eg. {1,2,1} adds 1 child node at lvl 1, 2 children at lvl 2, each with 1 child of their own at level 3.

    // Methods
    public ArrayList<Node> getChildren() {return children;}
    public void addChild() {} // Adds a child directly to this node
    public void removeChild(int i) {}

    public Node getCopy() {} //Returns a clone of this Node and all its child nodes.

    public String toString() {} // Eg. Node at Level ___ has ____ children.

}

HydraNode.java (the heads)

public class HydraNode extends Node {
    // Constructors
    public HydraNode(int hierarchyLevel) { // Just call super, bc this is essentially a Node structure that just acts a little differently.
        super(hierarchyLevel);
    }
    public HydraNode(int hierarchyLevel, int... nodesPerLevel) {
        super(hierarchyLevel, nodesPerLevel);

    // Methods
    public void cutHead() {} // Cutting a Level 1 head, which is attached to the body (level 0), does not regrow. However, any other cut will multiply that branch's parent x3.
}

Hydra.java

public class Hydra {
    // MAIN method
    public static void main(String[] args) {
        Hydra hydra = new Hydra(1,1,1);
        Node head2 = hydra.body.getChildren().get(0).getChildren().get(0);
        System.out.println(head2.toString()); // >> Node at Level 2 has 1 child node.
        //head2.cutHead(); // Doesn't work because cutHead() is a HydraNode method, not a Node method.
    }

    // Member Variables
    public static int regrowFactor = 2; // Every time a head is cut off, the hydra clones the remaining branches. In the original video, the hydra forms two new clones.
    HydraNode body;

    // Constructors
    public Hydra() {
        body = new HydraNode(0); // the body is just a new Node at level 0
    }
    public Hydra(int... headsPerLevel) {
        body = new HydraNode(0, headsPerLevel);
    }
}
Alex G
  • 747
  • 4
  • 15
  • 27
  • I don't understand. Store what as the subclass? And I don't think casting upward fixes it. If I make everything into a `Node`, then it won't have access to `cutHead()`. Honestly, the best thing would be to literally import `cutHead()` as a method into `Node`; would make it super easy, but I don't want to ruin `Node` by specializing it with functions specific to a Hydra -- I thought that was the whole point of OOP is to make general-purpose classes that can be extended to more specific uses. – Alex G Oct 17 '17 at 21:52
  • Maybe I don't understand; you're saying you can't `if(node instanceOf HydraNode)` then cast it to a `HydraNode` in order to call `cutHead()`? – dillius Oct 17 '17 at 21:55
  • @AlexG It looks like you're over-abstracting and making your code messier than it needs to be. Make a class that does exactly what you need. Nothing more, nothing less. – 4castle Oct 17 '17 at 21:59
  • No, because HydraNode is an instance of `Node`. `Node` is the generalized class I made for, well, node-like things (eg. Maybe a series of Rooms, where one Room leads to the next leads to the next). In this case, Hydra heads are very node-like; however, they have different behavior (you can remove a `Node`, but try to remove a Hydra's head, and it'll grow two more). So I made `HydraNode` which `extends Node`. Because `HydraNode` has the specific behaviors, retrieving the child `Nodes` doesn't give me access to those functions specific to a `HydraNode`. – Alex G Oct 17 '17 at 22:00
  • If the object itself was initialized/constructed as a `HydraNode`, then it has all the methods of a `HydraNode`, you just can't access them because the exposed interface is that of `Node`. That is exactly why java has the ability to check if a super type is a specific subclass and to cast. – dillius Oct 17 '17 at 22:05

4 Answers4

3

The cast suggestions we have all made are failing because:

public HydraNode(int hierarchyLevel, int... nodesPerLevel) {
    super(hierarchyLevel, nodesPerLevel);

When you are constructing HydraNodes from your Hydra, you are calling the super() constructor to chain it. This means you end up with:

HydraNode -> Node -> Node

You should be calling:

    this(hierarchyLevel, nodesPerLevel);

So that the chain of creation always results in more HydraNodes.

HydraNode -> HydraNode -> HydraNode

Then, you'll be able to cast from Node -> HydraNode and call cutHeadas specified in many responses.

dillius
  • 506
  • 4
  • 12
  • Okay, but then in that constructor, I need to copy-paste the exact same logic as in the `super()` constructor, only changing all places where it says `Node` to `HydraNode`. This seems...wasteful? And I don't think I can just change it to `this( /* arguments */)`, because I don't have any constructor in `HydraNode` that doesn't call `super()`. – Alex G Oct 17 '17 at 22:29
  • Your HydraNode that takes one parameter can call `super(hierarchyLevel)`, but you shouldn't call the 2 parameter Node constructor because it creates Children that are Nodes, not HydraNodes, which are what you want. – dillius Oct 17 '17 at 22:41
  • Some suggestions were already made regarding generics; that can save you from duplicate code if you make a generic AddChild method or the like; but the situation you have here is already a bit over-designed for your use case, which is doing more harm than good. – dillius Oct 17 '17 at 22:42
2

You can cast a super class to it sub class, this is called downcasting.

So in:

Node nodeAtLevel1 = hydra.getChildren.get(0);

You can cast it like:

HydraNode nodeAtLevel1 = (HydraNode) hydra.getChildren.get(0);

You also can include a check:

if (hydra.getChildren.get(0) instanceof HydraNode ) {
   HydraNode nodeAtLevel1 = (HydraNode) hydra.getChildren.get(0);
}

Downcasting is not always allowed. You can see more info here.

  • This didn't work. I get an error: `Exception in thread "main" java.lang.ClassCastException: infiniteseries.hydra.Node cannot be cast to infiniteseries.hydra.HydraNode at infiniteseries.hydra.Hydra.main(Hydra.java:15)`. I think it's because `hydra.getChildren.get(0)` is NOT an instance of `HydraNode`. `.get(0)` returns a `Node` object, and `Node` is the superclass of `HydraNode` ? – Alex G Oct 17 '17 at 22:06
  • Note: I could be misunderstanding `instanceof`, but from what I know, `a instanceof b` checks if `a` is an object that `extends b`, right? So in this case, `HydraNode` extends from `Node`, so I think that would be false? – Alex G Oct 17 '17 at 22:08
  • 1
    You can use instanceof to check if "an object is instance of a class, an instance of a subclass, or an instance of a class that implements a particular interface." [From here](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html) – Farid Saud Rolleri Oct 17 '17 at 22:19
  • Ok. I tried it and got `false` but it seems to be for the reason @dillius specified. – Alex G Oct 17 '17 at 22:32
2

Why not use generics for child nodes like so:

public class Node<T extends Node<T>> {
        // Member variables
        private ArrayList<T> children = new ArrayList<>(); // Holds "children" nodes.
        private int hierarchyLevel; // Where in the hierarchy is it?
        private int childCount = 0; //How many "child" nodes does this node have?

        // Constructors
        public Node(int hierarchyLevel) {}
        public Node(int hierarchyLevel, int... nodesPerLevel) {} //Adds children to this node, eg. {1,2,1} adds 1 child node at lvl 1, 2 children at lvl 2, each with 1 child of their own at level 3.

        // Methods
        public ArrayList<T> getChildren() {return children;}
        public void addChild() {} // Adds a child directly to this node
        public void removeChild(int i) {}

        public T getCopy() {return null;} //Returns a clone of this Node and all its child nodes.

        public String toString() {return null;} // Eg. Node at Level ___ has ____ children.

    }

    public class HydraNode extends Node<HydraNode> {
        // Constructors
        public HydraNode(int hierarchyLevel) { // Just call super, bc this is essentially a Node structure that just acts a little differently.
            super(hierarchyLevel);
        }
        public HydraNode(int hierarchyLevel, int... nodesPerLevel) {
            super(hierarchyLevel, nodesPerLevel);
        }

        // Methods
        public void cutHead() {} // Cutting a Level 1 head, which is attached to the body (level 0), does not regrow. However, any other cut will multiply that branch's parent x3.
    }

        public class Hydra {


            // MAIN method
            public static void main(String[] args) {
                Hydra hydra = new Hydra(1,1,1);
                HydraNode head2 = hydra.body.getChildren().get(0).getChildren().get(0);
                System.out.println(head2.toString()); // >> Node at Level 2 has 1 child node.
                head2.cutHead(); //works now
            }

            // Member Variables
            public static int regrowFactor = 2; // Every time a head is cut off, the hydra clones the remaining branches. In the original video, the hydra forms two new clones.
            HydraNode body;

            // Constructors
            public Hydra() {
                body = new HydraNode(0); // the body is just a new Node at level 0
            }
            public Hydra(int... headsPerLevel) {
                body = new HydraNode(0, headsPerLevel);
            }
        }        
tsolakp
  • 5,858
  • 1
  • 22
  • 28
  • I really like this. It'll take some time to implement, but I think this is the solution, seeing as Node was created as a general-purpose object for other things to inherit from. – Alex G Oct 17 '17 at 22:13
  • Why are all the classes static, though? – Alex G Oct 17 '17 at 22:14
  • Woah...I just realized what you wrote. You can do `Node>`? How does that not infinitely recurse? (I believe you, just shocked. I'll be trying this soon.) – Alex G Oct 17 '17 at 22:31
  • 1
    That's called self bound generics. – tsolakp Oct 17 '17 at 23:03
  • I wish I could give multiple "solved" but I can't. Your solution is the one I want to employ, but I tried it earlier and it kept giving me trouble. I'm going to keep working on it, but for the sake of closing this thread, I chose to mark @dillius seeing as he gave the explanation of why downcasting was failing, and while it's a quick and easy fix, it technically closes my case. It doesn't really fix the long-term, seeing as I'd like to make this a generic ultimately, though, so I'll keep working on this separately. I'll post an update if I get it working. Thank you. – Alex G Oct 18 '17 at 21:32
  • Btw using the term "self bound generics", I was able to find a guide explaining it; their explanation involves a generic Node class also! http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 – Alex G Oct 18 '17 at 21:33
1

Why do you use this inheritance at all?

Use a single HydraNode class with all the functions of Node copy-pasted into it and problem is solved.

Neo
  • 3,534
  • 2
  • 20
  • 32
  • Yeah, the compiler freaks out. `Exception in thread "main" java.lang.ClassCastException: infiniteseries.hydra.Node cannot be cast to infiniteseries.hydra.HydraNode at infiniteseries.hydra.Hydra.main(Hydra.java:15)` – Alex G Oct 17 '17 at 22:04
  • Right. I changed my answer to not include it. – Neo Oct 17 '17 at 22:09
  • @AlexG Do you instantiate `Node` anywhere? – Neo Oct 17 '17 at 22:10
  • I mean, a Hydra head is-a Node, right? I figure it has all the structure and functions of a Node, but with some added stuff. That seems like perfect inheritance right there, no? I could copy-paste and add it in, but that seems like more of a hack seeing as this is (from what I've been taught in class) a perfect case of inheritance. – Alex G Oct 17 '17 at 22:11
  • Yes, I have every Node containing its own Node. And the body of the Hydra has a Node on it (albeit a HydraNode) as the head. – Alex G Oct 17 '17 at 22:13
  • @AlexG But that's unneeded complication. You never use `Node` as it's own, it's just a base class for another class, and the inheritance isn't used to do polymorphism so why bother? – Neo Oct 17 '17 at 22:15
  • @AlexG I meant if you use the `Node` constructor anywhere outside of the `HydraNode` constructor? – Neo Oct 17 '17 at 22:16
  • The reason is mainly because I have a lot of projects (esp recently) where structures act like Nodes. It would be much more useful to create a Node class once, then use it in all my projects. Eg. A series of `Room` objects that are connected as in a map. Without a Node class (general purpose), I have to recreate this functionality each time. – Alex G Oct 17 '17 at 22:18
  • That's a reason. – Neo Oct 17 '17 at 22:20