Could anyone please explain why this code:
interface Lol {
default Try<Seq<? extends Number>> lol() {
return Try.of(List::empty);
}
}
class LolImpl implements Lol {
@Override
public Try<Seq<? extends Number>> lol() {
return Try
.of(() -> List.of(1, 2, 3))
//.onFailure(Object::hashCode)
;
}
}
fails to compile if I uncomment onFailure
statement? No idea what happens here. How to improve it?
You can call Try.of()
with explicit generic type returned to satisfy compiler checks. Something like:
Try.<Seq<? extends Number>of(() -> List.of(1,2,3))
Try.of()
returns type Try<T>
where T
is the type returned by the supplier. And because List.of(T t...)
returns List<T>
, then the final type seen by the compiler is Try<List<Integer>
, which is not what the method returned type defined. Java generics with specific type are invariant and they don't support covariant or contravariant substitutions, so List<Integer> != List<Number>
.
Working example:
import io.vavr.collection.List;
import io.vavr.collection.Seq;
import io.vavr.control.Try;
interface Lol {
default Try<Seq<? extends Number>> lol() {
return Try.of(List::empty);
}
}
class LolImpl implements Lol {
@Override
public Try<Seq<? extends Number>> lol() {
return Try
.<Seq<? extends Number>>of(() -> List.of(1, 2, 3))
.onFailure(t -> System.out.println(t.getMessage()));
}
public static void main(String[] args) {
System.out.println(new LolImpl().lol());
}
}
Output:
Success(List(1, 2, 3))
Further investigation shown that this is most probably a generic compiler problem. Take a look at following plain Java example:
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
interface Some<T> {
static <T> Some<T> of(Supplier<T> supplier) {
return new SomeImpl<>(supplier.get());
}
default Some<T> shout() {
System.out.println(this);
return this;
}
class SomeImpl<T> implements Some<T> {
private final T value;
public SomeImpl(T value) {
this.value = value;
}
}
static void main(String[] args) {
final Some<List<CharSequence>> strings = Some.of(() -> Arrays.asList("a", "b", "c"));
}
}
This code compiles without any issue and compiler infers type returned by Arrays.asList()
from the expected type on the left side:
Now, if I call this Some<T>.shout()
method, which does nothing and returns Some<T>
, compiler infers the type not from the expected variable type, but from the last returned type:
Of course Arrays.asList("a","b","c")
returns List<String>
and this is the type
shout()` method infers and returns:
Specifying explicit type of Some<T>.of()
solves the problem as in the Try.of()
example:
I was searching Oracle documentation on type inference and there is this explanation:
The Java compiler takes advantage of target typing to infer the type parameters of a generic method invocation. The target type of an expression is the data type that the Java compiler expects depending on where the expression appears.
Source: https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html#target_types
It looks like this "depending on where the expression appears" in this case means inferred type from the previously returned exact type. It would explain why skipping shout()
method makes compiler aware, that we expect Some<List<CharSequence>>
and when we add shout()
method it starts returning Some<List<String>>
, because this is what shout()
method sees from the returned type of Some.of()
method. Hope it helps.