首页 > 代码库 > Java 8 不止是Lambdas和Streams

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 8System.out.println(str.orElse("default value")); // Java 8System.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获取的热乎乎的ListStringBuffer 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_DATELocalDate parsedDay = LocalDate.parse("2016-08-31"); System.out.println("parsed day is " + parsedDay);// 内置 yyyyMMddDateTimeFormatter basicIsoDate = DateTimeFormatter.BASIC_ISO_DATE; // 内置 yyyy-MM-ddDateTimeFormatter isoLocalDate = DateTimeFormatter.ISO_LOCAL_DATE; // 自定义 yyyy:MM:ddDateTimeFormatter 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