In the wikipedia article about co- and contravariance there is an example use case and then an explanatory sentence describing what the type declaration means. I find this extremely useful. After reading the explanation a few times, I feel that I understand what it says.
<T extends Comparable<? super T>> T max(Collection<T> coll);
The bounded wildcard ? super T conveys the information that max calls only contravariant methods from the Comparable interface.
Can somebody explain in a similar language, what the type declaration on the of the andThen() function in the java.util.function.Consumer #FunctionalInterface means:
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
e.g.
The bounded wildcard ? super T conveys the information that andThen .... ?
And I have a secondary question: How can I find out myself, what such a type declaration means? E.g. in first example above from the java.util.Collections util class: How are the type bounds of a class - T - able to convey information about what the methods of T are doing? Can anybody point me to the relevant paragraphs in the Java language specification for this?
Possible answer to the first question:
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
The bounded wildcard ? super T conveys the information that andThen takes Consumers of T or supertypes of T, aka. contravariant Consumers, as arguments.
Possible answer to the secondary question: https://stackoverflow.com/a/2501513
Basically - completely independent of generics (!) - method return return types are inherently "covariant" (assumed to be "producers") in the Java language. If overriding a method in a child class, you can always declare a more specific return type.
Method arguments are of course also "covariant" - you can only pass more specific objects than the method signature specifies. But on subclasses, although the method is technically not overriden for non-parametric arguments - adhering to the Liskov_substitution_principle - it often makes sense to declare "contravariant" argument types in child classes, which - if the name and other arguments are equal - "overloads" the methods in the parent class. "Static" (reference-type-governed) method dispatch will then ensure that the (less specific) child method is called wins. Method arguments are assumed to be "consumed" and then PECS applies. Anyhow, for generic parameters, bridge methods are generated and it is all a bit more hairy. But we will get there.
Related
Suppose I have this (contrived) situation:
public final Comparable<? super Number> frob() {
return x -> 0; // whatever
}
final Comparable<? super Integer> c = frob();
The JLS defines assignability of reference types in terms of supertypes and subtypes only. Fine.
One of the direct supertypes of a parameterized type is an otherwise equivalent parameterized type with an appropriate containing type (a wildcard expression) in one of its type argument slots. A parameterized type with the same raw type and owner type and that containing type argument (among its others) is thus a direct supertype of the original parameterized type. (So by these rules Comparable<?> is a direct supertype of Comparable<Whatever>, as is Comparable<? extends Whatever>, and Comparable<? super Whatever>, and plenty of others that section 4.5.1 lays out.)
The implication of all this is, loosely speaking, if you have a "receiver" reference a bearing type A in your left hand, and a "payload" reference b bearing type B in your right hand, then b is assignable to a if and only if A is a supertype (not just direct) of B. So presumably what the compiler does is calculate B's supertypes, and then see if the resulting collection of types contains A.
I must be wrong in this presumption, though, because analyzing the supertypes of Comparable<? super Number> by itself following these rules will seemingly never yield Comparable<? super Integer>. (That is, looking at just the type arguments, if you "start from" Number and "go up" its supertype hierarchy to calculate its containing types, you'll never "reach" Integer, because as we all know Integer is a subtype of Number, not a supertype of it.)
So in my example above, what rules in the JLS permit Comparable<? super Integer> to be derived as a direct supertype of Comparable<? super Number>? Is it to be found in the section on type inference, even though to my eye there's no inference going on here (i.e. no diamond operator, no var, etc.)?
To make it clear: my problem is not with the assignment, which I know in practice is of course just fine (if the example method above returns a new Comparable<Object> anonymous class, for example, that would work). It's with my understanding of how the JLS allows this.
In Effective Java, in the item "Use bounded wildcards to increase API flexibility", when talking about the usage of PECS (producer-extends, consumer-super), the author mentioned that:
Comparables are always consumers, so you should generally use Comparable<? super T> in preference to Comparable. The same is true of comparators; therefore, you should generally use Comparator<? super T> in preference to Comparator.
It is not clear to me why Comparables and Comparators are considered consumers.
In one of the topic discussing PECS, What is PECS (Producer Extends Consumer Super)?, the consumer is usually referring to a Collection as a parameter for some generic method.
While here Comparable is just an interface.
Can anyone share some insights? Thanks!
A nice analogy can be drawn to the interfaces Consumer<T> and Supplier<T> (with Supplier being analogous to Producer). A Consumer<T> is a function that takes in a T, while a Supplier<T> is a function that returns a T. Notice that we are talking about method signatures and return type, we say nothing about the semantics of the method. This is a core property of PECS: it is independent of the semantics and can be determined solely on the signature and return type of the methods used.
Looking at Comparable<T> and Comparator<T>, we find that both have methods (int compareTo(T) and int compare(T, T)) that take in, i.e. consume, T's.
For the collections, we have to look on how we use the collection, i.e. if we are using producer- or consumer-methods:
If we retrieve data from the collection (T get(int), iterator, ...), the list produces values for us and we use ? extends T.
If we use the collection to store data, (i.e. we call add(T), addAll(Collection<T>), contains(T), ...), we call consuming methods, thus the method is a consumer of our data and we use ? super T.
If we use a collection to both store and retrieve values, the collection acts as a consumer and producer at the same time, thus we have to use the precise T, neither using ... extends ... nor ... super ....
„…It is not clear to me why Comparables and Comparators are considered consumers.…“
Anytime a method member of a generic class C<T>, takes in (i.e. „consumes“) an argument that is of type T, then that method is a consumer of Ts.
So Comparator<T>.compareTo(T o) is said to be a „consumer“ of the o object of the type represented by the type variable T.
When we say "Comparable is a consumer", we actually mean "the method of the interface is a consumer". This is an answer to your doubt that "Comparable is just an interface."
See its signature: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Comparable.html#compareTo(T)
int compareTo(T o)
It consumes the generic T, it does not produce any generic object.
(For the purposes of this post, lets set aside java.util.Observable)
I was experimenting around with generics, and then wildcard types. The aim was to create a type-generic observable cache with deltas provided to the observers. Where this starts to go off the rails is I wanted to allow more generic observers to be used than the one specified in the Observable, e.g. Observer<Object> or some other common superclass.
I've since concluded that this is overly complex for my use case, but the problem itself continues to bother me since I clearly don't understand how to use type wildcarding properly.
So if we start with a simple observer interface:
public interface Observer<T> {
public void notifyChange(ChangeHolder<T> change);
}
And the associated ChangeHolder, in a full implementation this would be more complex, providing lists of added / updated / deleted objects, but this is sufficient to demonstrate the issue
public interface ChangeHolder<T> {
T getChange();
}
So with the Observer defined, I tried to implement the Observable abstract class:
public abstract class Observable<T> {
private Set<Observer<? super T>> observers = new HashSet<>();
public void addObserver(Observer<? super T> obs){
observers.add(obs);
}
public void change(ChangeHolder<T> changes){
for(Observer<? super T> obs : observers){
obs.notifyChange(changes);
}
}
}
And with that I could define some object caches, by declaring something like class TreeCache extends ObservableCache<Tree>, (From this point on I'll use Tree as an example class to be used as a T, assume it to be a simple POJO extending only from Object) and pass ChangeHolder<Tree> objects to TreeCache.change() when necessary. Unfortunately the compiler disagrees:
The method notifyChange(ChangeHolder<capture#2-of ? super T>) in the type Observer<capture#2-of ? super T> is not applicable for the arguments (ChangeHolder<T>)
Which is where my understanding ends.
Without the ChangeHolder class (if my notifyChange method just took a plain T instead) it works just fine since it's perfectly legal to pass a Tree to Observer.notifyChange(Object).
I inferred that I should be able to do the same with the ChangeHolder - ChangeHolder<T> should satisfy notifyChange(ChangeHolder<? super T>) in the same way that T satisfies notifyChange(? super T) but clearly I am misunderstanding something?
There is no wildcard in the signature notifyChange(ChangeHolder<T> change). Therefore the generic type of the passed argument must exactly match the generic type of the Observer instance.
Observer<? super T> means an Observer of some unknown type that is a supertype of T. Since the generic type of obs may not exactly match the generic type of changes, the notifyChange method is not applicable.
There are two possible fixes:
Change the signature to notifyChange(ChangeHolder<? extends T> change) so that the method works for subtypes.
Get rid of the wildcards everywhere, so that you have just <T> instead.
I prefer solution 1, as it is a good idea for signatures to be as general as possible.
Consider the following 2 alternate APIs:
void method(List<?> list)
<T> void method(List<T> list)
I know that their internal implementation will have many differences to deal with, such as List<?> wont be able to write into the list etc.
Also, in my knowledge List<?> will allow any parameterized type with List as base type. So will be the case with List<T> also.
Can anybody tell me if there is any difference at all in what kinds of inputs these 2 APIs will accept. (NOT the 2 APIs internal implementation differences.)
The internal implementation is exactly the same. In fact, both methods compiled with javac will yield equal method byte code, if they compile at all).
However, during compilation, the first method is specified to not care about the component type of the list, while the second requires that the component type be invariant. This means that whenever I call such a method, the component type of list will be fixed by whatever the call site uses.
I can call my method with a List<String> and T will be synonymous to String during the call (from the perspective of the compiler). I can also call it with a List<Runnable> and T will be synonymous to Runnable during the call.
Note that your method does not return anything, but it very well could do so depending on the arguments. Consider the method:
<T> T findFirst(Collection<T> ts, Predicate<T> p) { … }
You can use this method for each T. BUT it only works if our T is equal for the collection and predicate — this is what "invariance" means. You could in fact specify the method to be applicable in more contexts:
<T> T findFirst(Collection<? extends T> ts, Predicate<? super T> p) { … }
This method would work the same as above, but it would be more lenient in what types it accepts. Consider a type hierarchy A extends B extends C. Then you could call:
Collection<A> cs = …;
Predicate<C> p = …;
B b = findFirst(cs, p);
We call the type of ts covariant and the type of p (in the method signature) contravariant.
Wildcards (?) are a different matter. They can be bounded (like in our cases above) to be co- or contravariant. If they are unbounded, the compiler actually needs a concrete type to fill in at compile time (which is why you will sometimes get errors like "type wildcard-#15 is not a match for wildcard-#17"). The specific rules are laid out in the Java Language Specification, Section 4.5.1.
I understand the <? extends T> in Java is crudely equivalent to the existential qualifier (∃) but is <? super T> related to the universal qualifier (∀)?
Feel free to correct me if I'm wrong about ∃.
It's early and I'm confused, so may well be talking nonsense. I could buy that both are existential, as direction need not negate the logic for a "for some" relationship..?
Partially prompted by the fact that List[_] in Scala is described as existential and that's roughly the same as List<?> in Java.
Wildcard types (both ? extends T and ? super T) form a subset of existential types. Both of these you can read like "there exists some type which (extends|is supertype of) T". The key idea is that you don't know exact type.
Universal types are just type parameters. For example, here:
class List<T> { ... }
T is arbitrary, like it has an implicit universal qualifier.
Its not really related to predicate logic, its related to the type hierachy. I can understand why you might read out loud <? extends T> as "For all T's" but its extended to "For all objects that have a super class of T" rather than "For all T in X" as in predicate logic.
Similarly <? super X> should be read as "For objects that are of a type that is a super class on X"
The main difference between <? super T> and <? extends T> is that the first declaration says that some type is an ancestor of T and the second says that some type is a subclass of T. I don't see any correlation with existential qualifier or universal qualifier here. As someone noticed in the comments, the more likely similarity (if you really must have one) is type floor and ceiling. Nevertheless, it's really an abstract analogy.