【问题标题】:How to remove null or empty list from an "unmodifiable" map如何从“不可修改”地图中删除空列表或空列表
【发布时间】:2019-08-04 18:57:56
【问题描述】:

我有一张不可修改的地图 (Map<String, Object>)。这包含一个键值对,其中值应该是一个列表,但对于特定键,值是一个空列表。

如何从地图中删除该空列表并返回?

我已经使用谓词来完成它,该谓词检查值是否是集合的实例,然后检查 Collections.isNotEmpty(..) 是否。

我想知道有没有比这更好的方法? (在 Java 8 中)

public class StudentTest {

    Predicate<Object> isCollectionNotEmpty = input -> {
        if (input instanceof Collection) {
            return CommonCollectionUtils.isNotEmpty((Collection<?>)input);
        }
        return true;
    };
    Predicate<Object> isObjectNotNull = input -> Objects.nonNull(input);

    public static void main(String[] args) {

        StudentTest s = new StudentTest();
        final Map<String, Object> map1 = new HashMap<>();
        map1.put("student_batch_size", 200);
        map1.put("student_avg_height", 172);
        map1.put("Student_names", Collections.emptyList());

        final Map<String, Object> finalMap = s.getFinalMap(Collections.unmodifiableMap(map1));

        System.out.println(finalMap);
    }

    private Map<String, Object> getFinalMap(final Map<String, Object> inputMap) {
        final Map<String, Object> resultMap = new HashMap<>();
        //inputMap.values().remove(Collections.emptyList());
        resultMap.putAll(inputMap.entrySet().stream()
            .filter(entry -> isObjectNotNull.and(isCollectionNotEmpty).test(entry.getValue()))
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
        return resultMap;
    }

}

预期输出:

{student_avg_height=172, student_batch_size=200}

【问题讨论】:

  • 如果instanceof 检查失败,您可以取消第二个Predicate 并返回false
  • 能否详细说明。如果它是一个空列表,instanceof 应该返回 true。
  • putAll 的使用只是奇怪collect 直接toMap - 你所做的只是增加不必要的认知负荷并创建不必要的对象(中间Map)。
  • 是的 - return inputMap.entrySet()...collect(toMap(...))。您保存了一些冗余代码和 Map 创建。

标签: java java-8


【解决方案1】:

Collection#removeIf 可能是一个不错的选择。

private Map<String, Object> getFinalMap(final Map<String, Object> inputMap) {
    final Map<String, Object> resultMap = new HashMap<>(inputMap);
    resultMap.entrySet().removeIf(e -> e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty());

    return resultMap;
}

如果基于流的方法对您更有吸引力

private Map<String, Object> getFinalMap(final Map<String, Object> inputMap) {
    return inputMap.entrySet()
        .stream()
        .filter(e -> !(e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

更新

如果你想过滤掉空对象而不考虑它们的类型,这里对上面提到的方法做一些细微的改动

1) 删除如果

resultMap.entrySet()
    .removeIf(e -> Objects.isNull(e.getValue()) || 
                   (e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty());

2) 基于流

.filter(e -> Objects.nonNull(e.getValue()))
.filter(e -> !(e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty()))

【讨论】:

  • @HadiJ .removeIf(e -&gt; Objects.isNull(e.getValue()) || e.getValue() instanceof Collection &amp;&amp; ((Collection) e.getValue()).isEmpty())
  • @RoyalTiger 它没有,但如果它不是一个集合,它不会被过滤掉。所以需要一个条件Objects.isNull(e.getValue())...
  • @RoyalTiger 我们使用instanceof 主要是在表达式的第二部分进行强制转换。我想分离两个条件(任何类型的空对象和空集合)
  • @RoyalTiger (我说的是作为字段的谓词) 这还不错,但是当条件像Objects.nonNull(input) 一样简单时它并不是特别有用(可能是@ 987654331@,顺便说一句)
  • @Andrew Tobilko:是的,实际上我想使用谓词,以便可以在多个地方使用。您添加一个公共类并将所有谓词移至它是正确的。这是个好主意。因此,如果有这样的场景,我们可以使用谓词。感谢您的解释。
【解决方案2】:

我认为您使用Object 作为Map 中的值的方法是有问题的。您不能完全控制类型安全。我会考虑用反映单个学生批次属性的对象替换学生 Map

class StudentBatch {
   int size;
   int avgHeight;
   Collection<String> names;

   boolean hasValidNames() {
       return names != null && !names.isEmpty();
   }
}

如您所见,我已将 hasValidNames 谓词放在对象上,使其可重用并允许简化您尝试优化的条件。

  List<StudentBatch> batchList = new ArrayList<>();
  batchList.stream().filter(StudentBatch::hasValidNames).collect(Collectors.toList());

【讨论】:

  • 对,但问题是这个 Map 即将发出 REST POST 请求。它是一个被转换为 Map 的 Json。此 json 可以包含如下任何内容: { "student_batch_size":200, "student_avg_height":170, "Student_names":[ "Ryan", "Nick", "Balbowisky" ] }
  • 好的。我得到它。您可以很容易地将 json 文档映射到一个对象。大多数 json 解析器包括。 Gson 和 Jackson 支持这一点。也许这适用于您的情况。
【解决方案3】:

instanceof 也检查 no nullity,因此无需显式设置它,并且您无需创建新的 HashMap 即使您流式传输的 Map 不可修改,因为流不会修改它直接但创建一个新地图:

final Map<String, Object> finalMap =
     map
    .entry()
    .stream()
    .filter(e-> !isEmptyCollection(e))
    .collect(toMap(Entry::getKey, Entry::getValue);


public void boolean isEmptyCollection(Entry<String, Object> entry){     
    return entry.getValue() instanceof Collection && ((Collection<?>) entry.getValue()).isEmpty();
}

【讨论】:

  • 不需要isEmptyCollection 可能是这样的...filter(entry -&gt; entry.getValue() instanceof Collection &amp;&amp; ((Collection&lt;?&gt;) entry.getValue()).isEmpty())... OP 也想检查空对象。 isObjectNotNull
  • @Boris the spider :我想你建议使用三元组。篇幅长,不一定好读。
  • @Hadi J 不需要?这是一个可读性问题。而且您提供的过滤器不正确,因为我们希望保持相反的状态。我们应该用否定运算符包围整个表达式。也不是很可读。关于 not null,这不是必需的,感谢instanceof 检查
猜你喜欢
  • 2015-09-20
  • 1970-01-01
  • 2017-11-02
  • 2022-11-01
  • 2021-07-11
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 2021-08-21
相关资源
最近更新 更多