Java8于2014年3月份发布,其主要更新的特性有:函数式接口、Lambda 表达式、集合的流式操作、注解的更新、安全性的增强、IO\NIO 的改进、完善的全球化功能等,本文将介绍Lambda表达式与集合流试操作。
函数式接口
Java8引入的一个重要的思想就是函数式编程。 提到函数式,必须提到函数式接口。在Java8中对函数式接口的注解是@FunctionalInterface,任何一个只有一个抽象方法的接口都默认为是函数式接口,@FunctionalInterface注解是非必须的。如果在一个接口上加上该注解,那么这个接口就只能有一个抽象方法。如下
@FunctionalInterface
public interface TestInterface { void test(); }
也可以省略注解
public interface TestInterface { void test(); }
几种常见的函数式接口比如说Function<T, R> 支持传入一个参数T并且返回R;
Predicate<T> 传入一个参数T返回一个boolean类型等等。
接口 | 参数 | 返回类型 |
---|---|---|
Function<T,R> | T | R |
Predicate<T> | T | boolean |
Supplier<T> | None | T |
UnaryOperator<T> | T | T |
Consumer<T> | T | void |
BinaryOperator<T> | (T, T) | T |
Lambda
我们可以用Lambda表达式来实例化函数式接口,避免了内部类冗余的代码。
Lambda表达式的组成
Lambda表达式有多种形式,总结来说不外乎三部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数,参数中可以指定类型,也可以省略类型(如果是单个参数的情况下括号可以省略,无参或者多个参数则不能省略括号);第二部分为一个箭头符号:-> 是lambda的运算符;第三部分为方法体,可以是表达式和代码块。如下
TestInterface t = () -> System.out.println("lambda");
如果有入参,第三部分是代码块,而且实现的接口有返回值的时候需要加上{}且要把结果return
public interface LongToInt { int toInt(Long i); }
LongToInt lti = i -> {
if(i != null){
return i.intValue();
} else return 0;
};
在Java8以前要想实例化接口使用匿名内部类来处理,代码显得啰嗦而不易读
new TestInterface(){
public void test() {
System.out.println("lambda");
}
}.test();
使用lambda表达式后就可以简化成一行代码
((TestInterface)() -> System.out.println("lambda")).test();
Lamdba中的方法引用
如果方法体只是简单的函数引用则可以直接使用 :: 引用,更大程度上简化我们的代码。Java8中的 :: 符号表示:符号左边的对象(List<Integer> res),调用符号右边的方法(add()),参数即为lamdba表达式的参数(Integer)。
List<Integer> res = Lists.newArrayList();
ints.forEach(res :: add);
Stream
java8中引用stream可以对集合进行过滤、排序、映射等操作。
生成Stream
Java8的stream分为两种,即并行和串行。可以通过Collection.parallelStream()生成一个并行流,可以通过Collection.stream()生成一个串行流(默认的stream开启的是串行流)。Stream通过StreamSupport.stream(Spliterator<T> spliterator, boolean parallel)方法中的parallel来判断是否生成并行流。
default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}
default Stream<E> parallelStream() {return StreamSupport.stream(spliterator(), true);}
我们还可以将生成的stream通过stream.parallel()或stream.sequential()转换成并行或者串行。
通过方法名我们不难理解,串行流在一个线程依次执行,而并行流则是在多个线程同时执行。并行流的执行效率远远大于串行流。
Stream应用
一般在Java中使用stream有三步:第一步生成stream;第二步对stream进行转换;第三步聚合操作得到想要的结果。
Stream的中间转换操作
- distinct: 对stream中的元素进行去重操作。
- filter: 对stream中包含的元素使用给定的过滤函数进行过滤操作,而filter方法传的参数就是Predicate实例化,即可以. 使用上面提到的lambda表达式(新的stream中只包含Predicate中判定为true的元素)。
map: 对stream中包含的元素使用给定的转换函数进行转换操作.这个方法有三个引申方法,分别是:mapToInt,mapToLong和mapToDouble,在map中执行转换函数,再转换新的的stream元素为Integer, Long, Double类型。转换函数类型为Function,用lambda表达式比较方便。 - flatMap:会把要求Function的返回值是stream即在map里面用stream的子元素再生成一个stream,把所有的子元素生成的stream压缩成一个stream。
- boxed: 将LongStream、IntStream、DoubleStream转换成对应类型的Stream<T>。
- limit: 对一个stream进行截断操作,获取其前n个元素,如果原stream中包含的元素个数小于n,那就获取其所有的元素;
- skip: 返回一个丢弃原stream的前n个元素后剩下元素组成的新stream,如果原stream中包含的元素个数小于n,那么返回空stream;
Stream的聚合操作
- collect: 将stream元素收集起来,可以通过Collectors.toList()实现将stream转换为List的操作。
- count: 得到stream的元素个数。
- findFirst: 得到第一个元素。
- max: 需要自定义一个Comparator来比较元素的大小,返回最大值。
- min: 需要自定义一个Comparator来比较元素的大小,返回最小值。
- allMatch: 判断元素是否全部符合Predicate标准。
- anyMatch: 判断元素是否有一个或者多个符合Predicate标准。
- noneMatch: 判断元素是否都不符合Predicate标准。
List<String> strList = Lists.newArrayList("11 ", "213 ", " 11", "23", "145", "15 ", "2 ", " 3", "");
List<Integer> ints = Lists.partition(strList, 3).stream().flatMapToInt(strs -> strs.stream().filter(str -> str != null && !Objects.equals(str, "")).mapToInt(str -> Integer.valueOf(str.trim()))).distinct().limit(5).skip(3).boxed().collect(Collectors.toList());
上面的例子是把一个List<String>,先通过Guava Lists将该list按每组3个拆分生成List<List<String>>再转换为Stream<List<String>>,再flatMapToInt,在map里面再将list转换为Stream<String>,过滤掉空字符串和null,再mapToInt去重,取前5个,再丢弃前三个元素,再转换成一个List<Integer>。
注:所有的聚合操作、foreach操作都会把stream关闭,如果一个stream被关闭后,不能再次对stream进行任何操作。