Обработка исключений с помощью потоков


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не может правильно представлять a Long; может быть какая-то проблема. Long::valueOfможет поднять исключения. Если это так, я хочу вернуть нулевой или пустойMap<String,List<Long>>

Потому что я хочу повторить эту outputкарту. Но я не могу принять любую ошибку преобразования; ни одного. Любая идея о том, как я могу вернуть пустой вывод в случае неправильного преобразования String -> Long?


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

Подобное обсуждение здесь: строка с int - вероятно, неверные данные должны избегать исключений, когда я в конечном итоге решил предварительно проверить с помощью регулярных выражений (документы parseLong используют те же правила синтаксического анализа, и вы, вероятно, захотите вернуть a, 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();
    }
}

хорошо звучит хорошо ... но в catch (nfe) я хотел бы получить конкретное значение ключа (Entry :: getKey) и неверную строку, для которой он не работает, поэтому я могу точно регистрировать, где он идет не так. Является ли это возможным ?
АнтонБоарф

@AntonBoarf Если вы просто хотите зарегистрировать ключ, для которого не удалось проанализировать строку, используйтеnfe.getMessage()
Naman

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 просто реализуй оба и сравнивай ...
Хольгер,

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

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

// 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.