Manipulando exceções com fluxos

10

Eu tenho um Map<String,List<String>>e quero que ele se transforme Map<String,List<Long>>porque cada um Stringna lista representa um 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()))
               );

Meu principal problema é que cada um Stringpode não representar corretamente a Long; pode haver algum problema. Long::valueOfpode gerar exceções. Se for esse o caso, desejo retornar um valor nulo ou vazioMap<String,List<Long>>

Porque eu quero iterar depois neste outputmapa. Mas não posso aceitar nenhuma conversão de erro; nem mesmo um. Alguma idéia de como eu posso retornar uma saída vazia em caso de String incorreta -> Conversão longa?

AntonBoarf
fonte
Concordo com solução Naman mas infelizmente no bloco catch, não consigo recuperar a chave (Entry :: getKey) para o qual o String -> conversão Long é incorreta
AntonBoarf
Discussão semelhante aqui: Cadeia de caracteres para int - provavelmente os dados incorretos precisam evitar exceções, onde finalmente decidi fazer uma pré-verificação com o regex (os documentos parseLong usam as mesmas regras de análise e você provavelmente deseja retornar um LongStreamse planeja remover emptyresultados)
AjahnCharles
Desculpe, eu não entendi. Eu pensei que você pretendia retornar uma única entrada como vazia / nula; mas agora acho que você quer dizer o mapa inteiro!
AjahnCharles
11
Não está totalmente claro qual é o ponto principal - você deseja retornar um mapa vazio em caso de erro, mas ainda imprime a "chave" onde o erro apareceu no console? Quero dizer, informações sobre o contexto em que a exceção apareceu são geralmente transportadas para cima da pilha de chamadas na exceção. Independentemente disso: você perguntou especificamente sobre fluxos, mas eu recomendo fortemente evitar chamadas "coletadas" aninhadas. As pessoas que precisam sustentar isso mais tarde (e isso pode muito bem ser o seu futuro !) Vão se perguntar o que diabos você fez lá. Pelo menos, introduza alguns métodos auxiliares nomeados corretamente.
Marco13

Respostas:

4

Que tal um explícito catchsobre a exceção:

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
fonte
ok parece bom ... mas no catch (nfe) eu gostaria de recuperar o valor específico da chave (Entry :: getKey) e a string incorreta para a qual ela falha, para que eu possa registrar com precisão onde está errado. É possível ?
AntonBoarf 2/10/19
@AntonBoarf Se você quiser apenas para registrar a chave, para o qual ele não conseguiu analisar a cadeia, usonfe.getMessage()
Naman
11
@AntonBoarf a mensagem da exceção conterá a sequência de entrada malformada. Para obter a chave responsável, que faria uma pesquisa explícita, somente quando a exceção ocorreu, por exemploinput.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger
@Suporte. Obrigado ... Isso parece complicado ... Eu estou querendo saber se usando Standart loop for Java5 não é melhor no meu caso
AntonBoarf
@AntonBoarf apenas implementar ambos e comparar ...
Holger
3

Pessoalmente, gosto de fornecer uma Optionalentrada sobre a análise de números:

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

Em seguida, usando seu próprio código (e ignorando a entrada incorreta):

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

Além disso, considere um método auxiliar para tornar isso mais sucinto:

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

Em seguida, você pode filtrar os resultados no coletor do seu stream:

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

Você também pode manter os Optionalobjetos vazios em suas listas e, comparando o índice deles no novo List<Optional<Long>>(em vez de List<Long>) com o original List<String>, pode encontrar a sequência que causou entradas incorretas. Você também pode simplesmente registrar essas falhas noMyClass#parseLong

No entanto, se seu desejo é não funcionar com nenhuma entrada ruim, cercar todo o fluxo no que você está tentando capturar (de acordo com a resposta de Naman) é o caminho que eu seguiria.

Vampiro
fonte
2

Você pode criar uma StringBuilderchave for com exceção e verificar se eleé numérico como abaixo,

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

Espero que ajude.

Code_Mode
fonte
0

Pode ser que você possa escrever um método auxiliar que possa procurar numéricos na sequência e filtrá-los do fluxo e também valores nulos e, finalmente, coletar no Mapa.

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

Isso vai cuidar de tudo.

E use isso no seu stream.

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
fonte