Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics GetThis Trick Explanation

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.

like image 287
javaStudent Avatar asked Dec 24 '09 12:12

javaStudent


1 Answers

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);
   }
}
like image 179
KLE Avatar answered Sep 28 '22 16:09

KLE