Java generics self-reference: is it safe?
up vote
12
down vote
favorite
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();
}
java generics this self-reference
add a comment |
up vote
12
down vote
favorite
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();
}
java generics this self-reference
add a comment |
up vote
12
down vote
favorite
up vote
12
down vote
favorite
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();
}
java generics this self-reference
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();
}
java generics this self-reference
java generics this self-reference
edited Nov 20 at 19:26
asked Nov 20 at 19:15
Michele Mariotti
5,67543163
5,67543163
add a comment |
add a comment |
4 Answers
4
active
oldest
votes
up vote
9
down vote
accepted
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.
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
2
I'm not sure that's correct. I was going to post something similar, but then I tested it, andE root = (E) this;
doesn't throw an exception.E root = (E) this;
is not resolved toNodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved toNode root = (Node) this;
@MicheleMariotti
– Eran
Nov 20 at 19:41
@Eran I thinkClassCastException
may happens when you iterate through result of 'listDescendants()'
– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
2
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
|
show 3 more comments
up vote
2
down vote
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>
.
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
1
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Not weird at all for something likeIComparable
in C# example
– D. Ben Knoble
Nov 21 at 4:08
add a comment |
up vote
2
down vote
The problem is not in E root = (E) this
. It might work well until you start iterating through result of listNodes()
.
That example demonstrates where exactly ClassCastException
will be thrown:
public interface Node<E extends Node<E>> {
List<E> getRelatedNodes();
default List<E> getAllNodes() {
List<E> result = new ArrayList<>();
result.add((E) this); //<--that cast is not a problem because of type erasure
return result;
}
}
class NodeA implements Node<NodeA> {
public NodeA() {
}
@Override
public List<NodeA> getRelatedNodes() {
return null;
}
}
class NodeB implements Node<NodeA> {
private List<NodeA> relatedNodes;
public NodeB(List<NodeA> relatedNodes) {
this.relatedNodes = relatedNodes;
}
@Override
public List<NodeA> getRelatedNodes() {
return relatedNodes;
}
}
Execute:
List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
for (NodeA node : nodes) { //ClassCastException will be thrown
System.out.println(node);
}
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
add a comment |
up vote
1
down vote
With this sort of situation it is often useful to have a getThis
method that (by convention) returns this
.
I would do the following
public interface Node<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public List<R> getParents();
public List<R> getChildren();
public List<E> listDescendants() ;
}
public interface NodeRelation<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public E getParent();
public E getChild();
}
abstract class ANode<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements Node<E,R> {
abstract protected E getThis() ;
public List<E> listDescendants()
{
List<E> result = new ArrayList<>();
E root = getThis() ;
...
return result;
}
}
abstract class ARelation<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements NodeRelation<E,R> {
}
class CNode extends ANode<CNode, CRelation> {
public CNode getThis() { return this ; }
...
}
class CRelation extends ARelation<CNode, CRelation> {
...
}
Although I might not bother with having both abstract class and interface layers.
1
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
add a comment |
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
9
down vote
accepted
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.
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
2
I'm not sure that's correct. I was going to post something similar, but then I tested it, andE root = (E) this;
doesn't throw an exception.E root = (E) this;
is not resolved toNodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved toNode root = (Node) this;
@MicheleMariotti
– Eran
Nov 20 at 19:41
@Eran I thinkClassCastException
may happens when you iterate through result of 'listDescendants()'
– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
2
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
|
show 3 more comments
up vote
9
down vote
accepted
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.
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
2
I'm not sure that's correct. I was going to post something similar, but then I tested it, andE root = (E) this;
doesn't throw an exception.E root = (E) this;
is not resolved toNodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved toNode root = (Node) this;
@MicheleMariotti
– Eran
Nov 20 at 19:41
@Eran I thinkClassCastException
may happens when you iterate through result of 'listDescendants()'
– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
2
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
|
show 3 more comments
up vote
9
down vote
accepted
up vote
9
down vote
accepted
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.
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.
answered Nov 20 at 19:36
ernest_k
18k41837
18k41837
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
2
I'm not sure that's correct. I was going to post something similar, but then I tested it, andE root = (E) this;
doesn't throw an exception.E root = (E) this;
is not resolved toNodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved toNode root = (Node) this;
@MicheleMariotti
– Eran
Nov 20 at 19:41
@Eran I thinkClassCastException
may happens when you iterate through result of 'listDescendants()'
– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
2
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
|
show 3 more comments
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
2
I'm not sure that's correct. I was going to post something similar, but then I tested it, andE root = (E) this;
doesn't throw an exception.E root = (E) this;
is not resolved toNodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved toNode root = (Node) this;
@MicheleMariotti
– Eran
Nov 20 at 19:41
@Eran I thinkClassCastException
may happens when you iterate through result of 'listDescendants()'
– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
2
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
That's it! Thanks!
– Michele Mariotti
Nov 20 at 19:39
2
2
I'm not sure that's correct. I was going to post something similar, but then I tested it, and
E root = (E) this;
doesn't throw an exception. E root = (E) this;
is not resolved to NodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this;
@MicheleMariotti– Eran
Nov 20 at 19:41
I'm not sure that's correct. I was going to post something similar, but then I tested it, and
E root = (E) this;
doesn't throw an exception. E root = (E) this;
is not resolved to NodeA root = (NodeA) this
due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this;
@MicheleMariotti– Eran
Nov 20 at 19:41
@Eran I think
ClassCastException
may happens when you iterate through result of 'listDescendants()'– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I think
ClassCastException
may happens when you iterate through result of 'listDescendants()'– Aleksandr Semyannikov
Nov 20 at 19:50
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
@Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
– ernest_k
Nov 20 at 20:13
2
2
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
@ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
– Aleksandr Semyannikov
Nov 20 at 20:35
|
show 3 more comments
up vote
2
down vote
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>
.
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
1
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Not weird at all for something likeIComparable
in C# example
– D. Ben Knoble
Nov 21 at 4:08
add a comment |
up vote
2
down vote
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>
.
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
1
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Not weird at all for something likeIComparable
in C# example
– D. Ben Knoble
Nov 21 at 4:08
add a comment |
up vote
2
down vote
up vote
2
down vote
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>
.
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>
.
answered Nov 20 at 19:19
chrylis
49.8k1678115
49.8k1678115
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
1
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Not weird at all for something likeIComparable
in C# example
– D. Ben Knoble
Nov 21 at 4:08
add a comment |
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
1
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Not weird at all for something likeIComparable
in C# example
– D. Ben Knoble
Nov 21 at 4:08
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
– Michele Mariotti
Nov 20 at 19:27
1
1
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
– flakes
Nov 20 at 20:25
Not weird at all for something like
IComparable
in C# example– D. Ben Knoble
Nov 21 at 4:08
Not weird at all for something like
IComparable
in C# example– D. Ben Knoble
Nov 21 at 4:08
add a comment |
up vote
2
down vote
The problem is not in E root = (E) this
. It might work well until you start iterating through result of listNodes()
.
That example demonstrates where exactly ClassCastException
will be thrown:
public interface Node<E extends Node<E>> {
List<E> getRelatedNodes();
default List<E> getAllNodes() {
List<E> result = new ArrayList<>();
result.add((E) this); //<--that cast is not a problem because of type erasure
return result;
}
}
class NodeA implements Node<NodeA> {
public NodeA() {
}
@Override
public List<NodeA> getRelatedNodes() {
return null;
}
}
class NodeB implements Node<NodeA> {
private List<NodeA> relatedNodes;
public NodeB(List<NodeA> relatedNodes) {
this.relatedNodes = relatedNodes;
}
@Override
public List<NodeA> getRelatedNodes() {
return relatedNodes;
}
}
Execute:
List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
for (NodeA node : nodes) { //ClassCastException will be thrown
System.out.println(node);
}
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
add a comment |
up vote
2
down vote
The problem is not in E root = (E) this
. It might work well until you start iterating through result of listNodes()
.
That example demonstrates where exactly ClassCastException
will be thrown:
public interface Node<E extends Node<E>> {
List<E> getRelatedNodes();
default List<E> getAllNodes() {
List<E> result = new ArrayList<>();
result.add((E) this); //<--that cast is not a problem because of type erasure
return result;
}
}
class NodeA implements Node<NodeA> {
public NodeA() {
}
@Override
public List<NodeA> getRelatedNodes() {
return null;
}
}
class NodeB implements Node<NodeA> {
private List<NodeA> relatedNodes;
public NodeB(List<NodeA> relatedNodes) {
this.relatedNodes = relatedNodes;
}
@Override
public List<NodeA> getRelatedNodes() {
return relatedNodes;
}
}
Execute:
List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
for (NodeA node : nodes) { //ClassCastException will be thrown
System.out.println(node);
}
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
add a comment |
up vote
2
down vote
up vote
2
down vote
The problem is not in E root = (E) this
. It might work well until you start iterating through result of listNodes()
.
That example demonstrates where exactly ClassCastException
will be thrown:
public interface Node<E extends Node<E>> {
List<E> getRelatedNodes();
default List<E> getAllNodes() {
List<E> result = new ArrayList<>();
result.add((E) this); //<--that cast is not a problem because of type erasure
return result;
}
}
class NodeA implements Node<NodeA> {
public NodeA() {
}
@Override
public List<NodeA> getRelatedNodes() {
return null;
}
}
class NodeB implements Node<NodeA> {
private List<NodeA> relatedNodes;
public NodeB(List<NodeA> relatedNodes) {
this.relatedNodes = relatedNodes;
}
@Override
public List<NodeA> getRelatedNodes() {
return relatedNodes;
}
}
Execute:
List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
for (NodeA node : nodes) { //ClassCastException will be thrown
System.out.println(node);
}
The problem is not in E root = (E) this
. It might work well until you start iterating through result of listNodes()
.
That example demonstrates where exactly ClassCastException
will be thrown:
public interface Node<E extends Node<E>> {
List<E> getRelatedNodes();
default List<E> getAllNodes() {
List<E> result = new ArrayList<>();
result.add((E) this); //<--that cast is not a problem because of type erasure
return result;
}
}
class NodeA implements Node<NodeA> {
public NodeA() {
}
@Override
public List<NodeA> getRelatedNodes() {
return null;
}
}
class NodeB implements Node<NodeA> {
private List<NodeA> relatedNodes;
public NodeB(List<NodeA> relatedNodes) {
this.relatedNodes = relatedNodes;
}
@Override
public List<NodeA> getRelatedNodes() {
return relatedNodes;
}
}
Execute:
List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
for (NodeA node : nodes) { //ClassCastException will be thrown
System.out.println(node);
}
answered Nov 20 at 20:15
Aleksandr Semyannikov
412214
412214
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
add a comment |
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
– Michele Mariotti
Nov 21 at 12:31
add a comment |
up vote
1
down vote
With this sort of situation it is often useful to have a getThis
method that (by convention) returns this
.
I would do the following
public interface Node<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public List<R> getParents();
public List<R> getChildren();
public List<E> listDescendants() ;
}
public interface NodeRelation<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public E getParent();
public E getChild();
}
abstract class ANode<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements Node<E,R> {
abstract protected E getThis() ;
public List<E> listDescendants()
{
List<E> result = new ArrayList<>();
E root = getThis() ;
...
return result;
}
}
abstract class ARelation<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements NodeRelation<E,R> {
}
class CNode extends ANode<CNode, CRelation> {
public CNode getThis() { return this ; }
...
}
class CRelation extends ARelation<CNode, CRelation> {
...
}
Although I might not bother with having both abstract class and interface layers.
1
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
add a comment |
up vote
1
down vote
With this sort of situation it is often useful to have a getThis
method that (by convention) returns this
.
I would do the following
public interface Node<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public List<R> getParents();
public List<R> getChildren();
public List<E> listDescendants() ;
}
public interface NodeRelation<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public E getParent();
public E getChild();
}
abstract class ANode<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements Node<E,R> {
abstract protected E getThis() ;
public List<E> listDescendants()
{
List<E> result = new ArrayList<>();
E root = getThis() ;
...
return result;
}
}
abstract class ARelation<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements NodeRelation<E,R> {
}
class CNode extends ANode<CNode, CRelation> {
public CNode getThis() { return this ; }
...
}
class CRelation extends ARelation<CNode, CRelation> {
...
}
Although I might not bother with having both abstract class and interface layers.
1
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
add a comment |
up vote
1
down vote
up vote
1
down vote
With this sort of situation it is often useful to have a getThis
method that (by convention) returns this
.
I would do the following
public interface Node<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public List<R> getParents();
public List<R> getChildren();
public List<E> listDescendants() ;
}
public interface NodeRelation<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public E getParent();
public E getChild();
}
abstract class ANode<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements Node<E,R> {
abstract protected E getThis() ;
public List<E> listDescendants()
{
List<E> result = new ArrayList<>();
E root = getThis() ;
...
return result;
}
}
abstract class ARelation<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements NodeRelation<E,R> {
}
class CNode extends ANode<CNode, CRelation> {
public CNode getThis() { return this ; }
...
}
class CRelation extends ARelation<CNode, CRelation> {
...
}
Although I might not bother with having both abstract class and interface layers.
With this sort of situation it is often useful to have a getThis
method that (by convention) returns this
.
I would do the following
public interface Node<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public List<R> getParents();
public List<R> getChildren();
public List<E> listDescendants() ;
}
public interface NodeRelation<E extends Node<E, R>,
R extends NodeRelation<E, R>>
{
public E getParent();
public E getChild();
}
abstract class ANode<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements Node<E,R> {
abstract protected E getThis() ;
public List<E> listDescendants()
{
List<E> result = new ArrayList<>();
E root = getThis() ;
...
return result;
}
}
abstract class ARelation<E extends ANode<E,R>,
R extends ARelation<E,R>>
implements NodeRelation<E,R> {
}
class CNode extends ANode<CNode, CRelation> {
public CNode getThis() { return this ; }
...
}
class CRelation extends ARelation<CNode, CRelation> {
...
}
Although I might not bother with having both abstract class and interface layers.
edited Nov 20 at 23:30
answered Nov 20 at 21:53
Theodore Norvell
7,07541637
7,07541637
1
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
add a comment |
1
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
1
1
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
Upvoted. Eventually, I ended up implementing the same thing :)
– Michele Mariotti
Nov 21 at 12:03
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53400009%2fjava-generics-self-reference-is-it-safe%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown