Java Generics GetThis Trick Explanation
Asked Answered
R

1

7

I am reading about Java Generics and I came across this topic where I am a bit confused.

From : http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205

public abstract class Node <N extends Node<N>>  {
   private final List<N> children = new ArrayList<N>();
   private final N parent;

   protected Node(N parent) {
     this.parent = parent;
     parent.children.add(this);  // error: incompatible types
   }
   public N getParent() {
     return parent;
   }
   public List<N> getChildren() {
     return children;
   }
 }

public class SpecialNode extends Node<SpecialNode> {
   public SpecialNode(SpecialNode parent) {
     super(parent);
   }
} 

Scrolling lower a couple of screens...

public abstract class Node <N extends Node<N>>  {
   ...
   protected Node(N parent) {
     this.parent = parent;
     parent.children.add( (N)this ); // warning: unchecked cast
   }
   ...
 }

Casts whose target type is a type parameter cannot be verified at runtime and lead to an unchecked warning. This unsafe cast introduces the potential for unexpected ClassCastException s and is best avoided.

Could someone give me an example where the above code throws a ClassCastException ?

Thanks.

Racialism answered 24/12, 2009 at 12:31 Comment(1)
You are correct; I did not look closely, and assumed that the exception would be thrown on extract. Have deleted my comment as it may be misleading.Anomaly
P
5

First code sample

In the first code sample, there is a compile-error. You can verify it yourself in your IDE.

Mine says : The method add(N) in the type List<N> is not applicable for the arguments (Node<N>)

The problem is that N is a subtype of Node. The List of N might be a list of StupidNode, where StupidNode is a subclass of Node. But the current instance might not be a StupidNode, it could be a different subclass of Node, so adding it could be wrong.


Second code sample

Now the second code sample is one where the developer, annoyed by the compile-time error that he doesn't understand, believes the compiler is wrong and try to force a cast. Such a cast make the code compile, but could break at runtime in the same conditions (as explained higher).

Therefore, the compiler issues a warning, to help you understand that something could be wrong.


Sample problem

For both previous code samples, the problem could happen if the calling code writes (for two subclasses NodeA and NodeB of Node):

Node<NodeA> root = new NodeA<NodeA>(null); 
// code needs a change, to be able to handle the root, that has no parent
// The line with parent.children will crash with a NullPointerException
Node<NodeB> child = new NodeB<NodeB>(root);

On the second line, the code that will run in the constructor of Node will interpret like (replacing the format parameter N by the current parameter NodeB):

public abstract class Node <NodeB>  {
   private final List<NodeB> children = new ArrayList<NodeB>();
   private final NodeB parent;

   protected Node(NodeB parent) {
     this.parent = parent;
     parent.children.add(this);  // error: incompatible types
   }
  // ...
 }

As you can see, the second line of the caller will pass a NodeA instance, while the constructor of Node expects a NodeB! Hence the error...


UPDATE as asked by comment : sample code for subclasses NodeA (or NodeB).

public class NodeA extends Node<NodeA>  {
   public NodeA(NodeA parent) {
     super(parent);
   }
}
Pyrology answered 24/12, 2009 at 13:12 Comment(9)
Thanks KLE, I think I understand what you wrote. Regarding the second code sample, could you show me what code could cause it to break at runtime ?Racialism
@Racialism For me, the second is just a replacement of the code that doesn't compile in the first, adding only a cast to make it compile. The conditions are exactly the same, it is just that, instead of receiving a compile-time error, you will receive a runtime error (ClassCastException).Pyrology
@Racialism I added a sample problem section to my answer, to help your understand the problem. Note the problem is the same for both code samples you wrote, the only difference is the way it manifests itself (compile-time error or runtime ClassCastException).Pyrology
I tried the following but there's no exception at runtime. public static void main(String[] args) { SpecialNode parent = new SpecialNode(null); SpecialNode child = new SpecialNode(parent); List<SpecialNode> children = child.getChildren(); for (SpecialNode specialNode : children) { // } }Racialism
@Racialism The current Node implementation doesn't compile, so you can't test it. Also, shown in my example, the line that calls a method on the parent cannot work if the parent is null, as in your comment... :-(Pyrology
@Pyrology Thanks for your patience. But I am still confused. Could you also add the class definition for NodeA/NodeB ?Racialism
@Pyrology - offtopic, but I think Christmas Day is a valid delay that doesn't require apologies. :-)Profligate
@Andrzej hahaha ! So true ;-)Pyrology
@Pyrology Hi, sorry, as I am late here. But can you please show a way how to safeguard NodeA being passed to the parameter of new NodeB<NodeB>(root); Is there a pattern / a design for this safety? Thanks in advance.Psychotechnics

© 2022 - 2024 — McMap. All rights reserved.