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也通俗易懂。但也存在一些坑。
两部分:

  1. format
    LocalDate 和 LocalTime 必须和 formatter 保持一致类型,不然会报错, 例如:

    LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

    或者:

    LocalTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
  2. parse
    和format同样的问题, 在string转 时间日期的时候, 也需要注意 一一对应, 日期只能转日期, 不可以转时间日期, 例如 :

    LocalDateTime.parse("2019-01-29", DateTimeFormatter.ofPattern("yyyy-MM-dd"));

为此, 写了一个工具类来规避这些问题
时间日期工具类, 只针对java8 时间日期


Crayon2f_
24 声望1 粉丝

每天进步一点点