Manejo de excepciones con flujos

10

Tengo un Map<String,List<String>>y quiero que se convierta Map<String,List<Long>>porque cada uno Stringen la lista representa un 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()))
               );

Mi principal problema es que cada uno Stringpuede no representar correctamente a Long; Puede haber algún problema. Long::valueOfpuede plantear excepciones. Si este es el caso, quiero devolver un valor nulo o vacíoMap<String,List<Long>>

Porque quiero iterar después sobre este outputmapa. Pero no puedo aceptar ninguna conversión de error; Ni siquiera uno solo. ¿Alguna idea de cómo puedo devolver una salida vacía en caso de una cadena incorrecta -> Conversión larga?

AntonBoarf
fuente
Estoy de acuerdo con la solución de Naman, pero desafortunadamente en el bloque catch, no puedo recuperar la clave (Entry :: getKey) para la cual la Cadena -> Conversión larga es incorrecta
AntonBoarf
Discusión similar aquí: Cadena a int: es probable que los datos incorrectos necesiten evitar excepciones en las que eventualmente decidí verificar previamente con regex (los documentos parseLong usan las mismas reglas de análisis y probablemente desee devolver un LongStreamsi planea eliminar los emptyresultados)
AjahnCharles
Lo siento, he entendido mal. Pensé que querías devolver una sola entrada como vacía / nula; pero ahora creo que te refieres a todo el mapa!
AjahnCharles
1
No está del todo claro cuál es el punto principal: ¿desea devolver un mapa vacío en caso de error, pero aún así imprimir la "clave" donde apareció el error en la consola? Quiero decir, la información sobre el contexto donde apareció la excepción generalmente se transporta a la pila de llamadas en la excepción. Independientemente de eso: Usted preguntó específicamente sobre las transmisiones, pero le recomiendo encarecidamente evitar las llamadas anidadas de "recopilación". Las personas que tienen que mantener eso más tarde (¡y esto podría ser tu futuro !) Se preguntarán qué demonios hiciste allí. Al menos, introduce algunos métodos auxiliares con nombre apropiado.
Marco13

Respuestas:

4

¿Qué tal un explícito catchsobre la excepción:

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
fuente
ok suena bien ... pero en el catch (nfe) me gustaría recuperar el valor específico de la clave (Entry :: getKey) y la cadena incorrecta para la que falla, por lo que puedo registrar con precisión dónde va mal. Es posible ?
AntonBoarf
@AntonBoarf Si solo desea registrar la clave, para la cual no se pudo analizar la Cadena, usenfe.getMessage()
Naman el
1
@AntonBoarf el mensaje de la excepción contendrá la cadena de entrada con formato incorrecto. Para obtener la clave responsable, haría una búsqueda explícita, solo cuando ocurriera la excepción, por ejemploinput.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger
@Poseedor. Gracias ... Eso parece complicado ... Me pregunto si usar standart para loop Java5 no es mejor en mi caso
AntonBoarf
@AntonBoarf solo implementa ambos y compara ...
Holger
3

Personalmente, me gusta proporcionar Optionalinformación sobre el análisis de números:

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

Luego, usando su propio código (e ignorando la entrada incorrecta):

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

Además, considere un método auxiliar para hacer esto más conciso:

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

Luego puede filtrar los resultados en el recopilador de su transmisión:

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

También puede mantener los Optionalobjetos vacíos en sus listas, y luego al comparar su índice en el nuevo List<Optional<Long>>(en lugar de List<Long>) con el original List<String>, puede encontrar la cadena que causó cualquier entrada errónea. También puede simplemente registrar estas fallas enMyClass#parseLong

Sin embargo, si su deseo es no operar con ninguna entrada incorrecta, entonces rodear todo el flujo en lo que está tratando de atrapar (según la respuesta de Naman) es la ruta que tomaría.

Pícaro
fuente
2

Puede crear una StringBuilderclave for con excepción y verificar si elees numérica como se muestra a continuación,

 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 eso ayude.

Code_Mode
fuente
0

Puede ser que pueda escribir un método auxiliar que pueda verificar los valores numéricos en la cadena y filtrarlos de la secuencia y también los valores nulos y luego finalmente recopilarlos en el Mapa.

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

Esto se encargará de todo.

Y usa esto en tu transmisión.

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
fuente