I am implementing my own linked list in Java mainly as an attempt to learnt the syntax. I have some experience in Scala, and am trying to implement a functional, immutable linked list. I'm having trouble understanding how to make a covariant concatenation method. I want to be able to concatenate (append) a List<Sub> with (to) a List<Super>.
public abstract List<T> {
public abstract T head();
public abstract List<T> tail();
public abstract boolean isEmpty();
// ... more methods
public List<T> concat(List<? extends T> that) {
if (this.isEmpty()) return (List<T>) that; // Gross
else return new Cons<>(this.head(), this.tail().concat(that));
}
}
public class Cons<T> extends List<T> {
private final T head;
private final List<T> tail;
public boolean isEmpty() {return false;}
public Cons(T head, List<T> tail) {this.head = head; this.tail = tail;}
public T head() {return head;}
public List<T> tail() {return tail;}
}
public class Nil<T> extends List<T> {
public T head() {throw new NoSuchElementException();}
public List<T> tail() {throw new NoSuchElementException();}
public boolean isEmpty() {return true;}
}
I seem to only be able to do this by explicitly casting the list of subtypes to a list of supertypes which seems ugly. I'm essentially trying to mimic Scala's List[ T] covariance formalism. Cheers.
CodePudding user response:
As far as Java is concerned, a List<Subclass> isn't a List<Superclass>, and there's no way to tell it otherwise. Neither covariance nor contravariance is supported.
I can think of a few options here:
- Declare
concatas returningList<? extends T>, rather than promising that it will return exactlyList<T>. - Do what you're doing — you know that, since your
Listclass is immutable, it's safe to reinterpret aList<Subclass>as aList<Superclass>, so you can just cast it, plus an appropriate@SuppressWarningsannotation with a comment. (You'll probably want to centralize this in a privateupcastmethod.) - Declare
Cons.tailas having typeList<? extends T>, and whenever you need to convert fromList<? extends Superclass>toList<Superclass>, you can do so by destructuring and reconstructing — creating a newCons<Superclass>orNil<Superclass>with the same fields. (The reason to declareCons.tailasList<? extends T>is so that you don't need to copy the whole list over, but just the first cons.) (As with #2, you'll probably want to centralize this in a privateupcastmethod, whichNilandConscan each implement appropriately.)
