Как преобразовать необязательный в OptionalInt?

У меня есть Optional который я хочу "преобразовать" в OptionalInt, но, похоже, не существует простого способа сделать это.

Вот что я хочу сделать (надуманный пример):

public OptionalInt getInt() {
    return Optional.ofNullable(someString).filter(s -> s.matches("\\d+")).mapToInt(Integer::parseInt);
}

Тем не менее, нет mapToInt() для Optional.

Лучшее, что я мог придумать, это:

return Optional.ofNullable(someString)
    .filter(s -> s.matches("\\d+"))
    .map(s -> OptionalInt.of(Integer.parseInt(s)))
    .orElse(OptionalInt.empty());

но это кажется не элегантным.

Я что-то упускаю из JDK, что может сделать преобразование более элегантным?

Ответ 1

Хотя код не читается, чем обычное условное выражение, существует простое решение:

public OptionalInt getInt() {
    return Stream.of(someString).filter(s -> s != null && s.matches("\\d+"))
        .mapToInt(Integer::parseInt).findAny();
}

С помощью Java 9 вы можете использовать

public OptionalInt getInt() {
    return Stream.ofNullable(someString).filter(s -> s.matches("\\d+"))
        .mapToInt(Integer::parseInt).findAny();
}

Как сказано, ни один из них не является более читаемым, чем обычное условное выражение, но я думаю, что он по-прежнему выглядит лучше, чем при использовании mapOrElseGet (и для первого варианта не требуется Java 9.

Ответ 2

Нет, нет способа сделать это более элегантно, используя стандартный Java API. И насколько я знаю, не планируется добавлять такие методы в JDK-9. Я попросил Пола Сандоса добавить mapToInt и т.д. здесь его ответ:

Я:

Не рекомендуется ли предлагать также способ для переноса между типами Optional типа mapToInt, mapToObj и т.д. как это сделано в Stream API?

Пол:

Я не хочу туда идти, мой ответ преобразуется Optional* в *Stream. Аргумент для добавления mapOrElseGet (обратите внимание, что примитивные варианты возвращают U) состоит в том, что из него может быть скомпилирована другая функциональность.

Итак, вы, вероятно, будете иметь в Java-9:

return Optional.of(someString).filter(s -> s.matches("\\d+"))
     .mapOrElseGet(s -> OptionalInt.of(Integer.parseInt(s)), OptionalInt::empty);

Но ничего больше.

Это потому, что авторы JDK настаивают на том, что класс Optional и его примитивные друзья (особенно примитивные друзья) не должны широко использоваться, это просто удобный способ выполнить ограниченный набор операций над возвращаемым значением методов, которые могут вернуться "отсутствие ценности". Кроме того, примитивные опционы предназначены для повышения производительности, но на самом деле это гораздо менее значительны, чем потоки, поэтому использование Optional<Integer> также прекрасное. С проектом Valhalla (надеюсь, вы прибудете на Java-10) вы сможете использовать Optional<int>, а OptionalInt станет ненужным.

В вашем конкретном случае лучший способ сделать это - использовать тернарный оператор:

return someString != null && someString.matches("\\d+") ? 
       OptionalInt.of(Integer.parseInt(someString)) : OptionalInt.empty();

Я предполагаю, что вы хотите вернуть OptionalInt из метода. В противном случае это еще более сомнительно, зачем вам это нужно.

Ответ 3

Если у вас есть какой-либо объект, а не только String, вы можете временно пройти через Stream:

public static <T> OptionalInt toOptionalInt(Optional<T> optional, ToIntFunction<? super T> func) {
  return optional.map(Stream::of).orElseGet(Stream::empty)
    .mapToInt(func)
    .findFirst();
}

Это решение имеет то преимущество, что это однострочный, то есть вы можете скопировать/вставить содержимое метода и просто изменить func на все, что захотите. Недостаток проходит через Stream для достижения того, чего вы хотите. Но если вы хотите использовать общий однострочный слой, это он.

Если вам нужен метод утилиты, вы, вероятно, предпочтете использовать следующее:

public static <T> OptionalInt toOptionalInt(Optional<T> optional, ToIntFunction<? super T> func) {
  if (optional.isPresent()) {
    return OptionalInt.of(func.applyAsInt(optional.get()));
  } else {
    return OptionalInt.empty();
  }
}

Ответ 4

Вот еще один вариант, который не должен использовать Stream и избегает компиляции регулярных выражений каждый раз:

private static final Predicate<String> IS_DIGITS = Pattern.compile("^\\d+$")
        .asPredicate();

public OptionalInt getInt() {
    return Optional.ofNullable(someString)
            .filter(IS_DIGITS)
            .map(Integer::valueOf)
            .map(OptionalInt::of)
            .orElseGet(OptionalInt::empty);
}

Обратите внимание, что вам нужно привязать регулярное выражение, потому что asPredicate() использует find() вместо match().

Или, если вы используете Guava, вы можете полностью исключить регулярные выражения и использовать их класс Ints:

public OptionalInt getInt() {
    return Optional.ofNullable(someString)
            .map(Ints::tryParse)
            .map(OptionalInt::of)
            .orElseGet(OptionalInt::empty);
}

Ответ 5

Вот как я конвертирую Optional<String> в OptionalInt

OptionalInt lastSeenId = Optional.of("123").map(Integer::parseInt).map(OptionalInt::of).orElseGet(OptionalInt::empty);