让代码更优雅:JAVA代码不同JDK版本的不同写法
一、概述
JDK不同版本有不同的特性,我刚毕业时候JDK1.8(8)已经出现了,但是大多公司还在用1.6(6),后面陆续出现了9、10、11、12,但是大多公司仍然坚守在1.6版本,逐渐在向1.8靠拢。
本篇讲述下一些1.6之后代码的风格,可以帮助你写出更优雅的代码。
二、try-with-resource
JDK1.7的新特性有很多可取之处,比如
- 泛型实例的创建可以通过类型推断来简化,如 - new HashMap<>不用再写- <>中的类型了
- switch可以使用string 
- 并发工具增强 
- Catch多个异常,单个catch块中处理多个异常 
- try-with-resources. 
- 等等等 
这里只说try-with-resource,是因为用了这么久的try catch,总是对try-with-resource不放心,从第一天写代码就被告知,打开的流一定要关闭,不然就会内存泄漏。所以总是下意识去关闭流,但是try-with-resource却告诉我们,用try-with-resource包围的流,不需要关闭了!
try-with-resources 声明要求其中定义的变量实现 AutoCloseable 接口,这样系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能。
可以查看下AutoCloseable这个接口:
AutoCloseable:
/*
 * @author Josh Bloch
 * @since 1.7
 */
