Обробка винятків потоками


10

У мене є Map<String,List<String>>і хочу, щоб це перетворилося, Map<String,List<Long>>тому що кожен Stringзі списку являє собою Long:

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(Long::valueOf)
                                                      .collect(toList()))
               );

Моє основне питання - це те, що кожен Stringможе не відображати правильно Long; може виникнути якесь питання. Long::valueOfможе спричинити винятки. Якщо це так, я хочу повернути нулеве або порожнєMap<String,List<Long>>

Тому що я хочу повторити цю outputкарту. Але я не можу прийняти жодне перетворення помилок; навіть жодної. Будь-яка ідея про те, як я можу повернути порожній вихід у разі неправильної рядки -> Довга конверсія?


Я погоджуюся з рішенням Naman, але, на жаль, у блоці уловлення я не можу отримати ключ (Entry :: getKey), для якого рядок -> Довге перетворення невірно
AntonBoarf

Подібне обговорення тут: String to int - ймовірно, погані дані повинні уникати винятків, коли я врешті-решт вирішив попередньо перевірити за допомогою регулярного вираження (документи parseLong використовують ті самі правила розбору, і ви, ймовірно, хочете повернути файл, LongStreamякщо плануєте видалити emptyрезультати)
AjahnCharles

Вибачте, я неправильно зрозумів. Я думав, ти маєш намір повернути один запис як порожній / нульовий; але зараз я думаю, ти маєш на увазі всю карту!
AjahnCharles

1
Не зовсім зрозуміло, в чому головний момент - ви хочете повернути порожню карту в разі помилки, але все-таки роздрукувати «ключ», де помилка з’явилася на консолі? Я маю на увазі, інформація про контекст, де з'явився виняток, зазвичай транспортується до стека викликів у винятку. Незалежно від цього: Ви спеціально запитували про потоки, але я настійно рекомендую уникати вкладених дзвінків "збирати". Люди, яким доведеться підтвердити це пізніше (а це, можливо, буде і вам у майбутньому !), Будуть дивуватися, що ч ... ви там зробили. Принаймні, введіть кілька правильно названих помічників.
Marco13

Відповіді:


4

Як щодо явного catchнад винятком:

private Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    try {
        return input.entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                        .map(Long::valueOf)
                        .collect(Collectors.toList())));
    } catch (NumberFormatException nfe) {
        // log the cause
        return Collections.emptyMap();
    }
}

ОК звучить добре ... але в уловці (nfe) я хотів би отримати конкретне значення ключа (Entry :: getKey) та неправильний рядок, для якого він не вдається, щоб я міг точно ввійти там, де йде не так. Це можливо ?
АнтонБорф

@AntonBoarf Якщо ви просто хочете , щоб увійти в ключ, для якого він не зміг розібрати рядок, використанняnfe.getMessage()
Нама

1
@AntonBoarf Повідомлення про виняток міститиме неправильно введений рядок введення. Щоб отримати відповідальний ключ, я б input.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger

@Holder. Дякую ... Це здається складним ... Мені цікаво, чи краще використовувати стандарт для циклу Java5 в моєму випадку
AntonBoarf

@AntonBoarf просто реалізуйте обидва та порівняйте…
Holger

3

Мені особисто подобається надати інформацію про Optionalаналіз числа:

public static Optional<Long> parseLong(String input) {
    try {
        return Optional.of(Long.parseLong(input));
    } catch (NumberFormatException ex) {
        return Optional.empty();
    }
}

Потім, використовуючи власний код ( ігноруючи неправильний вхід):

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(MyClass::parseLong)
                                                      .filter(Optional::isPresent)
                                                      .map(Optional::get)
                                                      .collect(toList()))
               );

Крім того, розгляньте допоміжний метод, щоб зробити це більш лаконічним:

public static List<Long> convertList(List<String> input) {
    return input.stream()
        .map(MyClass::parseLong).filter(Optional::isPresent).map(Optional::get)
        .collect(Collectors.toList());
}

public static List<Long> convertEntry(Map.Entry<String, List<String>> entry) {
    return MyClass.convertList(entry.getValue());
}

Потім ви можете відфільтрувати результати в колекторі потоку:

Map<String, List<Long>> converted = input.entrySet().stream()
    .collect(Collectors.toMap(Entry::getKey, MyClass::convertEntry));

Ви також можете зберігати порожні Optionalоб’єкти у своїх списках, а потім, порівнюючи їх індекс у новому List<Optional<Long>>(замість List<Long>) з оригіналом List<String>, ви можете знайти рядок, який викликав помилкові введення. Ви також можете просто зареєструвати ці збоїMyClass#parseLong

Однак, якщо ваше бажання не працювати на будь-який поганий вхід взагалі, то навколишній весь потік в тому, що ви намагаєтеся зловити (за відповідь Нама в) є маршрут я б.


2

Ви можете створити StringBuilderключ за винятком і перевірити, чи eleє цифра, як показано нижче,

 public static Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    StringBuilder sb = new StringBuilder();
    try {
    return input.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                    .map(ele->{
                        if (!StringUtils.isNumeric(ele)) {
                            sb.append(e.getKey()); //add exception key
                            throw new NumberFormatException();
                        }
                        return Long.valueOf(ele);
                    })
                    .collect(Collectors.toList())));
} catch (NumberFormatException nfe) {
    System.out.println("Exception key "+sb);
    return Collections.emptyMap();
}
}

Сподіваюся, це допомагає.


0

Можливо, ви можете написати допоміжний метод, який зможе перевірити чисельність у рядку та відфільтрувати їх з потоку, а також нульові значення, а потім нарешті зібрати на Map.

// StringUtils.java
public static boolean isNumeric(String string) {
    try {
        Long.parseLong(string);
        return true;
    } catch(NumberFormatException e) {
        return false;
    }
}

Це подбає про все.

І використовуйте це у своєму потоці.

Map<String, List<Long>> newMap = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> mapToLongValues(entry.getValue())));

public List<Long> mapToLongValues(List<String> strs) {
    return strs.stream()
        .filter(Objects::nonNull)
        .filter(StringUtils::isNumeric)
        .map(Long::valueOf)
        .collect(Collectors.toList());
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.