I am trying to make a container(parent) object where you can specify type of objects(children) it can contain.
Similarly, you can specify type of parent of the child.
Parent and child needs to communicate in the future, so I need them both to have object references to each other.
This code is a simpler representation of the actual code in my app.
class Parent<T extends Child> {
ArrayList<T> childObjects;
public void addChildChild(T newChild) {
childObjects.add(newChild);
newChild.setParent(this);
}
public void newChildConnected(T connectedChild) {
System.out.println("Child connected");
}
}
class Child <T extends Parent> {
T parentObject;
public void setParent(T newParent) {
parentObject = newParent;
parentObject.newChildConnected(this);
}
}
My IDE says:
Unchecked call to 'newChildConnected(T)' as a member of raw type 'test.Parent'
I have been trying different ways to make it better with wildcards and stuff, but I this is best I can do.
So what is the correct way of implementing such a behavior?
My goal is to be able to specify the child type for parent and parent type for child and do it in the way that both children and parent are able to use functionality of each other without using the intanceof() operator and casting. (that's why I use generics after all)
Is is even possible in Java?
Your usage of generic types creates a circular type reference. If the type(or interface/base class) of the parent and child are the same, use a tree structure within a single class:
class MyObject<T> {
T parentObject;
ArrayList<T> childObjects = new ArrayList();
public void addChildChild(T newChild) {
childObjects.add(newChild);
newChild.setParent(this);
}
public void newChildConnected(T connectedChild) {
System.out.println("Child connected");
}
public void setParent(T newParent) {
parentObject = newParent;
parentObject.newChildConnected(this);
}
When using this class you must check for top level objects where parentObject == null and leaf objects where childObjects.size()==0.
If there is no common interface or base class, this is not possible to do safely.
As #Strom pointed out correctly, this cannot be done in a typesafe way without base classes or interfaces.
If you can extend a class/interface, a typesafe solution without any casts would look like this:
interface BaseParent<P extends BaseParent<P, C>, C extends BaseChild<P, C>> {
List<C> getChildren();
void setChildren(List<C> children);
P self();
default void addChild(C child) {
if (child.getParent() == null) {
child.setParent(self());
}
final ArrayList<C> newChildren = new ArrayList<>(getChildren());
newChildren.add(child);
setChildren(newChildren);
}
}
interface BaseChild<P extends BaseParent<P, C>, C extends BaseChild<P, C>> {
void setParent(P parent);
P getParent();
}
final class Parent implements BaseParent<Parent, Child> {
private List<Child> children = new ArrayList<>();
#Override
public List<Child> getChildren() {
return children;
}
#Override
public void setChildren(List<Child> children) {
this.children = children;
}
#Override
public Parent self() {
return this;
}
}
final class Child implements BaseChild<Parent, Child> {
private Parent parent;
public Child(Parent parent) {
this.parent = parent;
this.parent.addChild(this);
}
#Override
public void setParent(Parent parent) {
this.parent = parent;
}
#Override
public Parent getParent() {
return parent;
}
}
The solution uses "recursive" generics for type safety and a self-type reference to avoid casts. Both of these approaches have caveats and are not entirely safe because you have to rely on the implementor of the base/class interface to return the correct self type and define correct type parameters, but should be good enough for internal APIs.
Related
I have the following java code:
public class TreeNode<T> {
public TreeNode<T> getParent() {
return null;
}
public void setParent(TreeNode<T> parent) {
}
public List<TreeNode<T>> getChildren() {
return null;
}
public void setChildren(List<TreeNode<T>> children) {
}
public T getData() {
return null;
}
public void setData(T data) {
}
}
Now, I want to create a class that extends the one above, like the following:
public class BinaryTreeNode<T> extends TreeNode<T> {
public BinaryTreeNode<T> getLeftChild() {
return null;
}
public void setLeftChild() {
}
public BinaryTreeNode<T> getRightChild() {
return null;
}
public void setRightChild() {
}
#Override
public void setChildren(List<BinaryTreeNode<T>> children) {
}
}
However, the last method won't compile because the parameter children is not of type List<TreeNode<T>>. I understand why this happens (because List<BinaryTreeNode<T>> is not considered a subtype of List<TreeNode<T>>), but what is the best way to fix something like this?
I know that I can just define the children parameter to be of type List<TreeNode<T>>, but if possible I would like to enforce it to be of type List<BinaryTreeNode<T>>.
The issue is not coming from the lists not being subclasses, it's because you can't override a method and upcast its parameter.
if you have the following classes:
public class A
{
public void f(A a) {}
}
public class B extends A
{
public void problem(){}
public void f(B a)
{
a.problem();
}
}
Since B extends from A, you could have the following code running:
A a1 = new B();
A a2 = new A();
a1.f(a2);
when that code will run, it will try to execute the function "problem" on an A instance that doesn't have it.
That's why in general you can't override the parameters with classes that inherit the original type.
Now, you can leave the parameter as List> children, but then you might get nodes that aren't binary, which I assume you don't want.
If you're sure that's the way you want the inheritance to be, you can check the type of "children" at the beginning of the method and throw an exception if it doesn't fit.
To sum up what shmosel proposed:
public class TreeNode<T, N extends TreeNode<T, N>> {
public N getParent() { return null; }
public void setParent(N parent) {}
public List<N> getChildren() { return null; }
public void setChildren(List<N> children) {}
public T getData() { return null; }
public void setData(T data) {}
}
The downside is that your Nodes now have to by typed with a "redundant" generic type. That is because you try to counter the fact that sub classes can act as object of super classes.
Your Binary Node class would look like this:
class BinaryTreeNode<T> extends TreeNode<T, BinaryTreeNode<T>>
As an alternative and for further reading, try this article:
https://www.sitepoint.com/self-types-with-javas-generics/
Let me start by abstractly formulating the problem: I have two public interface types. One of them contains a method which receives at least two instances of the other interface type. The implementation of the method depends on the implementation of the passed objects.
Consider the following public API, which consists of two interfaces:
public interface Node {
}
public interface Tree {
void connect(Node parent, Node child);
}
Now, I want to implement that API, like so:
public class NodeImpl implements Node {
private final Wrapped wrapped;
public NodeImpl(Wrapped wrapped) {
this.wrapped = wrapped;
}
public Wrapped getWrapped() {
return wrapped;
}
}
public class TreeImpl implements Tree {
#Override
public void connect(Node parent, Node child) {
// connect parent and child using the wrapped object
}
}
public class Wrapped {
// wrapped object which actually represents the node internally
}
I need to access the wrapped objects in the connect method, which is impossible, because the getWrapped method is not part of the API. It is an implementation detail.
So the question is: How can I implement the connect method without leaking implementation detail to the API?
Here is what I tried so far:
Put the connect method in the Node interface and call parent.connect(child). This gives me access to the wrapped object of the parent, however the wrapped object of the child is still not available.
Just assume the passed Node is of type NodeImpl and use a downcast. This seems wrong to me. There might be other Node implementations.
Don't put the wrapped object in the node, but use a map in the TreeImpl that maps Node's to Wrapped objects. This is basically the same as above. It breaks down as soon as a Node instance is passed to the connect method, which has no associated mapping.
Please note, that the Node interface might contain methods. However, this is unimportant for this question.
Also, please note that I am in control of both: The interface declaration as well as the implementation.
Another attempt to solve this is to convert the connect method to a addChild method in the Node interface and to make the Node interface generic:
public interface Node<T extends Node<T>> {
void addChild(Node<T> child);
}
public class NodeImpl implements Node<NodeImpl> {
private final Wrapped wrapped;
public NodeImpl(Wrapped wrapped) {
this.wrapped = wrapped;
}
public Wrapped getWrapped() {
return wrapped;
}
#Override
public void addChild(Node<NodeImpl> child) {
}
}
public class Wrapped {
// wrapped object which actually represents the node internally
}
public Node<NodeImpl> createNode() {
return new NodeImpl(new Wrapped());
}
private void run() {
Node<NodeImpl> parent = createNode();
Node<NodeImpl> child = createNode();
parent.addChild(child);
}
Node and createNode are part of the public API. NodeImpl and Wrapped should be hidden. run is the client code. As you can see, NodeImpl has to be visible to the client, so this is still a leaking abstraction.
if connect method needs to access the Wrapped object in each node, that means NodeImpl can be only connected to a NodeImpl, so no need to make it complex, add a method addChild or connect to Node interface, in NodeImpl implementation you can down cast the argument to NodeImpl, if there is a type mismatch you may throw a exception.
without down-casting you can use generics like, but i think the simple solution is to down cast the
interface NodeConnector<T extends Node>
{
void connect(T parent,T child);
}
public abstract class AbstractNode implements Node
{
#Override
public void connect(Node node)
{
NodeConnector<Node> nodeConnector = getNodeConnector();
nodeConnector.connect(this, node);
Node parent = this;
}
protected abstract NodeConnector<Node> getNodeConnector();
}
class NodeImpl extends AbstractNode
{
#SuppressWarnings("unchecked")
protected NodeConnector<Node> getNodeConnector()
{
return (NodeConnector) new NodeConnectorImpl();
}
}
class NodeConnectorImpl implements NodeConnector<NodeImpl>
{
#Override
public void connect(NodeImpl parent, NodeImpl child)
{
}
}
I want to implement an abstract Hierarchy class. Any Hierarchy object should have a parent (could be null) and a set of children, both of the exact same type as the concrete implementation of Hierarchy. Can this be done with generics, and if not is there any mechanism to enforce this relationship?
Here's what I was thinking so far, but there are issues:
public abstract class Hierarchy<T extends Hierarchy<T>> {
private T parent;
private Set<T> children;
public T getRoot() {
if( parent == null ) {
return this;
} else {
return parent.getRoot();
}
}
}
The problem is with return this. It gives a compile error because this is of type Hierarchy, not T. I can't cast it, because I don't actually guarantee that this is of type T. The following declaration would compile just fine:
public class B extends Hierarchy<B> {...}
public class A extends Hierarchy<B> {...}
So is there any way I can disallow A extends Hierarchy<B> declarations?
So is there any way I can disallow A extends Hierarchy
declarations?
No, it is not possible.
You can do what you want with something like this:
public abstract class Hierarchy<T> {
private T parent;
private Set<T> children;
public static <E extends Hierarchy<E>> E getRoot(E x) {
while (x.parent != null) {
x = x.parent;
}
return x;
}
}
I'm writing a generic class:
public class Node<T> {
private Node<T> parent = null;
private List<? extends Node<T>> children = null;
public Node<T> getParent() {
return parent;
}
public void setParent(Node<T> parent) {
if(this.parent != null){
// Remove current parent's children references
this.parent.getChildren().remove(this);
}
// Add references
this.parent = parent;
parent.getChildren().add(this);
}
public List<? extends Node<T>> getChildren() {
return children;
}
}
I want some other class which subclass this Node. This code cannot be compiled with the error on line parent.getChildren().add(this);. Because I declared getChildren() with List<? extends Node<T>> as return type, and 'this' is type Node<T>.
Is there anyway to solve this?
Declare the list as:
List<Node<T>> children
You may still put instances of subclasses in the list.
If you leave it as an unknown type, the compiler can't ensure which class it is typed as. Eg it might be typed as SubClassA, but you're adding SubClassB and it has no way to know based on the declared type, which is all the compiler has to go on. At runtime, while the type of list and child might match, the compiler can't assert.
Consider this code:
public class TreeNode<T extends TreeNode<T, E>, E> {
protected T parent;
protected E data;
protected List<T> children = new ArrayList<T>();
public TreeNode(T parent, E data) {
this.parent = parent;
this.data = data;
}
public T getRoot() {
if (parent == null) {
return this; //<---- Problem is here!
} else {
return getParent().getRoot();
}
}
public T getParent() {
if (parent == null) {
throw new RuntimeException("This already the parent!");
} else {
return parent;
}
}
}
/*
incompatible types
required: T
found: TreeNode<T,E>
*/
How can I fix that and make my code work?
You want to use the so-called getThis() trick. Declare a new method like so:
/** Subclasses must implement this method as {#code return this;} */
protected abstract T getThis();
Then when you need to use this, just call getThis() instead. As a side note, implementing this method will confound a class like BadNode in #Michael Williamson's answer, thus making it harder to write such a class in the first place (which is a good thing).
It's not guaranteed that the type T is the same as the type of the class itself, so you need to add a cast to the line that doesn't compile:
public T getRoot() {
if (parent == null) {
return (T)this;
} else {
return getParent();
}
}
To give a simple example of code that will expose the typing error:
public class GoodNode extends TreeNode<GoodNode, Integer> {
public GoodNode(GoodNode parent, Integer data) {
super(parent, data);
}
}
public class BadNode extends TreeNode<GoodNode, Integer> {
public BadNode(GoodNode parent, Integer data) {
super(parent, data);
}
public static void main(String[] args) {
GoodNode node = new BadNode(null, null).getRoot();
}
}
Running BadNode.main causes a typing error since BadNode(null, null).getRoot() returns an object of class BadNode (since it has no parent), but because BadNode extends TreeNode<GoodNode, Integer>, the return type of getRoot() is GoodNode. Since BadNode cannot be cast to GoodNode, there's a class cast exception:
Exception in thread "main" java.lang.ClassCastException: BadNode cannot be cast to GoodNode
at BadNode.main(BadNode.java:7)
Maybe you are trying to be "too generic"?
public class TreeNode<E> {
protected TreeNode<E> parent;
protected E data;
protected List<TreeNode<E>> children = new ArrayList<TreeNode<E>>();
public TreeNode(T parent, E data) {
this.parent = parent;
this.data = data;
}
public TreeNode<E> getRoot() {
if (parent == null) {
return this;
} else {
return getParent(); // <--- ???
}
}
...
}
BTW: You might want to call something along the lines of parent.getRoot() instead of getParent().
Why do you use a raw type in the extends clause? That might hinder type inference.
Try the following:
public class TreeNode<T extends TreeNode<T,E>, E> {