2

Given the following classes:

class A { public A a() {return new A();}; };
class B extends A { public B b() {return new B();} };
class C extends B { public C c() {return new C();} };
class D extends C { public D d() {return new D();} };

I want to write some UnaryOperators that can accept instances of A, B and C, but not D.

So my choice for declaring the reference type is UnaryOperator<? super C>. Since I'm using super, it means it can also accept an instance of Object.

UnaryOperator<? super C> op1 = arg -> arg.a(); // does not compile, arg could be Object
UnaryOperator<? super C> op2 = arg -> arg.b(); // does not compile, arg could be Object
UnaryOperator<? super C> op3 = arg -> arg.c(); // does compile
UnaryOperator<? super C> op4 = arg -> arg.d(); // this is not expected to compile

Why does op1 makes the code fail to compile with a message

Type mismatch: cannot convert from A to C

and op2 makes the code fail to compile with a message

Type mismatch: cannot convert from B to C,

but op3 compiles fine, and let me call a method available only in C?

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
  • 2
    Why do you want to do this? Not accepting instances of D is a violation of https://en.wikipedia.org/wiki/Liskov_substitution_principle – VeeArr Jul 18 '18 at 20:48
  • 2
    `op1 = (A arg) -> arg.a(); op2 = (B arg) -> arg.b() ...` – Oleksandr Pyrohov Jul 18 '18 at 20:51
  • This is just for understanding better how the java compiler infers types in lambda expressions involving generics. You're right about the substitution principle. It's just because, as far as I know, this behaviour is and edge case, and I want to know if there is some justification for this behaviour. – Felipe Araújo Jul 18 '18 at 21:08
  • The problem you have is that both D is a C, but A is an Object, you can't defined a range in the hierarchy for the compiler to check, you would need to add a run time check. – Peter Lawrey Jul 19 '18 at 09:50

2 Answers2

2

Why does op1 makes the code fail to compile...?

When a generic functional interface is parameterized by wildcards, there are different instantiations that could satisfy the wildcard and produce different function types. For example, each of 1:

UnaryOperator<C> (function type C -> C);
UnaryOperator<B> (function type B -> B);
UnaryOperator<A> (function type A -> A);
UnaryOperator<Object> (function type Object -> Object);

is a UnaryOperator<? super C>.

Sometimes, it's possible to known from the context, such as the parameter types of a lambda, which function type is intended. Other times, it is necessary to pick one:

UnaryOperator<? super C> op1 = (A arg) -> arg.a();
                                ^

UnaryOperator<? super C> op2 = (B arg) -> arg.b();
                                ^
...

If you don't pick one, the bounds are used 2:

UnaryOperator<? super C> op = arg -> arg.c(); // valid

Where C is a bound, so the lambda expression is a UnaryOperator<C> in this case.


1 - JLS 9.9. Function Types - the last paragraph.
2 - JLS 15.27.3. Type of a Lambda Expression - if T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed, then the ground target type is the non-wildcard parameterization T.

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
1

@Oleksandr has told you why it fails to compile. I will tell you why you cannot use UnaryOperator<? super C> to only accept A, B, C but reject D.


UnaryOperator<? super C> does not means the function accept ? super C. It means the generic T in UnaryOperator<T> is ? super C.

Consider you have a instance UnaryOperator<? super C> uo.

  • Does uo.apply(new A()) make sense? NO, because UnaryOperator<B> is assignable to UnaryOperator<? super C> but can't accept A.
  • Does uo.apply(new B()) make sense? NO, because UnaryOperator<C> is assignable to UnaryOperator<? super C> but can't accept B.

So what does uo can accept? The answer is C, because only C can assignable to any ? super C.

So your UnaryOperator<? super C> is in fact same as UnaryOperator<C>. Though your IDE may tell you it accept Object, but it will not accept object not assignable to C.

Dean Xu
  • 4,438
  • 1
  • 17
  • 44