在流中,LinkedHashMap的entrySet顺序没有被保留(Android)

9
我正在为注册界面创建一个非常简单的表单验证工具,但是在使用LinkedHashMap及其entrySet生成的流时,我遇到了一些意外的行为。
我将验证结果存储在LinkedHashMap中,并按以下语句排序:
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);

一次迭代会使得上述所有字段无效,除了“确认密码”之外。通过简单的for循环遍历条目集并省略有效结果,无效结果按以下顺序出现:

  • 电子邮件
  • 用户名
  • 生日
  • 密码

然后我尝试利用Android上可用的Stream API子集(针对版本25,最小为19,因此缺少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;
}

但是这段代码的输出结果却是:

  • 生日
  • 密码
  • 用户名
  • 邮箱

这里到底发生了什么,为什么流操作的结果不遵循 LinkedHashMap 的插入顺序?请注意,如果我将forEachOrdered替换为forEach,它仍然不是按插入顺序排序的。


你能告诉我们你在使用什么流API吗? - ByeBye
我正在使用Android上可用的Java Stream API子集。 - Chris
fieldsfieldStatuses是同一个映射吗? - Ole V.V.
1
那你为什么不使用EnumMap呢? - Ole V.V.
1
你能否尝试将其简化为 [mcve]?很难知道你是否遗漏了一些重要的东西。 - shmosel
显示剩余13条评论
1个回答

8
这是Android 7.0 / 7.1版本的LinkedHashMap中已知的bug。 LinkedHashMap的集合视图的entrySet,values和keySet的分割器正确地报告它们是有序的,但实际上它们并不是,因为内部使用了父类(HashMap)的分割器实现。 这种行为已经在Javadoc中得到了记录,并提出了解决方法。修复已于2016-08-16提交,并将出现在下一个Android版本中。

澄清一下:Google最初在2017年1月就意识到了这个漏洞,因此上述“修复”是一个意外的修复。如果他们早些知道这个问题,解决方案将被包含在7.1中。


2
所以我不是疯了!感谢您的洞察力。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接