Java 8 不止是Lambdas和Streams

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 8 不止是Lambdas和Streams相关的知识,希望对你有一定的参考价值。

转眼淘系应用升级JDK8已经几个月过去了,Lambdas表达式和Streams APIs的确给同学们带来了编程效率和代码可读性上的提升,代码变得更加简洁直接,更加符合人的思维(看来编程语言的发展也是本着“以人为本”的思路)。ATA上讲这两个新特性的文章已经很多了, 大鱼大肉大家吃的差不多,得常常家常小菜吧,下面总结一下常用的Java 8中Lambdas和Streams之外的一些新特性。

1. Default Method

Java 单继承的特性决定了一个类只能有1个父类,如果有通用的方法实现就只能写在父类里,如果是接口就不能有实现,偏偏Java 8中的stream方法就遇到了这个问题。原来没有吧,在Collection接口里加个非defaultstream方法,必须在所有实现接口的类中都要实现这个方法(难道他们真的把XXXListXXXSet都改了?)

肯(bu)定(yao)没(sha)有(le),Oracle猿们开了个后门(自己的东西就是方便,自己想改就改),他们用default method解决了这个问题。在Collection接口中实现了一个default method - stream, 这样所有其他的XXXList都不用改了,还可以直接使用这个方法。当然,只要实现类中不重写这个方法,实例调用的就是接口中的实现。

可以看到java.util.Collection中确实加了3个default方法。

default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
}

使用上其实就是default关键字,在interface的方法上加上default就会有2个作用:a. 接口中必须实现这个方法;b. 子类中可以不实现这个方法了。
另外,还可以在interface中加入static方法,配合default使用,这样直接静态方式使用接口的default方法,减少了写一些另外的util/helper类。

// 接口类
public interface BaseJava {

    default void sayHi(){
        System.out.println("Hi default method");
    }
    public void goodbye();
}

// 实现类
public class ChildJava implements BaseJava{

    @Override
    public void goodbye() {
        System.out.println("必须实现的方法");
    }

    public static void main(String...args){
        ChildJava cj = new ChildJava();
        cj.sayHi(); // default method
    }
}

2. Optional

业务应用的对象往往比较大,对象里面有对象,对象里面还有对象...在处理的时候,总是得判断是否为null,这无形增加了程序的判断深度(如果不加判断,就是NullPointerException了,关键是一旦出了异常,排查起来也是时间)

如果是这种普通的非空判断(用isPresent()代替!=null),其实和以前差不多,好像没有怎么方便(个人感觉甚至麻烦了,实际上相当于把非空判断封装了一个Helper静态方法)。

Optional<String> str = Optional.ofNullable("some returned value"); // 可能是空
System.out.println(str.isPresent()?str.get():"default value"); // Java 8
System.out.println(str.orElse("default value")); // Java 8
System.out.println(str!=null?str:"default value"); // Java 7

// 看看Java源代码
public boolean isPresent() {
        return value != null;
}
public T orElse(T other) {
        return value != null ? value : other;
}

但是,在对象比较复杂的情况下,就显得比较高效了,尤其是配合stream (reduce max min)和 lambdas表达使用。看到返回值是Optional的情况下,都会下意识去判断非空,这和之前相比,可以减少程序因为NullPointerException而出现的功能缺陷,提高开发效率(我记得google的一个三方包里早就有Optional这个东东了)。

3. 字符串API

Scenario 1. 业务应用处理最多的东西莫过于List和String,常见的操作就是给你一个List,处理一下串成一个String,或者给你一个String(用delimiter分隔的),处理一下变成一个List(木办法,前端要String ,后端要List,夹在中间只能不停地ForEach了)

这要搁以前(Java 7),某猿会这样写:

// 1 给前端拼一个逗号分隔的商品id列表的字符串
List<String> itemIdList = ...; // 从IC获取的热乎乎的List
StringBuffer sb = new StringBuffer();  // 也可以用StringBuilder...
for(String itemId: itemIdList){
        sb.append(itemId).append(","); // TODO  BUG 末尾还有一个 ,
}
return sb.toString();        

// 2 前端传来的都好分隔的商品id列表字符串,咱转成List给后端
String str ="1000,1001,1002,1003";
String[] arr=str.split(",");
List<String> list = Arrays.asList(arr);


这样写当然没问题,但是在业务逻辑处理中,老是来这么一下,一则影响代码美观(本来类像一棵直挺挺的树,加了几个这个,像是树瘤一样,100行代码中有50行是for循环...),二则容易打断了主要业务逻辑的思路(转而处理for循环里面的内容,当心最后一个 , )。

使用Java 8以后,情况会好很多,一两行代码搞定:

