Reputation: 376
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?
Upvotes: 2
Views: 674
Reputation: 2261
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.
Upvotes: 2
Reputation:
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.
Upvotes: 2