background

Many times we need to use an ordered Map, we need to use a LinkedHashMap. What if we need to do a filter on the LinkedHashMap? Is it possible to use the Filter of Stream? What seems like a simple question can reveal a lot when unfolded. I start with the conclusion and then think differently using a small requirement that the LinkedHashMap filter value contains 1.

First give the conclusion:

  • Don’t write code that everyone on the team thinks is obscure
  • Business-independent technical duplicates that are common to different business code should be extracted
  • Collectors. ToMap’s default collection result is not LinkedHashMap

Error – Use Stream Filter directly

A normal Stream and Filter can be written as follows: Use a common MapStream to Filter and then use a Collectors. ToMap. The result is an unordered Map.

Map<String, String> map = new LinkedHashMap<>(); map.put("a", "a1"); map.put("aa", "a2"); map.put("b", "b1"); map.put("bb", "b2"); System.out.println(map); // Filter out those containing 1, Filter (e -> LLDB etValue().contains("1")).collect(Collectors. ToMap (e -> LLDB etKey(), e -> e.getValue())); System.out.println(map);Copy the code

What we want is a LinkedHashMap. Can we force a cast to LinkedHashMap?

LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("a", "a1");
map.put("aa", "a2");
map.put("b", "b1");
map.put("bb", "b2");
System.out.println(map);
map = (LinkedHashMap) map.entrySet().stream()
        .filter(e -> e.getValue().contains("1"))
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
System.out.println(map);
Copy the code

An error message is displayed:

{a=a1, aa=a2, b=b1, bb=b2}
Exception in thread "main" java.lang.ClassCastException: class java.util.HashMap cannot be cast to class java.util.LinkedHashMap (java.util.HashMap and java.util.LinkedHashMap are in module java.base of loader 'bootstrap')
	at fly.sample.collection.LinkedHashMapFilterTest.test2(LinkedHashMapFilterTest.java:40)
	at fly.sample.collection.LinkedHashMapFilterTest.main(LinkedHashMapFilterTest.java:10)
Copy the code

The reason is that toMap’s result is not the original LinkedHashMap, but the newly created HashMap

public static <T, K, U> Collector<T, ? , Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) { return new CollectorImpl<>(HashMap::new, uniqKeysMapAccumulator(keyMapper, valueMapper), uniqKeysMapMerger(), CH_ID); }Copy the code

The correct way to write Sream

LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("a", "a1");
map.put("aa", "a2");
map.put("b", "b1");
map.put("bb", "b2");
System.out.println(map)
map = map.entrySet()
        .stream().filter(e -> e.getValue().contains("1")).collect(Collectors.toMap(e -> e.getKey(), v -> v.getValue(),
                (oldValue, newValue) -> oldValue, LinkedHashMap::new));
System.out.println("4" + map);
Copy the code

It can execute normally and the output is still in order

{a=a1, aa=a2, b=b1, bb=b2}
4{a=a1, b=b1}
Copy the code

The legibility of the correct form is poor, and many people complain why they don’t use the original form. So I recommend myself to extract a more readable method, so that not only is it more readable, but the filtering LinkedHashMap code can be shared between different businesses.

General method extraction

The first general writing method echoes the part of people who don’t like obscure writing. They can choose the following method:

public static <K, V> LinkedHashMap<K, V> filterLinkedHashMap(LinkedHashMap<K, V> linkedHashMap, Predicate<Map.Entry<K, V>> predicate) {
    LinkedHashMap resultMap = new LinkedHashMap<>();
    for (Map.Entry entry : linkedHashMap.entrySet()) {
        if (predicate.test(entry)) {
            resultMap.put(entry.getKey(), entry.getValue());
        }
    }
    return resultMap;
}
Copy the code

The legibility can be greatly improved by simply using the filterLinkedHashMap method as follows.

LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("a", "a1");
map.put("aa", "a2");
map.put("b", "b1");
map.put("bb", "b2");
System.out.println(map)
map = filterLinkedHashMap(map, e -> e.getValue().contains("1"));
System.out.println(map);
Copy the code

Of course, you can also choose to write the implementation of filterLinkedHashMap in an obscure way, since it is not visible to the method user.