1
  1. question about Wildcard

Example:Student extends Person

    Person person = new Person();
    Student student = new Student();

    List<? super Student> list = new ArrayList<>();
    list.add(student); // success
    list.add(person); // compile error

    List<? extends Person> list2 = new ArrayList<>();
    list2.add(person); // compile error
    list2.add(student);// compile error

I have read the answer below a question "capture#1-of ? extends Object is not applicable"

You are using generic wildcard. You cannot perform add operation as class type is not determinate. You cannot add/put anything(except null) -- Aniket Thakur

Official doc:The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype

But why could list.add(student) compile successfully ?

  1. Design of java.util.function.Function
public interface Function<T, R>{

    //...

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
}

Why before is designed to Function<? super V, ? extends T> rather than Function<V,T> when the type of return is Function<V,R> and type of the input is V ? (It still can pass compile and use flexibly)

lczapski
  • 4,026
  • 3
  • 16
  • 32
SimpleIto
  • 13
  • 2
  • 3
    See: [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/q/2723397/5221149) --- I believe that once your understand what PECS means, you'll have your answers. – Andreas Aug 19 '19 at 02:48
  • 1
    See also: [Is List a subclass of List? Why are Java generics not implicitly polymorphic?](https://stackoverflow.com/q/2745265/5221149) – Andreas Aug 19 '19 at 02:49

2 Answers2

2

To understand these questions, you have to understand how generics work with subtyping (which is explicitly denoted in Java using the extends keyword). Andreas mentioned the PECS rules, which are their representations in Java.

First of all, I want to point out that the codes above can be corrected by a simple cast

ArrayList<? super Student> list = new ArrayList<>();
list.add(new Student());
ArrayList<Person> a = (ArrayList<Person>) list; // a covariance
a.add(new Person());

And compiles & runs well (rather than raising any exceptions)

The reason is simple, when we have a consumer (which takes some objects and consume them, such as the add method), we expect it to take objects of type no more than(superclasses) the type T we specified, because the process of consuming needs possibly any member(variables, methods etc.) of the type it wants, and we want to ensure that type T satisfy all the members the consumer requires.

On the contrary, a producer, which produces objects for us (like the get method), has to supply objects of type no less than the specified type T so that we can access any member that T has on the object produced.

These two are closely related to subtyping forms called covariance and contravariance

As for the second question, you can refer to the implementation of Consumer<T> as well (which is somewhat simpler):

default Consumer<T> andThen(Consumer<? super T> after) {
  Objects.requireNonNull(after);
  return (T t) -> { accept(t); after.accept(t); };
}

the reason why we need this ? super T is that: when we are combining two Consumers using the method andThen, suppose that the former Consumer takes an object of type T, we expect the later to take a object of type no more than T so it would not try to access any member that T doesn't have.

Therefore, rather than simply writing Consumer<T> after but Consumer<? super T> after, we allow the former consumer (of type T) to be combined with a consumer that takes an object not exactly of type T, but maybe smaller then T, by the convenience of covariance. That makes the following codes sound:

Consumer<Student> stu = (student) -> {};
Consumer<Person> per = (person) -> {};
stu.andThen(per);

The compose method of type Function also applies, by the same consideration.

ice1000
  • 6,406
  • 4
  • 39
  • 85
Phosphorus15
  • 134
  • 7
1

IMO This is probably the most complex concept in vanilla Java. So let's break this down a bit. I'll start with your second question.

Function<T, R> takes an instance t of type T and returns an instance r of type R. With inheritance that means that you could supply an instance foo of type Foo if Foo extends T and similarly return bar of type Bar if Bar extends R.

As a library maintainer who wants to write a flexible generic method, it's hard, and actually impossible, to know in advance all the classes which might be used with this method which extend T and R. So how are we going to write a method that handles them? Further, the fact that these instances have types which extend the base class is none of our concern.

This is where the wildcard comes in. During the method call we say that you can use any class which meets the envelope of the required class. For the method in question, we have two different wildcards using upper and lower bounded generic type parameters:

public interface Function<T, R>{
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before)

Lets now say that we want to take advantage of this method... for the example lets define some basic classes:

class Animal{} 
class Dog extends Animal{} 
class Fruit{} 
class Apple extends Fruit{} 
class Fish{} 
class Tuna extends Fish{} 

Imagine our function and transformation is defined as below:

Function<Animal, Apple> base = ...;
Function<Fish, Animal> transformation = ...;

We can combine these functions using compose to create a new function:

Function<Fish, Apple> composed = base.compose(transformation);

This is all fine and dandy, but now imagine that in the desired output function we actually only want to use Tuna as the input. If we did not use the lower-bounded ? super V as the input type parameter for the Function we pass to compose then we would get a compiler error:

default <V> Function<V, R> compose(Function<V, ? extends T> before)
...
Function<Tuna, Apple> composed = base.compose(transformation);
> Incompatible types: 
> Found: Function<Fish, Apple>, required: Function<Tuna, Apple>

This happens because the return type for the call to compose specifies V as Tuna while transformation on the other hand specifies its "V" as Fish. So now when we try to pass transformation to compose the compiler requires transformation to accept a Tuna as its V and of course Tuna does not identically match Fish.

On the other hand, the original version of the code (? super V) allows us to treat V as a lower bound (i.e. it allows "contravariance" vs. "invariance" over V). Instead of encountering a mismatch between Tuna and Fish the compiler is able to successfully apply the lower bound check ? super V which evaluates to Fish super Tuna, which is true since Tuna extends Fish.

For the other case, imagine our call is defined as:

Function<Animal, Apple> base = ...;
Function<Fish, Dog> transformation = ...;
Function<Fish, Apple> composed = base.compose(transformation);

If we did not have the wildcard ? extends T then we would get another error:

default <V> Function<V, R> compose(Function<? super V, T> before)
Function<Fish, Apple> composed = base.compose(transformation);
// error converting transformation from
//    Function<Fish, Dog> to Function<Fish, Animal>

The wildcard ? extends T allows this to work as T is resolved to Animal and the wildcard resolves to Dog, which can satisfy the constraint Dog extends Animal.

For your first question; these bounds really only work in the context of a method call. During the course of the method, the wildcard will be resolved to an actual type, just as ? super V was resolved to Fish and ? extends T was resolved to Dog. Without the information from the generic signature, we would have no way for the compiler to know what class can be used on the type's methods, and therefore none are allowed.

mouselabs
  • 284
  • 2
  • 7
flakes
  • 21,558
  • 8
  • 41
  • 88