Код компилируется в Eclipse, но не в javac: карри-лямбды с функциональным подынтерфейсом. Что правильно? - PullRequest
8 голосов
/ 15 марта 2019

Я разработал некоторый код в Eclipse, успешно проверил его, отправил на наш сервер Jenkins CI и получил электронное письмо, которое Maven задыхался из-за ошибки компиляции Java. Впоследствии я выделил проблему и создал следующий минимальный пример, показывающий проблему:

import java.util.List;
import java.util.function.Function;

class MinimalTypeFailureExample {
    public static void main(String[] args) {
        List<String> originalList = null;  // irrelevant
        List<IntToByteFunction> resultList = transform(originalList,
                outer -> inner -> doStuff(inner, outer));
        System.out.println(resultList);
    }

    static <F, T> List<T> transform(List<F> originalList,
            MyFunction<? super F, ? extends T> function) {
        return null;  // irrelevant
    }

    static Byte doStuff(Integer inner, String outer) {
        return null;  // irrelevant
    }
}

@FunctionalInterface
interface MyFunction<F, T> extends Function<F, T> {
    @Override
    T apply(F input);
}

@FunctionalInterface
interface IntToByteFunction {
    Byte applyIntToByte(Integer inner);
}

В Eclipse этот код компилируется без ошибок и, кажется, выполняется как задумано. Однако компиляция с использованием javac выдает следующую ошибку:

MinimalTypeFailureExample.java:7: error: incompatible types: cannot infer type-variable(s) F,T
                List<IntToByteFunction> resultList = transform(originalList, outer -> inner -> doStuff(inner, outer));
                                                              ^
    (argument mismatch; bad return type in lambda expression
      T is not a functional interface)
  where F,T are type-variables:
    F extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
    T extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
1 error

Изменение типа аргумента transform() с MyFunction на Function или удаление подстановочного знака ? extends в типе аргумента приводит к компиляции примера кода в javac.

Очевидно, что Eclipse или javac нарушают Спецификацию языка Java. Вопрос в том, подать ли отчет об ошибке в Eclipse или javac ? Правила вывода типов для универсальных лямбд настолько сложны, что я понятия не имею, является ли эта программа допустимой Java или нет в соответствии с JLS.

Мотивация

В исходном коде transform() был com.google.common.collect.Lists.transform() Гуавы. Интерфейс MyFunction был интерфейсом com.google.common.base.Function компании Guava, который по историческим причинам расширяет java.util.function.Function.

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

Информация о версии для воспроизводимости

Проверенные версии Eclipse:

  • 2018-09 (4.9.0) Идентификатор сборки: 20180917-1800
  • 2019-03 RC1 (4.11 RC1) Идентификатор сборки: 20190307-2044

протестированные версии javac:

Ответы [ 2 ]

2 голосов
/ 16 марта 2019

Похоже, вы столкнулись с JDK ошибка JDK-8156954 , которая была исправлена ​​в Java 9, но не в Java 8.

Это ошибкаJava 8 javac, потому что в вашем примере все типы переменных метода transform могут быть выведены без нарушения спецификации языка Java следующим образом:

  • F: String (через первый параметр originalList типа List<String>)
  • T: IntToByteFunction (через тип возвратаList<IntToByteFunction>)

Эти предполагаемые типы переменных совместимы с типом второго параметра, лямбда-цепочкой выражение:

  • outer -> inner -> doStuff(inner, outer) разрешает (с doStuff(Integer, String) до
  • String -> Integer -> doStuff(Integer, String) разрешает до
  • String -> Integer -> Byte совместимо с
  • String -> IntToByteFunction совместимо с
  • MyFunction<? super String, ? extends IntToByteFunction>

Ваш пример можно свести к минимуму:

import java.util.function.Function;

class MinimalTypeFailureExample {

    void foo() {
        transform((Function<Integer, String>)null, o -> i -> {return "";});
    }

    <T, F> void transform(F f, MyFunction<T, ? extends F> m) {}

}

@FunctionalInterface
interface MyFunction<T, R> extends Function<T, R> {
    @Override
    R apply(T t);
}

MyFunction переопределяет то же самое с тем же (R apply(T t);). Если Function вместо MyFunction используется или MyFunction extends Function, но без @Override R apply(T t); ошибка исчезнет.Также с F вместо ? extends F ошибка исчезает.

Даже если ваш пример отличается от примера в упомянутой ошибке, можно предположить, что это та же ошибка, потому что это единственное несоответствие аргумента ; неверный тип возврата в лямбда-выражении ошибка, исправленная в Java 9, но не в Java 8, которая возникает только с лямбда-функциями в сочетании с Generics Java.

1 голос
/ 15 марта 2019

Я попробовал пример кода с javac 11.0.2 и не получил ошибки. Это предполагает, что ошибка, возможно, была в javac и исправлена ​​в последних версиях. Я немного удивлен этим, потому что, как уже упоминалось, я попробовал протестировать JDK 10 в онлайн-интерфейсе.

Я открыт для других ответов, которые предоставляют более подробную информацию о конкретной проблеме, например, номер ошибки JDK для этой проблемы.

В качестве обходного пути для компиляции кода в JDK 8 к внутреннему лямбда-выражению можно добавить явное приведение:

List<IntToByteFunction> resultList = transform(originalList,
        outer -> (IntToByteFunction) inner -> doStuff(inner, outer));
...