Почему я не могу исключить исключение в выражении лямбда Java 8?

Я обновился до Java 8 и попытался заменить простую итерацию с помощью Map с новым выражением lamdba. Цикл ищет нулевые значения и генерирует исключение, если оно найдено. Старый код Java 7 выглядит следующим образом:

for (Map.Entry<String, String> entry : myMap.entrySet()) {
    if(entry.getValue() == null) {
        throw new MyException("Key '" + entry.getKey() + "' not found!");
    }
}

И моя попытка конвертировать это в Java 8 выглядит так:

myMap.forEach((k,v) -> {
    if(v == null) {
        // OK
        System.out.println("Key '" + k+ "' not found!");

        // NOK! Unhandled exception type!
        throw new MyException("Key '" + k + "' not found!");
    }
});

Может ли кто-нибудь объяснить, почему оператор throw здесь не разрешен и как это можно исправить?

Предложение быстрого исправления Eclipse не подходит для меня... оно просто окружает оператор throw блоком try-catch:

myMap.forEach((k,v) -> {
    if(v == null) {
        try {
            throw new MyException("Key '" + k + "' not found!");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
});

Ответы

Ответ 1

Вам запрещено выдавать проверенные исключения, потому что метод void accept(T t, U u) в интерфейсе BiConsumer<T, U> не генерирует никаких исключений. И, как вы знаете, forEach принимает такой тип.

public void forEach(BiConsumer<? super K, ? super V> action) { ... }
                         |
                         V
@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u); // <-- does throw nothing
}

Это правда, когда мы говорим об отмеченных исключениях. Но вам разрешено исключать неконтролируемое исключение, например, IllegalArgumentException:

new HashMap<String, String>()
    .forEach((a, b) -> { throw new IllegalArgumentException(); });

Ответ 2

Вы можете исключать исключения из lambdas.

Лямбде разрешено использовать те же исключения, что и функциональный интерфейс, реализованный лямбдой.

Если у метода функционального интерфейса нет предложения throws, лямбда не может выбрасывать CheckedExceptions. (Вы все еще можете использовать RuntimeExceptions).


В вашем конкретном случае Map.forEach использует параметр BiConsumer в качестве параметра, BiConsumer определяется как:

public interface BiConsumer<T, U> {
    void accept(T t, U u);
}

Лямбда-выражение для этого функционального интерфейса не может вызывать CheckedExceptions.


Методы функциональных интерфейсов, определенные в пакете java.util.function, не генерируют исключений, но вы можете использовать другие функциональные интерфейсы или создавать свои собственные, чтобы иметь возможность генерировать исключения, т.е. с учетом этого интерфейса:

public interface MyFunctionalInterface<T> {
    void accept(T t) throws Exception;
}

Следующий код будет легальным:

MyFunctionalInterface<String> f = (s)->throw new Exception("Exception thrown in lambda");