1. Stream 的 collect toMap 方法 vulueMapper 返回值不能为空
List<User> list = new ArrayList<>();
list.add(new User("1", 23));
list.add(new User("2", 23));
list.add(new User("3", null));
list.add(new User("4", 23));
list.add(new User("5", null));
Map<String, Integer> collect = list.stream().collect(Collectors.toMap(
User::getName, User::getAge
));
jdk1.8 版本:
此时就会报NPE,原因出在 java.util.Map 类中 的merge方法,代码如下:
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
1.9版本,依旧会报NPE,原因出在Collectors的组装toMap的累加器uniqKeysMapAccumulator方法:
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);
}
private static <T, K, V>
BiConsumer<Map<K, V>, T> uniqKeysMapAccumulator(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper) {
return (map, element) -> {
K k = keyMapper.apply(element);
V v = Objects.requireNonNull(valueMapper.apply(element));
V u = map.putIfAbsent(k, v);
if (u != null) throw duplicateKeyException(k, u, v);
};
}
解决方案:在收集到map之前过滤掉valueMapper为null的元素:
Map<String, Integer> collect = list.stream().filter(ths -> Objects.nonNull(ths.getAge())).collect(Collectors.toMap(
User::getName, User::getAge
));
2. 依旧是Stream 的 collect toMap 方法,当key可能存在重复时候,必须指定mergeFunction,否则会报 “Duplicate key ...”
List<User> list = new ArrayList<>();
list.add(new User("1", 23));
list.add(new User("2", 23));
list.add(new User("2", 23));
Map<String, Integer> collect = list.stream().collect(Collectors.toMap(
User::getName, User::getAge
));
解决方案: 在key可能重复的,或者不确定key是否重复的时候,均需要指定mergeFunction,代码如下:
Map<String, Integer> collect = list.stream().collect(Collectors.toMap(
User::getName, User::getAge, (pre,next) -> next
));
//当出现重复key时候,后一个覆盖前一个。
针对以上两个问题,重写了toMap :
collector: toMap
CollectorKit
3. Optional中的orElse(),尽量传入变量,不要传方法,如果是方法请使用orElseGet()
举例:
@Test
void testOrElse() {
System.out.println(method("23"));
System.out.println(method("aaa"));
}
private Integer method(String param) {
return Optional.ofNullable(param)
.filter(StringUtils::isNotEmpty)
.filter(string -> string.matches("[\\d]+"))
.map(Integer::parseInt)
.orElse(this.getDefaultInteger());
}
private Integer getDefaultInteger() {
System.out.println("entry default");
return 0;
}
/* 输出:
entry default
23
entry default
0
*/
可以看到,虽然最终结果是我们想要的,但是无论传入的参数是否命中 Optional,orElse里面的方法都执行了,这种情况如果在具体业务逻辑中,那么可能会出现问题,因为orElse中的值是未命中filter条件的所产生的的值,但是在前面的filter都符合的情况下仍然执行了;再比如:
private List<User> method(User param) {
return Optional.ofNullable(param)
.map(User::getUserList)
.filter(CollectionUtils::isNotEmpty)
.orElse(new ArrayList<>());
}
和上面一样,当我符合条件时,内存中仍旧会新new一个ArrayList,无形中占用了不必要的内存;
原因:
public T orElse(T other) {
return value != null ? value : other;
}
其实本身把一个方法的返回值当做参数传递时,这个方法必然会执行,所以只要你使用了,orElse,那么你传入的方法就会被执行,与前面是否符合条件无关!!
其实OrElse的初衷就是让你传入一个变量,或者静态值,或者null;
如果想传递一个函数,请使用orElseGet() :
private List<User> method(User param) {
return Optional.ofNullable(param)
.map(User::getUserList)
.filter(CollectionUtils::isNotEmpty)
.orElseGet(ArrayList::new);
}
private Integer method(String param) {
return Optional.ofNullable(param)
.filter(StringUtils::isNotEmpty)
.filter(string -> string.matches("[\\d]+"))
.map(Integer::parseInt)
.orElseGet(this::getDefaultInteger);
}
4. java8时间日期 格式化与 parse 的问题
java8 以前 说起时间日期使用最多的是 java.util.Date 和 java.util.Calendar (非常之麻烦)
java8 以后 出现了新的时间日期包 在java.time下,且都是不可变,线程安全的,用起来十分方便,api也通俗易懂。但也存在一些坑。
两部分:
-
format
LocalDate 和 LocalTime 必须和 formatter 保持一致类型,不然会报错, 例如:LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
或者:
LocalTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
-
parse
和format同样的问题, 在string转 时间日期的时候, 也需要注意 一一对应, 日期只能转日期, 不可以转时间日期, 例如 :LocalDateTime.parse("2019-01-29", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
为此, 写了一个工具类来规避这些问题
时间日期工具类, 只针对java8 时间日期
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。