Java8 Lambdas и исключения

Интересно, может ли кто-нибудь объяснить мне эту странность? Я использую обновление для Java 8.

Учитывая этот метод

private <F,T> T runFun(Function<Optional<F>, T> fun, Optional<F> opt) {
   return fun.apply(opt) ;
}

Если я сначала создаю функцию Object и передаю это в метод выше, вещи компилируются.

private void doesCompile() {
    Function<Optional<String>, String> fun = o -> o.orElseThrow(() -> new RuntimeException("nah"));
    runFun(fun, Optional.of("foo"));

}

Но, если я встраиваю функцию в лямбда, компилятор говорит

несообщаемое исключение X; должен быть пойман или объявлен брошенным

private void doesNotCompile () {
    runFun(o -> o.orElseThrow(() -> new RuntimeException("nah")), Optional.of("foo"));
}

Обновление: Оказывается, сообщение об ошибке было сокращено maven. Если скомпилировать непосредственно с помощью javac, ошибка:

error: unreported exception X; must be caught or declared to be thrown
            runFun(o -> o.orElseThrow(() -> new RuntimeException("nah")), Optional.of("foo"));
                                     ^
  where X,T are type-variables:
    X extends Throwable declared in method <X>orElseThrow(Supplier<? extends X>)
    T extends Object declared in class Optional

Также смотрите здесь для исполняемого тестового кода.

Ответ 1

Это выглядит как ошибка JDK-8054569, которая не влияет на Eclipse.

Мне удалось сузить его, заменив функцию "Поставщик" и извлекая метод orElseThrow:

abstract <T> void f(Supplier<T> s);

abstract <T, X extends Throwable> T g(Supplier<? extends X> x) throws X;

void bug() {
    f(() -> g(() -> new RuntimeException("foo")));
}

а затем, удалив поставщиков и лямбда вообще:

abstract <T> void f(T t);

abstract <T, X extends Throwable> T g(X x) throws X;

void bug() {
    f(g(new RuntimeException("foo")));
}

который на самом деле является более чистым примером, чем тот, который представлен в отчете об ошибке. Это показывает ту же ошибку, если она скомпилирована как Java 8, но отлично работает с -source 1.7.

Я предполагаю, что что-то о передаче типа возвращаемого типа метода в общий параметр метода приводит к тому, что вывод типа для исключения прерывается, поэтому он предполагает, что тип Throwable и жалуется, что этот проверенный тип исключения не обрабатывается. Ошибка исчезнет, ​​если вы объявите bug() throws Throwable или измените привязку на X extends RuntimeException (чтобы она не была отмечена).

Ответ 2

Вот что я решил для меня:

вместо записи

optional.map(this::mappingFunction).orElseThrow(() -> new BadRequestException("bla bla"));

Я написал:

optional.map(this::mappingFunction).<BadRequestException>orElseThrow(() -> new BadRequestException("bla bla"));

Добавление явного <BadRequestException> помогает с этими случаями крапивницы лямбды (которые очень раздражают...)

UPDATE: это, если вы не можете обновить последнюю версию JDK, если вы можете...

Ответ 3

Если вы пытаетесь скомпилировать проект другого человека, попробуйте выполнить обновление до версии 1.8.0_92

Ответ 4

Подобно @keisar, я мог бы решить мою проблему (см., Что maven-compiler-plugin не может скомпилировать файл, с которым у Eclipse нет проблем), указав параметр type.

Однако я обнаружил, что гораздо удобнее (поскольку я использую NotFoundException во многих местах) просто сделать класс исключений своим собственным Supplier:

public class NotFoundException extends RuntimeException
    implements Supplier<NotFoundException> {

    // Rest of the code

    @Override
    public NotFoundException get() {
        return this;
    }

}

Тогда вы можете просто сделать:

// Distribution.rep().get(id) returns a java.util.Optional
Distribution distro = Distribution.rep().get(id).orElseThrow(
    new NotUniqueException("Exception message"));