Java не может скомпилировать общий лямбда-аргумент, если не указан тип параметра - PullRequest
7 голосов
/ 02 апреля 2019

Во время тестирования я обновил свой Junit до 5.0 (таким образом, заменив некоторые из моих методов assertTrue () новыми версиями). После этого я обнаружил, что один из моих тестов не скомпилирован. Я уменьшил проблему до простой старой Java без Junit или других зависимостей. В результате получается следующий код, который не будет компилироваться:

  public static void recreate() {
    // This does NOT work
    Recreation.assertTrue(identity((x) -> Boolean.TRUE)); 
    // This DOES work
    Recreation.assertTrue(identity((String x) -> Boolean.TRUE)); 
  }

  private static class Recreation {
    public static void assertTrue(boolean b) {
      System.out.println("boolean argument: " + b);
    }

    // If this method is removed, the code will compile. 
    public static void assertTrue(Supplier<Boolean> booleanSupplier) {
      System.out.println("supplier argument: " + booleanSupplier.toString());
    }
  }

  private static <K> K identity(Function<String, K> function) {
    return function.apply("hello");
  }

Как показано в приведенном выше примере, код скомпилируется, если выполняется одно из следующих условий:

  1. Указывается тип лямбда-параметра

  2. Перегруженный метод assertTrue (поставщик booleanSupplier) удален

Это проблема с выводом / стиранием типа или что здесь происходит?

Ошибка сборки:

Error:(10, 35) incompatible types: inference variable K has incompatible bounds
    lower bounds: java.util.function.Supplier<java.lang.Boolean>,java.lang.Object
    lower bounds: java.lang.Boolean

Характеристика:

openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment (build 11.0.1+13-Ubuntu-3ubuntu114.04ppa1)
OpenJDK 64-Bit Server VM (build 11.0.1+13-Ubuntu-3ubuntu114.04ppa1, mixed mode, sharing)

OS: Ubuntu 14.04.5 LTS

РЕДАКТИРОВАТЬ: Подтверждено, что проблема существует и в Java 8:

java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
exit status 1
Main.java:10: error: incompatible types: inferred type does not conform to upper bound(s)
    Recreation.assertTrue(identity((x) -> Boolean.TRUE));
                                  ^
    inferred: Boolean
    upper bound(s): Supplier<Boolean>,Object
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

Ответы [ 2 ]

4 голосов
/ 04 апреля 2019

После осмотра и прочтения спецификации языка Java здесь https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1

Я думаю, что здесь есть два шага:

Во-первых, разрешение перегрузки не можетвыведите тип identity((x) -> Boolean.TRUE), потому что это неявная лямбда, я думаю, что это не принимается во внимание для простоты.Таким образом, он будет расширять поиск параметров и использовать public static void assertTrue(Supplier<Boolean> booleanSupplier).

Во-вторых, после завершения разрешения перегрузки включается вывод типа . На этот раз он действительно проверяет предполагаемый тип, который являетсяBoolean, и, поскольку он не совместим с Supplier<Boolean> booleanSupplier, вы получаете ошибку компиляции.

Как и в предыдущем ответе, есть решения для этого,

например,

Recreation.assertTrue(identity((x) -> () -> Boolean.TRUE));

Я нашел хорошее объяснение здесь: Java8: неоднозначность с лямбдами и перегруженными методами

0 голосов
/ 04 апреля 2019

После обширных исследований я наткнулся на это сообщение об ошибке .Этот отчет ссылается на этот раздел в спецификации Java, который дает вам базовые знания о том, как все это работает.Имейте это в виду, потому что это будет необходимо позже.

Проблема может быть уменьшена, чтобы выглядеть следующим образом (хотя я знаю, что это не то, что вы хотите, это воспроизведет ошибку):

Supplier<Boolean> o = identity((x) -> true);

Итак, я считаю, что проблема заключается в том, как Java решает, какими должны быть дженерики типов.Когда вы указываете Supplier<Boolean> в качестве типа o, он сообщает компилятору, что тип возвращаемого значения identity должен быть Supplier<Boolean>.

Теперь в вашем примере у вас нетпеременная, которая хранит вывод identity.Это та часть, которая вытекает из спецификации.Всякий раз, когда Java получает универсальный тип, этот тип должен находиться в определенных пределах.В основном это означает, что в иерархии расширения есть определенный самый высокий класс и самый низкий класс.

Например, если у вас есть класс C, который расширяет класс B, который также расширяет класс A, вашверхняя граница может быть C, а нижняя граница может быть A.Таким образом, вы можете использовать что угодно от A до C.Это всего лишь пример того, как работают границы.

Это в основном то, что происходит в вашем классе.Поскольку вы не указываете String в качестве типа параметра, Java не знает, какой это может быть тип.Так как он не знает, он делает все возможное, чтобы привести его к правильному типу, но поскольку родовые типы настолько неоднозначны, он не может привести его к правильному типу.Он как-то решает использовать Supplier<Boolean> в качестве базовой линии (вероятно, также определенной где-то в спецификации) и ожидает, что это будет тип возвращаемого значения.Так как он не получает это, он выдает ошибку.Почему он не решает проверить, является ли это Boolean, выше моего понимания, но согласно отчету об ошибке все работает так, как задумано.

Некоторые потенциальные исправления могут выглядеть следующим образом:

Recreation.assertTrue(identity((Function<String, Boolean>) (x) -> Boolean.TRUE));
Recreation.assertTrue((Boolean) identity((x) -> Boolean.TRUE));

Или просто неявно сообщая ему, какой тип параметра.

Recreation.assertTrue(identity((String x) -> Boolean.TRUE));

Я знаю, что у меня нет лучшего понимания этого конкретного обстоятельства, но это должно дать вам хотя бы базовую оценкупонимание того, как работает вся система.

...