Вывод типа по лямбда-выражениям

Я преобразовывал некоторый код для использования функций Java 8. В следующем надуманном примере

    Arrays.asList("1", "2", "3", "cheese", "5").stream().map(line -> {
        try {
            return Optional.of(Integer.parseInt(line));
        } catch (NumberFormatException xep) {
            return Optional.empty();
        }
    }).forEach( v -> 
        System.out.println(v.orElse(999))
    );

(намерение состоит в том, чтобы проанализировать некоторые строки как целые и заменить любые неразборчивые значения на 999)

Компилятор сообщает

error: incompatible types: int cannot be converted to CAP#1
System.out.println(v.orElse(999))
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ? extends Object"

Я безуспешно пытался преобразовать 999 в целое число или объект.

Кажется, что реальная проблема заключается в том, что предполагаемый возвращаемый тип первой лямбды — Optional<Object>, а не Optional<Integer>.

Если я сделаю это

    Arrays.asList("1", "2", "3", "cheese", "5").stream().map(line -> {
        Optional<Integer> ans;
        try {
            ans = Optional.of(Integer.parseInt(line));
        } catch (NumberFormatException xep) {
            ans = Optional.empty();
        }
        return ans;
    }).forEach( v -> 
        System.out.println(v.orElse(999))
    );

он работает отлично, но не так элегантно. Есть ли лучший способ «направить» компилятор к типу возвращаемого значения, который я хочу?


person Peter Hull    schedule 15.10.2014    source источник
comment
Вы пробовали использовать Optional.<Integer>empty()?   -  person JB Nizet    schedule 15.10.2014


Ответы (4)


Простое исправление заключается в использовании целевого типа:

return Optional.<Integer> empty();

Также я отмечаю, что вы используете Integer.parseInt, который возвращает int, поэтому вы также можете использовать OptionalInt, который решит вашу проблему и сохранит операцию бокса:

try {
  return OptionalInt.of(Integer.parseInt(line));
} catch (NumberFormatException xep) {
  return OptionalInt.empty();
}
person assylias    schedule 15.10.2014
comment
re: OptionalInt - хорошая мысль, хотя я использовал 'int' только в качестве простого примера. Моя настоящая проблема в том, что я просто не могу вспомнить, что параметризованный тип идет после точки - я пробовал Optional<Integer>.empty(), но потом отказался от этого подхода! - person Peter Hull; 15.10.2014

Как сказано assylias, вы можете исправить это, используя return Optional.<Integer> empty();.

Однако большой вопрос заключается в том, почему вы вообще используете здесь Optional?

Stream.of("1", "2", "3", "cheese", "5").mapToInt(line -> {
    try {
        return Integer.parseInt(line);
    } catch (NumberFormatException xep) {
        return 999;
    }
}).forEach(System.out::println);

делает работу намного проще, когда вы все равно хотите заменить значение.

Если вы хотите выполнить действие только для действительных значений (аналог Optional.ifPresent(Consumer)), вы можете принять во внимание правило, согласно которому вы должны предпочитать предварительные проверки перехвату исключений, когда вы ожидаете недопустимые значения:

Stream.of("1", "2", "3", "cheese", "5")
      .filter(Pattern.compile("^[+-]?[0-9]{1,9}$").asPredicate())
      .mapToInt(Integer::parseInt)
      .forEach(System.out::println);

(Я упростил регулярное выражение, оно не принимает все возможные значения int, но отклоняет все недопустимые)

person Holger    schedule 15.10.2014
comment
«int» был просто простым примером, мой фактический код включал поиск вещей в сложном объекте, похожем на карту. Спасибо за Stream.of(), я не знал об этой функции. - person Peter Hull; 15.10.2014
comment
Понятно, что это всего лишь простой пример, но шаблоны все еще применимы: вам не нужен Optional, если вы используете orElse в той же потоковой операции, и вы можете рассмотреть предварительные проверки вместо перехвата. Как пища для размышлений… - person Holger; 15.10.2014

Вы можете сделать это, если вам не нужна переменная-член ans:

try {
    return Optional.<Integer>of(Integer.parseInt(line));
} catch (NumberFormatException xep) {
    return Optional.<Integer>empty();
}

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

person Jesper    schedule 15.10.2014
comment
Нет необходимости в целевом типе в блоке try. - person assylias; 15.10.2014
comment
@assylias Я только что попробовал, ты прав. Странно, что там это не нужно... - person Jesper; 15.10.2014
comment
Я полагаю, потому что Optional.of(someInteger) явно является Optional<Integer>, тогда как компилятор не может определить общий тип Optional.empty() без дополнительного контекста. - person assylias; 15.10.2014

ответ @Assilyas правильный. Я хотел бы предложить альтернативу на основе утилиты guava Ints::tryParse:

В отличие от Integer.parseInt(String), этот метод возвращает null вместо того, чтобы генерировать исключение в случае сбоя синтаксического анализа.

С его помощью и новыми ссылками на методы вы можете написать:

Arrays.asList("1", "2", "3", "cheese", "5")
        .stream()
        .map(Ints::tryParse)
        .map(Optional::ofNullable)
        .forEach(v -> System.out.println(v.orElse(999)));
person gontard    schedule 15.10.2014