// 1 给前端拼一个逗号分隔的商品id列表的字符串
List<String> itemIdList = ...; 
return String.join(",", itemIdList);

// 当然,如果获取的是Object,就需要配合streams来完成join了
List<ItemDO> itemDOs = ...
String names = itemDOs.stream().map(ItemDO::getItemId).collect(Collectors.joining(","));

// java 8 源码 String.join的实现
public static String join(CharSequence delimiter, CharSequence... elements) {
    Objects.requireNonNull(delimiter);
    Objects.requireNonNull(elements);
    // Number of elements not likely worth Arrays.stream overhead.
    StringJoiner joiner = new StringJoiner(delimiter);
    for (CharSequence cs: elements) {
        joiner.add(cs);
    }
    return joiner.toString();
}

4. 日期和时间API

Scenario 2. 业务应用总是免不了和日期时间打交道,而且,与多个系统对接后,要在日期、时间、日期时间、字符串等来回倒腾,前端要字符串,数据库存的datetime, 兄弟团队的二方包居然只要日期部分的String等等,当然还有日期的操作,向前多少天,向后多少天

Java 7当中,我们最长使用的莫过于Calendar了,因为它简单轻便,而且支持日期操作。此外,配合SimpleDateFormat, 就可以把任何String转换成日期,然后利用Calendar操作,最后再使用另一个SimpleDateFormat写成前端需要的格式。通常情况下,这样写:

SimpleDateFormat sdf =new SimpleDateFormat("yyyy/MM/dd");
SimpleDateFormat sdf2 =new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2016/09/10");
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, 10);
Date tenDaysLater = cal.getTime();
System.out.println(sdf2.format(tenDaysLater));

Java 8中引入了 LocalDateLocalTimeLocalDateTime 分别表示日期、时间、日期和时间,简单直接,而且就地提供了线程安全的日期时间操作,总体来说比7要方便很多了。上面的代码,在8里可以这样写:

LocalDate today = LocalDate.now();
System.out.println("today is " + today);

LocalDate specifiedDay = LocalDate.of(2016, Month.JUNE, 1);
System.out.println("specific date is " + specifiedDay);

LocalDate fifthIn2016 = LocalDate.ofYearDay(2016, 5);
System.out.println("5th day of 2016 is " + fifthIn2016);

// 默认使用 DateTimeFormatter.ISO_LOCAL_DATE
LocalDate parsedDay = LocalDate.parse("2016-08-31"); 
System.out.println("parsed day is " + parsedDay);

// 内置 yyyyMMdd
DateTimeFormatter basicIsoDate = DateTimeFormatter.BASIC_ISO_DATE; 
// 内置 yyyy-MM-dd
DateTimeFormatter isoLocalDate = DateTimeFormatter.ISO_LOCAL_DATE; 
// 自定义 yyyy:MM:dd
DateTimeFormatter customDate = DateTimeFormatter.ofPattern("yyyy:MM:dd"); 
// 转换
LocalDate formattedDay = LocalDate.parse("20160821", basicIsoDate);
System.out.println("isoLocalDate day is " + formattedDay.format(isoLocalDate));
System.out.println("customDate day is " + formattedDay.format(customDate));

// 时间操作
LocalTime now = LocalTime.now();
LocalTime later = now.plus(5, HOURS);

// 日期操作
LocalDate today = LocalDate.now();
LocalDate thirtyDaysLater = today.plusDays(30);
LocalDate afterOneMonth = today.plusMonths(1);
LocalDate beforeOneMonth = today.minusMonths(1);

相对于Calendar操作来说,LocalDate每次都返回一个全新的对象(LocalDate/LocalTime都是final class),这样做在多线程环境下更安全。

5. 总结

Java 8从应用升级到全面使用,大概还有比较长的路要走,毕竟很多已有的代码不会为了尝试新语法和特性而进行更新。在新的工程和代码中,尝试新的语法和特性,会带来更好的体验和效率。另外,功能等价的代码,除了语法上的不同,Java 7和Java 8在性能上有什么不同么,Java 8真的减少了资源提高了效率么?这个问题还得在今后的使用中进一步探讨。

6. 参考资料

Joda-Time 
Java 8 新特性
Java 8 基础实践

以上是关于Java 8 不止是Lambdas和Streams的主要内容,如果未能解决你的问题,请参考以下文章

Java 8 Stream得到最少

使用 JDK8 和 lambda (java.util.stream.Streams.zip) 压缩流

Java 8新特性终极指南

从 Java 8 到 Java 15 的新特性

Java9函数式编程 Functional Programming with Streams in Java 9

Java基础Java中的语法糖