public interface AutoCloseable {
    void close() throws Exception;
}
那么,都有什么类实现了这个接口呢?
如图,太多了,看这些没啥用,一般我们用到的流,都实现了Closeable接口,这个接口也继承了AutoCloseable。
所以,我们就可以这样写:
public static void copy(String src, String dst) {
    try (InputStream in = new FileInputStream(src);
         OutputStream out = new FileOutputStream(dst)) {
        byte[] buff = new byte[1024];
        int n;
        while ((n = in.read(buff)) >= 0) {
            out.write(buff, 0, n);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
InputStream和OutputStream都实现了Closeable接口,也就实现了AutoCloseable这个接口,所以上面的代码就不需要写finally了,也不需要手动close了。注意需要关闭的流,必须写在try(…)中。
三、stream流
stream流是jdk8的新特性,stream流更像一个高级版本的 Iterator,可以很方便地为我们提供各种操作。
3.1 List转另外一个List
3.1.1 map
List<PerformTemplateNorm> normDbList = xxxxxx;
List<String> fieldList = normDbList.stream().map(s -> s.getNormName()).collect(Collectors.toList());
上面的这段代码,将normDbList的normName属性取出来,重整成新的list。
3.1.2 filter
List<PerformTemplateNorm> normDbList = xxxxxx;
List<PerformTemplateNorm> fieldList = normDbList.stream().filter(t -> t.getMainDepartment() == 1).collect(Collectors.toList());
上面的这段代码,将normDbList的mainDepartment属性为1保留,过滤掉为0的,重整成新的list。
3.2 List遍历
List<FirstLineUserShort> userList = xxxx;
userList.stream().forEach(user -> {
    minMap.put(user.getMin(), user);
});
用forEach遍历。
3.3 List排序
List<PerformMonthStaticsRes> resList = xxxxxx;
resList.stream().sorted(Comparator.comparing(s -> Integer.parseInt(s.getMonth())))
                .collect(Collectors.toList());
上面这段代码是根据resList的month属性转成整型后的大小排序。默认升序。
3.4 List转Map
List<PeformMonthNormListReq> normParamList = xxxxx;
Map<Integer, PeformMonthNormListReq> paramMap = normParamList.stream()
        .collect(Collectors.toMap(PeformMonthNormListReq::getNormId, s -> s));
上面这段代码里,PeformMonthNormListReq::getNormId指的是获取normId属性,s->s是指的stream当前遍历的元素当作map的value;
这个方法可能报错(java.lang.IllegalStateException: Duplicate key),因为list中的元素是有可能重复的。toMap有个重载方法,可以传入一个合并的函数来解决key冲突问题:
List<PeformMonthNormListReq> normParamList = xxxxx;
Map<Integer, PeformMonthNormListReq> paramMap = normParamList.stream()
        .collect(Collectors.toMap(PeformMonthNormListReq::getNormId, Function.identity(), s -> s));
这种是后者覆盖前者来解决key重复问题;
3.5 parallelStream
并行流,使用fork-join模式,分线程然后归并结果的一种方法。
public Map<String, LineUserShort> getUserShortInfoMap(List<String> userList) {
    List<List<String>> partsList = Lists.partition(userList, 400);
    Map<String, LineUserShort> minMap = new ConcurrentHashMap<>();
    partsList.parallelStream().forEach(list -> {
        List<LineUserShort> userList = lineUserMapper.selectByEnNameList(list,0);
        userList.stream().forEach(user -> {
            minMap.put(user.getMin(), user);
        });
    });
    return minMap;
}
上面的代码用了一个parallelStream和一个stream,它的逻辑是,先将list按400大小分成多个list,然后每个list并行去数据库中查询信息,查完放到map中;
userList用stream而不用parallelStream是因为:在线程的开销和业务执行时间之间评估下是否有必要使用多线程操作。
四、函数式接口Function
jdk8新特性加入了函数式接口,Function、Predicate等一大堆,除了jdk8中可以使用函数式接口,我们也可以用它来做一些爱做的事情。
如我们先定义一个BiFunction,连接将k和v放到数组中,逗号分隔:
BiFunction<Integer, String, String> func = (k,v) -> String.join(",", Arrays.asList(String.valueOf(k), v));
然后把BiFunction当作参数传递给方法。方法直接调用即可按照BiFunction设定的逻辑去执行。
public String doSomeThing(BiFunction<Integer, String, String> func) {
    return func.apply(1, "heihei");
}
其他函数略不同,用法相同。
Lambda表达式和函数式接口是可以对应起来的,如:
- Function接口,可以通过 - s->xxx来调用;
- BiFunction接口,可以通过 - (k,v)->xxx来调用;
五、jdk8中Collection和Map的一些精简方法
5.1 Collectione的removeIf
Set<LineUserInfo> userList = xxxx;
userList.removeIf(s -> lineUserMapper.selectByName(s.getMin(), 0) != null);
上面这段代码,是过滤掉selectByName能查询到的人员。
5.2 Map的getOrDefault
Map<Integer,String> userMap = xxxx;
String name = userMap.getOrDefault(1, "路人甲");
上面这段代码,是获取id为1的name,如果不存在,返回"路人甲",注意,这里只是给个默认返回值,并不会保存到map里。
5.3 Map的computeIfAbsent
Map<Integer,String> userMap = xxxx;
String name = userMap.computeIfAbsent(1, k -> "路人甲");
上面这段代码,是获取id为1的name,如果不存在,存入字符串"路人甲",并返回。注意,这里是会将默认值写入的。
5.4 Map的compute和computeIfPresent
Map<Integer,String> userMap = xxxx;
String name = userMap.compute(1, (k,v) -> "路人甲");
Map<Integer,String> userMap = xxxx;
String name = userMap.computeIfPresent(1, (k,v) -> "路人甲");
- compute的方法,不管key存不存在,操作完成后保存到map中; 
- computeIfPresent 的方法,对指定的在map中已经存在的key的value进行操作。只对已经存在key的进行操作,其他不操作 
这两个方法都是用的BiFunction,所以要用(k,v) ->这种兰布达表达式。
5.5 Map的merge
Map<Integer, String> userMap = new HashMap<>();
String name = userMap.computeIfAbsent(1, k -> "路人甲");
name = "路人乙";
userMap.merge(1, name, (k, v) -> String.join(",", k, v));
上面这段代码,是将userMap中的历史数据和现有数据,通过传入的BiFunction进行合并,怎么合并是BiFunction说了算。
六、Optional接口
Optional是JDK8新增的接口,其实啥额外的功能都没有,还可能会让你的代码多写几行,但是它是为不喜欢检查null的人设计的,一些对象如果是null,使用的时候就会抛出NullPointerException,所以如果返回了Optional 
 
较新版本的JPA查询都是返回的Optional接口了。
Optional<Integer> optional = Optional.ofNullable(1); 
if(optional.isPresent()){
    Integer a = optional.get();
}
至于Optional其他的map、orElseGet方法,一眼就明白了,这里不讲。
其他说明
有人说JDK8的日期API也很好啊,的确还可以,但是这套接口仍存在兼容性问题:
- fastjson最新版本1.2.73无法使用 - @JSONField(format = "yyyy-MM-dd HH:mm:ss")转换
- mybatis使用需要引入 - mybatis-typehandlers-jsr310,而且,如果mybatis版本小于3.4.0,还需要额外配置日期API的typeHandler;大于3.4.5,jsr310和typeHandler就都不需要额外配置了;
- fastxml也无法使用 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")转换;
至于JDK9及以上,我想国内用到的企业屈指可数,我也没研究过。
这里就先说这么多了,可以点击左下方的【阅读原文】查看更多内容哦!
欢迎在评论区留下你的观点,一起讨论提高。
如果今天的文章让你有新的启发,或者在学习能力的提升上有新的认识,欢迎转发分享给更多人。
欢迎各位读者加入品茗IT技术群聊,群号:801159061
