Reputation: 751
I accept that due to the lower bounded wildcard, this predicate should not accept a superclass of String
without an explicit cast.[1,2] This question is rather about type safety enforcement in the lambda's parameter list. Given the second block fails compilation, why is the first allowed to compile without a warning? It appears that in the first case, despite the lambda's parameter declaration, CharSequence
is being cast to String
to satisfy predicate's boundary constraint.
Predicate<? super String> predicate1 = (CharSequence c)
-> c.toString().length() > 2 ;
System.out.println(predicate1.test("foo")); // compiles
Predicate<? super String> predicate2 = (CharSequence c)
-> c.toString().length() > 2 ;
System.out.println(predicate2.test((CharSequence)"foo")); // capture error
error: method test in interface Predicate<T> cannot be applied to given types;
out.println(predicate2.test((CharSequence)"foo"));
^
required: CAP#1
found: CharSequence
reason: argument mismatch; CharSequence cannot be converted to CAP#1
where T is a type-variable:
T extends Object declared in interface Predicate
where CAP#1 is a fresh type-variable:
CAP#1 extends Object super: String from capture of ? super String
Thanks for the work on this. The issue appears to be an assumption that the lambda and the generic process would forced to consume a CharSequence
. However, it's now clear that String
can be submitted to the lambda without a compiler error, so what's happening in the first case and a String
is being submitted to both processes. It's no surprise that the generics process is ignoring the content of the lambda.
Upvotes: 5
Views: 604
Reputation: 1199
It appears that in the first case, despite the lambda's parameter declaration, String is being cast to CharSequence to satisfy predicate's boundary constraint. You said this referring to the 1st case, but there is nothing being cast to CharSequence
here.
If you break your 1st case code like this,
final Predicate<CharSequence> charSequencePredicate = (CharSequence c) -> c.toString().length() > 2;
Predicate<? super String> predicate1 = charSequencePredicate;
System.out.println(predicate1.test("foo"));
You will see that when the predicate1.test("foo")
is called, "foo" is passed as a String
, and when this String
is passed to the Predicate
as it's argument, the assignment to parameter c
is done without any casting as String
implements Charsequence
.
Upvotes: 1
Reputation: 140319
Predicate<? super String> predicate2 = (CharSequence c)
-> c.toString().length() > 2 ;
System.out.println(predicate2.test((CharSequence)"foo")); // capture error
The issue is that predicate2
says it is something which will accept a String
; you're trying to pass it a CharSequence
.
If you change the declaration of the variable to
Predicate<? super CharSequence> predicate2
then it works.
The reason this doesn't work is that you might be passing a MyCharSequence implements CharSequence
to the predicate:
System.out.println(predicate2.test((CharSequence)new MyCharSequence()));
This shouldn't be accepted, because the type of the predicate2
variable says it should only expect to be given an instance of String
. The compiler only sees the type of the actual parameter as CharSequence
, so it can't distinguish between "this is a String
" (would be OK) and "this is a MyCharSequence
" (wouldn't be OK).
Upvotes: 4