处理集合中的null

处理集合中的null

使用Optional或Stream API来处理集合中的null


  1/**
  2 * {@link java.util.Optional} and
  3 * {@link com.google.common.base.Optional}
  4 * offer useful methods to deal with
  5 * <b>{@code null}</b> troubles.
  6 *
  7 * @author wangy
  8 * @date 2021.01.26/0026 14:13
  9 */
 10@SuppressWarnings({ "raw", "unused" })
 11public class SuckNull {
 12
 13    private static Object[] OBA = new Object[] {
 14            1,
 15            "2",
 16            null,
 17            new SuckNull() };
 18
 19    /**
 20     * <b>!不要</b>直接使用<code>Arrays.asList(OBA)</code>,
 21     * 这样做无法推断List的类型。
 22     * <p>
 23     * 会导致后续使用{@link Optional}对集合元素进行操作时抛出
 24     * {@link UnsupportedOperationException}
 25     */
 26    private static List<Object> someList = new ArrayList<>(
 27            Arrays.asList(OBA));
 28
 29    private static Set<Object> someHashSet = new HashSet<>(
 30            Arrays.asList(OBA));
 31
 32    private static Map<Object, Object> someMap = new HashMap<>() {
 33        {
 34            Key key;
 35            put(key = new Key(), new Value(key));
 36            put(key = new Key(), new Value(key));
 37            put(null, new Value(null));
 38            put(key = new Key(), new Value(key));
 39            put(key = new Key(), null);
 40        }
 41    };
 42
 43    /**
 44     * the <b>guava</b> also offer
 45     * {@link com.google.common.base.Optional}
 46     * and {@link com.google.common.base.Objects}
 47     * for basic Object operations.
 48     * <p>
 49     *
 50     * @see java.util.Optional
 51     * @see java.util.Objects
 52     */
 53    static void listOption() {
 54        // non-null elements in someList
 55        // usage of Optional in jdk
 56        System.out.println("NULL elements in list: " +
 57                someList
 58                        .stream()
 59                        .filter(e -> !Optional
 60                                .ofNullable(e)
 61                                .isPresent())
 62                        .count());
 63
 64        // use stream filter null element
 65        // usage of Objects in jdk
 66        List<Object> list1 = someList
 67                .stream()
 68                .filter(Objects::nonNull)
 69                .collect(Collectors.toList());
 70        System.out.println("list1: " + list1);
 71
 72        // stream operation does not change origin list
 73        System.out.println("Stream operation did not " +
 74                "modify origin list: " + someList);
 75
 76        // just operate the stream
 77        someList
 78                .stream()
 79                .filter(Objects::nonNull)
 80                .forEach(System.out::print);
 81        System.out.println();
 82
 83        /*
 84         * Stream is much convenient than Optional.
 85         * Different to Stream operation, the Optional operation
 86         * changes the original List
 87         */
 88        List<Object> list2 = Optional
 89                .ofNullable(someList)
 90                .map(l -> {
 91                    l.removeIf(Objects::isNull);
 92                    return l;
 93                })
 94                .get();
 95        System.out.println("list2: " + list2);
 96        System.out.println("someList modified by Optional Operation: "
 97                + someList);
 98
 99        someList.add(null);
100        System.out.println("someList: " + someList);
101
102        // the google guava common utils offer similar operations.
103        List<Object> list3 = fromNullable(someList)
104                .transform(input -> {
105                    input.removeIf(e -> !fromNullable(e).isPresent());
106                    return input;
107                })
108                .get();
109
110        System.out.println("list3: " + list3);
111        System.out.println("someList: " + someList);
112    }
113
114    static void setOption() {
115        // Remove null element from `someHashSet` by using Optional,
116        // and the origin `someHashSet` is modified permanently.
117        Optional.ofNullable(someHashSet)
118                .filter(s -> s.contains(null))
119                .ifPresent(s -> s.removeIf(
120                        o -> !Optional
121                                .ofNullable(o)
122                                .isPresent()));
123        System.out.println(someHashSet);
124    }
125
126    /**
127     * Stream and Optional operation for HashMap
128     */
129    static void mapOption() {
130
131        // use stream to filter null key and null values
132        Map<Key, Value> map = someMap.entrySet().stream()
133                .filter(entry -> Optional
134                        .ofNullable(entry.getKey())
135                        .isPresent()
136                        &&
137                        Optional
138                                .ofNullable(entry.getValue())
139                                .isPresent())
140                .collect(Collectors.toMap(
141                        entry -> (Key) entry.getKey(),
142                        entry -> (Value) entry.getValue()));
143
144        map.forEach(
145                (key, value) -> System.out.println(key + ", " +
146                        value.getValue()));
147
148        // use Optional to get value with `null` key and `null` value
149        Optional<Map<Object, Object>> mapNullKeyValue = Optional
150                .ofNullable(someMap)
151                .filter(m -> m.containsKey(null))
152                .filter(m -> m.containsValue(null));
153        /*
154         * The Optional present shows that `someMap` contains `null` key
155         * and `null` value, but it doesn't work like stream, no change
156         * made to `someMap`.
157         *
158         * So the consumer below will print all elements in `someMap`.
159         */
160        // mapNullKeyValue
161        // .ifPresent(objectObjectMap -> objectObjectMap.forEach((k, v)
162        // -> System.out.println(k + "," + v)));
163
164        final Value[] nullKeyValue = new Value[1];
165        final Key[] nullValueKey = new Key[1];
166        mapNullKeyValue.ifPresent(mp -> {
167            nullKeyValue[0] = (Value) mp.get(null);
168            for (Map.Entry<Object, Object> entry : mp.entrySet()) {
169                if (Objects.isNull(entry.getValue())) {
170                    nullValueKey[0] = (Key) entry.getKey();
171                }
172            }
173        });
174        System.out.println(null + ", " + nullKeyValue[0]);
175        System.out.println(nullValueKey[0] + ", " + null);
176    }
177
178    public static void main(String[] args) {
179
180        // listOption();
181        // setOption();
182        mapOption();
183    }
184
185    static class Key {
186        static int seed = new Random(47).nextInt(100);
187
188        int serial;
189
190        public Key() {
191            this.serial = seed++;
192        }
193
194        @Override
195        public String toString() {
196            return "Key{" + serial + "}";
197        }
198
199        @Override
200        public int hashCode() {
201            return super.hashCode();
202        }
203
204        @Override
205        public boolean equals(Object obj) {
206            return super.equals(obj);
207        }
208    }
209
210    static class Value {
211        private Key key;
212
213        public Value(Key key) {
214            this.key = key;
215        }
216
217        public int getValue() {
218            return key.serial;
219        }
220
221        @Override
222        public String toString() {
223            return Objects.isNull(key) ? null : "Value{" + getValue() + "}";
224        }
225    }
226}