Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is <T> List<? extends T> f() a useful signature

Is <T> List<? extends T> f() a useful signature? Is there any problem with it / using it?

This was an interview question. I know this:

  1. It compiles fine
  2. Use it like List<? extends Number> lst = obj.<Number>f(), and then I can call on lst only those List methods that do not contain T in their signatures (say, isEmpty(), size(), but not add(T), remove(T)

Does that fully answer the question?

like image 442
user10777718 Avatar asked Jan 02 '19 10:01

user10777718


People also ask

What is the difference between List <? Super T and List <? Extends T?

extends Number> represents a list of Number or its sub-types such as Integer and Double. Lower Bounded Wildcards: List<? super Integer> represents a list of Integer or its super-types Number and Object.

What is Super T means in Java?

super T denotes an unknown type that is a supertype of T (or T itself; remember that the supertype relation is reflexive). It is the dual of the bounded wildcards we've been using, where we use ? extends T to denote an unknown type that is a subtype of T .

Is ArrayList a subtype of List?

Generic Classes and Subtyping Using the Collections classes as an example, ArrayList<E> implements List<E>, and List<E> extends Collection<E>. So ArrayList<String> is a subtype of List<String>, which is a subtype of Collection<String>.


2 Answers

This method signature is "useful", in the sense that you can implement non-trivial, non-degenerate methods with it (that is, returning null and throwing errors are not your only options). As the following example shows, such a method can be useful for implementing some algebraic structures like e.g. monoids.

First, observe that List<? extends T> is a type with the following properties:

  • You know that all elements of this list conform to the type T, so whenever you extract an element from this list, you can use it in position where a T is expected. You can read from this list.
  • The exact type is unknown, so you can never be certain that an instance of a particular subtype of T can be added to this list. That is, you effectively cannot add new elements to such a list (unless you use nulls / type casts / exploit unsoundness of Java's type system, that is).

In combination, it means that List<? extends T> is kind-of like an append-protected list, with type-level append-protection.

You can actually do meaningful computations with such "append-protected" lists. Here are a few examples:

  • You can create append-protected lists with a single element:

    public static <T> List<? extends T> pure(T t) {   List<T> result = new LinkedList<T>();   result.add(t);   return result; } 
  • You can create append-protected lists from ordinary lists:

    public static <T> List<? extends T> toAppendProtected(List<T> original) {   List<T> result = new LinkedList<T>();   result.addAll(original);   return result; } 
  • You can combine append-protected lists:

    public static <T> List<? extends T> combineAppendProtected(   List<? extends T> a,   List<? extends T> b ) {   List<T> result = new LinkedList<T>();   result.addAll(a);   result.addAll(b);   return result; } 
  • And, most importantly for this question, you can implement a method that returns an empty append-protected list of given type:

    public static <T> List<? extends T> emptyAppendProtected() {   return new LinkedList<T>(); } 

Together, combine and empty form an actual algebraic structure (a monoid), and methods like pure ensure that it's non-degenerate (i.e. it has more elements that just an empty list). Indeed, if you had an interface similar to the usual Monoid typeclass:

  public static interface Monoid<X> {     X empty();     X combine(X a, X b);   } 

then you could use the above methods to implement it as follows:

  public static <T> Monoid<List<? extends T>> appendProtectedListsMonoid() {     return new Monoid<List<? extends T>>() {       public List<? extends T> empty() {         return ReadOnlyLists.<T>emptyAppendProtected();       }        public List<? extends T> combine(         List<? extends T> a,         List<? extends T> b       ) {         return combineAppendProtected(a, b);       }     };   } 

This shows that methods with the signature given in your question can be used to implement some common design patterns / algebraic structures (monoids). Admittedly, the example is somewhat contrived, you probably wouldn't want to use it in practice, because you don't want to astonish the users of your API too much.


Full compilable example:

import java.util.*;  class AppendProtectedLists {    public static <T> List<? extends T> emptyAppendProtected() {     return new LinkedList<T>();   }    public static <T> List<? extends T> combineAppendProtected(     List<? extends T> a,     List<? extends T> b   ) {     List<T> result = new LinkedList<T>();     result.addAll(a);     result.addAll(b);     return result;   }    public static <T> List<? extends T> toAppendProtected(List<T> original) {     List<T> result = new LinkedList<T>();     result.addAll(original);     return result;   }    public static <T> List<? extends T> pure(T t) {     List<T> result = new LinkedList<T>();     result.add(t);     return result;   }    public static interface Monoid<X> {     X empty();     X combine(X a, X b);   }    public static <T> Monoid<List<? extends T>> appendProtectedListsMonoid() {     return new Monoid<List<? extends T>>() {       public List<? extends T> empty() {         return AppendProtectedLists.<T>emptyAppendProtected();       }        public List<? extends T> combine(         List<? extends T> a,         List<? extends T> b       ) {         return combineAppendProtected(a, b);       }     };   }    public static void main(String[] args) {     Monoid<List<? extends String>> monoid = appendProtectedListsMonoid();     List<? extends String> e = monoid.empty();     // e.add("hi"); // refuses to compile, which is good: write protection!     List<? extends String> a = pure("a");     List<? extends String> b = pure("b");     List<? extends String> c = monoid.combine(e, monoid.combine(a, b));     System.out.println(c); // output: [a, b]   }  } 
like image 124
Andrey Tyukin Avatar answered Sep 25 '22 02:09

Andrey Tyukin


I interpret "is it a useful signature" to mean "can you think of a use-case for it".

T is determined at the call site, not inside the method, so there are only two things that you can return from the method: null or an empty list.

Given that you can create both of these values in roughly as much code as invoking this method, there isn't really a good reason to use it.


Actually, another value that can be safely returned is a list where all of the elements are null. But this isn't useful either, since you can only invoke methods which add or remove literal null from the return value, because of the ? extends in the type bound. So all you've got is thing which counts the number of nulls it contains. Which isn't useful either.

like image 22
Andy Turner Avatar answered Sep 24 '22 02:09

Andy Turner