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
接口里加个非default
的stream
方法,必须在所有实现接口的类中都要实现这个方法(难道他们真的把XXXList
、XXXSet
都改了?)
肯(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中引入了 LocalDate
, LocalTime
, LocalDateTime
分别表示日期、时间、日期和时间,简单直接,而且就地提供了线程安全的日期时间操作,总体来说比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的主要内容,如果未能解决你的问题,请参考以下文章
使用 JDK8 和 lambda (java.util.stream.Streams.zip) 压缩流