Obsługa wyjątków za pomocą strumieni

10

Mam Map<String,List<String>>i chcę, aby się zmieniło, Map<String,List<Long>>ponieważ każdy Stringna liście reprezentuje 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()))
               );

Moim głównym problemem jest to, że każdy z nich Stringmoże nie przedstawiać poprawnie Long: może być jakiś problem.Long::valueOfmoże zgłaszać wyjątki. W takim przypadku chcę zwrócić wartość zerową lub pustąMap<String,List<Long>>

Ponieważ chcę iterować po tej outputmapie. Ale nie mogę zaakceptować żadnej konwersji błędu; ani jednego. Masz pomysł, jak mogę zwrócić puste wyjście w przypadku niepoprawnego ciągu -> Długa konwersja?

AntonBoarf
źródło
Zgadzam się z rozwiązaniem Naman, ale niestety w bloku catch nie udało mi się pobrać klucza (Entry :: getKey), dla którego String -> Long konwersja jest niepoprawna
AntonBoarf
Podobna dyskusja tutaj: ciąg do int - prawdopodobnie złe dane muszą unikać wyjątków, w których ostatecznie postanowiłem sprawdzić wstępnie za pomocą wyrażenia regularnego (dokumenty parseLong używają tych samych reguł parsowania i prawdopodobnie chcesz zwrócić wartość LongStreamjeśli planujesz usunąć emptywyniki)
AjahnCharles
Przepraszam, źle zrozumiałem. Myślałem, że chcesz zwrócić pojedynczy wpis jako pusty / pusty; ale teraz myślę, że masz na myśli całą mapę!
AjahnCharles
1
Nie jest do końca jasne, o co chodzi - chcesz zwrócić pustą mapę w przypadku błędu, ale nadal wydrukować „klucz” tam, gdzie błąd pojawił się na konsoli? Mam na myśli, że informacje o kontekście, w którym pojawił się wyjątek, są zwykle przenoszone w górę stosu wywołań w wyjątku. Niezależnie od tego: konkretnie pytałeś o strumienie, ale zdecydowanie zalecam unikanie zagnieżdżonych wywołań „zbieraj”. Ludzie, którzy będą musieli to później utrzymywać (a być może będzie to Ty w przyszłości !), Będą się zastanawiać, co h ... zrobiliście tam. Przynajmniej wprowadź odpowiednio nazwane metody pomocnicze.
Marco13

Odpowiedzi:

4

Co powiesz na wyraźny catchwyjątek:

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();
    }
}
Naman
źródło
ok brzmi dobrze ... ale w catch (nfe) chciałbym odzyskać określoną wartość klucza (Entry :: getKey) i niepoprawny ciąg znaków, dla którego się nie udaje, więc mogę zalogować się dokładnie tam, gdzie idzie źle. Czy to możliwe ?
AntonBoarf,
@AntonBoarf Jeśli chcesz tylko zalogować klucz, dla którego nie udało się go przeanalizować ciągu, użyjnfe.getMessage()
Naman
1
@AntonBoarf komunikat wyjątku będzie zawierał zniekształcony ciąg wejściowy. Aby uzyskać klucz odpowiedzialny, przeprowadziłbym jawne wyszukiwanie, tylko gdy wystąpił wyjątek, np.input.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger
@Uchwyt. Dzięki ... To wydaje się skomplikowane ... Zastanawiam się, czy użycie standardowego dla pętli Java5 nie jest lepsze w moim przypadku
AntonBoarf
@AntonBoarf po prostu zaimplementuj oba i porównaj…
Holger
3

Osobiście lubię podawać dane Optionalwejściowe dotyczące analizowania liczb:

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

Następnie, używając własnego kodu (i ignorując złe dane wejściowe):

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()))
               );

Dodatkowo rozważ metodę pomocniczą, aby uczynić to bardziej zwięzłym:

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());
}

Następnie możesz filtrować wyniki w kolektorze strumienia:

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

Możesz także zachować puste Optionalobiekty na swoich listach, a następnie porównując ich indeks w nowym List<Optional<Long>>(zamiast List<Long>) z oryginałem List<String>, możesz znaleźć ciąg, który spowodował błędne dane wejściowe. Możesz także po prostu zalogować się do tych awariiMyClass#parseLong

Jednakże, jeśli pragnieniem jest, aby nie działać na każdym nieudanym wejściu w ogóle, to otaczająca cały strumień w co próbujesz złapać za odpowiedź Naman (za) jest droga wziąłbym.

Łobuz
źródło
2

Możesz utworzyć StringBuilderklucz for z wyjątkiem i sprawdzić, czy elema wartość numeryczną, jak poniżej,

 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();
}
}

Mam nadzieję, że to pomoże.

Code_Mode
źródło
0

Być może możesz napisać metodę pomocniczą, która może sprawdzić wartości liczbowe w ciągu i odfiltrować je ze strumienia, a także wartości zerowe, a następnie zebrać do mapy.

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

To załatwi wszystko.

I użyj tego w swoim strumieniu.

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());
}
TheTechMaddy
źródło