I've started learning generics and I've come across a compilation error regarding wildcards that I don't understand.
Consider the following partial implementation of a LinkedList:
public class LinkedList<T> {
Node<T> head;
private class Node<T> {
T item;
Node<T> next;
Node(T item){
this.item = item;
}
}
public void concat(LinkedList<T> otherList) {
Node<T> otherListHead = otherList.head;
// ... iterating through otherList and adding its members to this list
}
}
this compiles well.
However, if I wanted to be able to concatenate any list of wildcards extending T:
public class LinkedList<T> {
...
public void concat(LinkedList<? extends T> otherList) {
Node<? extends T> otherListHead = otherList.head;
// ... iterating through other list and adding its members to this list
}
}
I would get the following compilation error:
error: incompatible types: LinkedList<CAP#1>.Node<CAP#1> cannot be converted to LinkedList<T>.Node<? extends T>
Node<? extends T> otherListHead = otherList.head;
^
where T is a type-variable:
T extends Object declared in class LinkedList
where CAP#1 is a fresh type-variable:
CAP#1 extends T from capture of ? extends T
Now I have two questions:
- After reading about the get-put principle and about errors when wrapping wild cards I have a good intuition as to why it doesn't work, but I'll still be happy for a specific explanation.
- What baffled me most, is that making the nested class static removes the compilation error but I don't understand why though.
public class LinkedList<T> {
Node<T> head;
/*This compiles well. Why?*/
private static class Node<T> {
T item;
Node<T> next;
Node(T item){
this.item = item;
}
}
public void concat(LinkedList<? extends T> otherList) {
Node<? extends T> otherListHead = otherList.head;
// ... iterating through other list and adding its members to this list
}
}
I would be delighted if anyone could throw some light on this, cheers!
CodePudding user response:
The problem is that the compiler doesn't know that the type of LinkedList<? extends T> is the same type as Node<? extends T> - the two wildcard types could be different subtypes of T, so the compile error is appropriate.
The fix is to type the method, locking the subtype to the same subtype for both:
public <U extends T> void concat(LinkedList<U> otherList) {
// see below
}
This is the same as a wildcard <? extends T>, but it's a constant for the method invocation.
You need a little more generics fu inside the method too:
public <U extends T> void concat(LinkedList<U> otherList) {
// Because you have an inner class, you must use this syntax
LinkedList<U>.Node<U> otherListHead = otherList.head;
// Create the local typed Node using the foreign Node's data
Node<T> node = new Node<>(otherList.head.item);
// Add node to this and repeat for all Nodes in the other list
}
The reason it works without special syntax when Node is a static class, ie:
public <U extends T> void concat(LinkedList<U> otherList) {
Node<U> otherListHead = otherList.head; // doesn't need LinkedList<U> prefix
// ...
}
is because whereas instances of static classes exist independently, inner classes can't exist without the enclosing class instance and this is just the syntax that java requires.
Incidentally, when the Node class is not static, the type T of Node is hiding the type T of LinkedList, so you should delete the type from Node, instead just using the type T from LinkedList which the inner class Node acquires from the enclosing LinkedList instance, ie:
public static class LinkedList<T> {
Node head;
private class Node { // non static version should not be typed
T item; // T comes from the enclosing class instance
Node next;
Node(T item){
this.item = item;
}
}
// ...
}
Perhaps that is what you intended.
