14年java8发布,至今已经5年了,实际上很多软件企业都没有转到java8,比如苏宁,至今还停留在java7。我公司虽然已经升级到了java8,但是实际开发中,各个同事用到java8的新功能少之又少,lambda是用的最多的,stream用的很少。ps:当然java8在很多地方都有改进。
虽然之前已经在用这些新特性了,但最近才通过《java8 in action》这本书系统性了解新特性的,这里大概总结一下个人观点,是个人观点,不是权威观点。
-
lambda表达式。推荐使用,通过lambda表达式,可以缩减很多模板代码,当然也不会影响性能。但是,如果逻辑比较复杂,建议不要用lambda表达式,因为栈跟踪很难分析。怎么样的lambda表达式不算复杂?就说一点,在lambda中不要使用{},如果要使用{},就是复杂了,哈哈哈
-
stream流处理。并行流需要非常谨慎使用,比如下面这段代码1:
@Test public void testReduce() { long n = 10000000L; long result = 0; long startTime = System.currentTimeMillis(); for (long i = 1L; i <= n; i++) { result += i; } log.info("result = {}, use time = {}", result, System.currentTimeMillis() - startTime); }
代码1就是个很普通循环求和算法,如果转换成stream方式,且使用了并行流,如下代码2:
@Test public void testReduce1(){ long n = 10000000L; long startTime = System.currentTimeMillis(); Optional<Long> res = Stream.iterate(1L, i -> i + 1) .limit(n) .parallel() .reduce((i1, i2) -> Long.sum(i1, i2)); log.info("result = {}, use time = {}", res, System.currentTimeMillis() - startTime); }
经过测试,代码2的性能极差,实际耗时远高于代码1。原因是有两个,1,iterate()生成的Long是包装后的Long对象,还需要拆箱求和。2,iterate()的并行执行很难拆分,因为依赖前一次的执行结果。
再看代码3:
@Test public void testReduce2(){ long n = 1000000000L; long startTime = System.currentTimeMillis(); OptionalLong res = LongStream.rangeClosed(1L, n) .parallel() .reduce((i1, i2) -> Long.sum(i1, i2)); log.info("result = {}, use time = {}", res, System.currentTimeMillis() - startTime); }
代码3使用了LongStream可以避免代码2的2个性能问题。但是实际执行后还是比代码1要慢一丢丢。。。这其中问题我还没找到原因。
说实话,看到这里我对stream流还是有点小失望的,因为这个新特性无法在团队中推广使用,不熟悉stream流内部原理的程序员,写出的代码很可能执行正确,代码看着也很简洁漂亮,但是性能极差。而能力很强的程序员,可能倾向于使用更底层的api来实现并发,为什么更倾向?那我们就来看看stream的其它问题:
- 调试stream流。在stream流中调试是比较困难的,很可能出错的方法名都不会显示,这个非常蛋疼。
- 日志打印。一般需要用peek()方法来打印整个流程,这个也比较麻烦,让本来简洁的代码更加复杂了。
总结一下就是,api封装的太高级了,想在某个流程中自定义一些代码会比较困难。
-
optional,局部可以使用。全面使用比较困难,因为在序列化方面有问题,而且和json转化也有问题。
-
时间api,推荐使用。新的时间api,LocalDate表示年月日,LocalTime表示一天当中的时分秒,LocalDateTime表示某年某月某日某时某分某秒,Duration表示一个时间段,区分到纳秒,Period只区分到年月日