2

I encountered something that bugs me when I wrote some code. I gathered the two examples in the code sample below.

The cls1 line uses a lambda expression but doesn't compile, while the cls2 line uses a method references and compiles. I know that if I'm using non generic objects, I have no issues there, but here, I'm using generics, and, more specifically, wildcards.

import java.lang.annotation.*;
import java.util.Optional;

public class MCVE {

  static class Foo {}
  static class Bar extends Foo {}

  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.TYPE)
  static @interface Baz { Class<? extends Foo> value(); }

  @Baz(Bar.class)
  static class Quz {}

  // Lambda expression - doesn't compile
  Class<? extends Foo> cls1 = Optional.ofNullable(Quz.class.getAnnotation(Baz.class))
      .map(baz -> baz.value())
      .orElse(Bar.class);

  // Method reference - compiles
  Class<? extends Foo> cls2 = Optional.ofNullable(Quz.class.getAnnotation(Baz.class))
      .map(Baz::value)
      .orElse(Bar.class);

}

In functionality, both lines are doing the same. So I just don't understand what's going on under the roof that makes the use of a lambda expression fail while a method reference has no issue.

For those who'll ask, the error received while compiling is the following:

MCVE.java:25: error: incompatible types: Class<Bar> cannot be converted to Class<CAP#1>
      .orElse(Bar.class);
                 ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Foo from capture of ? extends Foo
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

This is rather laconic and doesn't provide any extremely useful piece of information.

Also, please note that I've done my research and that this is error is not the same as in the "Similar Questions" you can see on the right panel. The interesting part here is "fresh-type variable".

Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
  • http://stackoverflow.com/questions/20543966/incompatible-types-and-fresh-type-variable – Hosseini Jun 09 '16 at 22:14
  • 1
    In general: Java's type system is pretty much a black box these days, and there's not much in the way of general rules about when explicit types are necessary and unnecessary. – Louis Wasserman Jun 09 '16 at 22:18
  • @Hosseini Thank you for that link. While it's much closer to what I experienced than all I saw in "Similar Questions", it's speaking about assignment -- which is not relevant here, I guess -- It doesn't address the question about why it's not OK with a lambda, but OK with a method reference. – Olivier Grégoire Jun 09 '16 at 22:18
  • In eclipse, I get errors on both cases. Assigning the result of ```map``` to an intermediate variable of type ```Optional>``` fixes both cases. Inference of the parameter of ```orElse``` shows ```Class extends Foo>``` so ```Bar.class``` should be applicable. Weird... – Jorn Vernee Jun 09 '16 at 22:33
  • @JornVernee I did as well; however, if you compile manually then the cls2 line does not give an error. Probably a bug in Eclipse. – Michael Markidis Jun 09 '16 at 22:36
  • @JornVernee NetBeans shows only one issue (this is where I saw the original issue), so does `javac`. I made sure to get the compiled error as retrieved by `javac` to avoid any IDE bias. – Olivier Grégoire Jun 09 '16 at 22:36

1 Answers1

1

Imagine you have 3 classes:

static class Foo {}
static class Bar extends Foo {}
static class Dem extends Foo {}

Compiler will find from lambda expression:

var x1 = Optional.ofNullable(Quz.class.getAnnotation(Baz.class));
// typeof x1 = Optional<Baz> 

var x2 = x1.map(baz -> baz.value())
// typeof x2 = Optional<Class<T>>, where <T extends Foo> - this is the black magic you suffer with
// E.g. it can be 
// a) Optional<Class<T=Foo>> or 
// b) Optional<Class<T=Bar>> or 
// c) Optional<Class<T=Dem>>

var x3 = x2.orElse(Bar.class);
// In case (a) and (b) - this code should work, in case (c) it should fail.
// Without additional explicit hint (conversion) compiler reports about this issue.

When you use method reference - compiler ignores described type inference and uses original Baz type declaration, so

.map(Baz::value) // is identical to
.map(baz -> (Class<? extends Foo>) baz.value())
ursa
  • 4,404
  • 1
  • 24
  • 38
  • Something bugs me with this explanation, which is why I haven't accepted yet. It's that there is absolutely no difference between your `Bar` and `Dem` classes, yet you say that it's different: "In case (a) and (b) - this code should work, in case (c) it should fail." That's so inconsistent, I just can't get around this to properly understand. Maybe you have a typo somewhere? – Olivier Grégoire Jun 16 '16 at 14:31
  • When T=Der you have typeof x2 = Optional>, so you cannot apply orElse(Bar.class) to it - Der and Bar are incompatible. You cannot cast der object to Bar and visa versa. This is just a theoretical example to show compiler concerns. – ursa Jun 16 '16 at 15:22
  • Okay, your intent is much clearer with this and I understand what you meant. Thank you for everything :) – Olivier Grégoire Jun 17 '16 at 07:29