Estoy creando una utilidad de validación de formulario muy simple para una pantalla de registro, y me encuentro con un comportamiento inesperado relacionado con LinkedHashMap
y una secuencia creada a partir de su entrySet
.
Estoy almacenando resultados de validación en un LinkedHashMap
, con el siguiente orden de declaraciones:
Map<ValidationResult.SignUpField, Boolean> fieldStatuses = new LinkedHashMap<>(); fieldStatuses.put(EMAIL, isValidEmail(emailAddress)); fieldStatuses.put(USERNAME, isValidUsername(username)); fieldStatuses.put(BIRTHDAY, isValidBirthday(birthday)); fieldStatuses.put(PASSWORD, isValidPassword(password)); fieldStatuses.put(CONFIRM_PASSWORD, password.equals(confirmedPassword)); List<ValidationEntry> invalidFields = aggregateInvalidFields(fieldStatuses);
Una iteración en particular produce que todos los campos anteriores no sean válidos, excepto "confirmar contraseña". Usando un bucle for simple sobre el conjunto de entradas y omitiendo los resultados válidos, los resultados no válidos aparecen en el siguiente orden:
Luego intenté hacer uso del subconjunto de Stream API disponibles en Android (apuntando a la versión 25 con un mínimo de 19, de ahí la falta de Collectors.toMap()
):
private static List<ValidationEntry> aggregateInvalidFields(Map<ValidationResult.SignUpField, Boolean> fields) { List<ValidationEntry> invalidFields = new ArrayList<>(); fields.entrySet() .stream() .filter(entry -> !entry.getValue()) .forEachOrdered(entry -> { ValidationResult.SignUpField field = entry.getKey(); invalidFields.add(new ValidationEntry(field, lookUpErrorCode(field))); }); return invalidFields; }
Pero ese código produce el siguiente orden:
¿Qué está sucediendo exactamente aquí? ¿Por qué el resultado de la transmisión no respeta el orden de inserción de LinkedHashMap
? Tenga en cuenta que si cambio forEachOrdered
con forEach
, todavía no está ordenado por inserción.
Este comportamiento es un error conocido en la implementación de Android 7.0/7.1 de LinkedHashMap.
Los divisores de vistas de colección de LinkedHashMap para entrySet
, values
y keySet
informan correctamente que están ORDERED
, pero en realidad no lo están porque la implementación del divisor de la clase principal (HashMap) se usa internamente.
Este comportamiento ya se ha documentado en el Javadoc y allí también se proponen soluciones alternativas.
La solución se comprometió el 16 de agosto de 2016 y aparecerá en la próxima versión de Android.
Para aclarar: Google se dio cuenta de este error al principio en 2017-01, por lo que la "corrección" mencionada anteriormente fue una solución accidental. Si hubieran conocido este problema antes, la resolución se habría incluido en 7.1