处理集合中的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}