引言
最近在工作中用到java8的Stream流式操作很多,因而对相关概念与实操作简单归纳与总结:
函数式接口
函数式接口是java8新加入特性,为配合lambda表达式而生。lambda表达式与匿名表达式异同可参考 时光隧道 。判断一个接口是否为函数式接口特别简单,只需满足一个条件即可:一个接口有且仅有一个函数(接口默认static及default方法除外)! 即使该接口未被@FunctionalInterface注解标记,但仍为一个函数式接口
我们来看一个示例:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello the thread.");
}
});
thread.start();
使用lambda表达式创建方式为:
Thread thread=new Thread(()-> System.out.println("hello"));
thread.start();
从示例可以看出,使用lambda表达式配合函数式接口可以大大精简代码,那么函数式接口有什么用呢?
使用lambda表达式调用该函数,将接口方法的实现封装到具体方法,实际也是实现且创建一个接口对象,将方法作为接口实现。更加抽象,只能看到接口方法内部实现。
那么,我们可将常见的使用方式归纳为:
方法引用通过::将方法隶属和方法自身连接起来,如:ClassName :: methodName
1. 静态方法 (args) -> ClassName.staticMethod(args) 转换成 ClassName::staticMethod
2. 实例方法 (args) -> args.instanceMethod() 转换成 ClassName::instanceMethod
3. 外部的实例方法 (args) -> ext.instanceMethod(args) 转换成 ext::instanceMethod(args)
我们来看一个完整的示例:
@FieldDefaults(level = AccessLevel.PRIVATE)
@ToString
public class Kid {
Integer age;
String name;
public Kid(int age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {return age;}
public void setAge(Integer age) {this.age = age;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
public class MethodReference {
public static void main(String[] args) {
List<Kid> kids = Arrays.asList(
new Kid(10, "奶酪1"),
new Kid(8, "奶酪3"),
new Kid(11, "奶酪2")
);
// 采用lambda表达式排序
kids.sort((Kid a, Kid b) -> compare(a.getAge(), b.getAge()));
kids.forEach(System.out::println);
// 采用方法引用排序
kids.sort(Comparator.comparing(Kid::getName));
kids.forEach(System.out::println);
}
}
输出如下:
Kid(age=8, name=奶酪3),Kid(age=10, name=奶酪1),Kid(age=11, name=奶酪2)
Kid(age=10, name=奶酪1),Kid(age=11, name=奶酪2),Kid(age=8, name=奶酪3)
java8新增的java.util.function包,为我们提供了诸多的函数式接口,归纳如下:
| 函数式接口名 | 参数类型 | 接口说明 |
|---|---|---|
| Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
| Consumer | Consumer< T > | 接收T对象,不返回值 |
| Predicate | Predicate< T > | 接收T对象并返回boolean |
| Function | Function< T, R > | 接收T对象,返回R对象 |
| UnaryOperator | UnaryOperator< T > | 接收T对象,返回T对象 |
| BiConsumer | BiConsumer<T, U> | 接收T对象和U对象,不返回值 |
| BiPredicate | BiPredicate<T, U> | 接收T对象和U对象,返回boolean |
| BiFunction | BiFunction<T, U, R> | 接收T对象和U对象,返回R对象 |
| BinaryOperator | BinaryOperator< T > | 接收两个T对象,返回T对象 |
函数式接口须配合Stream才能发挥其最大的用处,接下来我们对Steam接口方法常见使用方式进行举例。
Stream浅析
何为Stream ?
流(Stream)是Java API的新成员,它允许以声明性的方式处理数据集合(类似于数据库查询语句).暂且理解为遍历数据集的高级迭代器.
我们可以用一图来归纳Stream的通用场景:

- intermediate(中间流 [可一个或多个])
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered - terminal(终结流 [只可有一个])
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator - short-circuiting(短路 [可随时终止])
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
我们现在开始慢慢解剖Stream常用API,完成以后你可能可以基本掌握Stream的用法!
Stream filter(Predicate<? super T> predicate);
filter:简而言之,按指定条件过滤,保留符合条件的对象,数据流程如下:

final List<Cat> catList = initList();
// 1. 过滤年龄小于等于3的猫咪
List<Cat> ageLessThanThreeList = catList.parallelStream().filter(cat -> cat.getAge().compareTo(3) <= 0).collect(toCollection(LinkedList::new));
ageLessThanThreeList.parallelStream().forEach(System.err::println);
result:
Cat{age=2, cnName='奶酪', enName='cheese', isMale=true}
Cat{age=3, cnName='曾三妹', enName='sisterThree', isMale=false}
Cat{age=2, cnName='奶油', enName='cream', isMale=true}
Stream map(Function<? super T, ? extends R> mapper);
map:对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。该方法有三个类型的变种,依次为:mapToInt、mapToLong、mapToDouble。比如,mapToInt意为新生成的Stream中的元素类型皆为int。多种变形方法,可以免除自动装箱/拆箱的额外消耗;通过流程图,我们一探究竟:

// 2. 将英文名称全部大写
List<String> upperCaseEnNameList = catList.parallelStream().map(cat -> cat.getEnName().toUpperCase()).collect(Collectors.toList());
upperCaseEnNameList.parallelStream().forEach(enName -> {
System.err.print(enName + " ");
});
result:
SISTERTHREE SEVENTH CHEESE CREAM
IntStream mapToInt(ToIntFunction<? super T> mapper);
// 3. 对年龄求和
int sumAge = catList.parallelStream().mapToInt(Cat::getAge).sum();
System.err.println(sumAge);
result:
11
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
flatMap:主要用于将层级结构扁平化,就是将最底层元素抽出来放到一起,最终产生的Stream里面已经没有List了,都是最底层的元素。我们通过流程图能更加直观理解流程:


// 4. 取出所有的int元素,并汇聚为list
List<List<Integer>> flatMapList = flatMapInitList();
List<Integer> numberList = flatMapList.parallelStream().flatMap(itemList -> itemList.parallelStream()).collect(toList());
numberList.parallelStream().forEach(item -> {
System.err.print(item.toString() + " ");
});
result:
6 5 8 7 3 4 2 1
Stream distinct();
distinct:滤重 , 数据流程图为:

// 5. 过滤去重
List<Integer> dupIntegerLit = dupIntegerInitList();
List<Integer> duplicateIntegerList = dupIntegerLit.parallelStream().distinct().collect(toList());
duplicateIntegerList.parallelStream().forEach(System.out::print);
System.out.println();
result :
231
Stream sorted();
源码表述为,sorted according to natural order, 对流中数据按照自然顺序排序。我们来看一个例子:
// 6. sorted 按年龄正序排列
List<Integer> orderAgeList = catList.parallelStream().map(Cat::getAge).sorted().collect(toList());
orderAgeList.stream().forEach(System.out::print);
System.out.println("attention:并行流会影响排序,需要特别注意!并行流结果为:");
orderAgeList.parallelStream().forEach(System.out::print);
result:
2234
attention:并行流会影响排序,需要特别注意!并行流结果为:
3422
Stream sorted(Comparator<? super T> comparator);
源码中描述为:sorted according to the provided {@code Comparator},意味着可以按照Comparator的排列规则,正序或倒序排列。并且,入参为Comparator,那么便可使用Comparator类中的多种方法,提升程序的扩展性。我们来看一个简单的例子:
/* sorted(Comparator<? super T> comparator) 按年龄倒序排列
若是按照自然顺序排列,则这种写法与.map(Cat::getAge).sorted()效果一模一样
*/
List<Cat> orderedAgeOfCatList = catList.parallelStream().sorted(Comparator.comparing(Cat::getAge).reversed()).collect(toList());
/*
等效于:
List<Cat> orderedAgeOfCatList = catList.parallelStream().sorted(Collections.reverseOrder(Comparator.comparing(Cat::getAge))).collect(toList());
即:Comparator.comparing(Cat::getAge).reversed() = Collections.reverseOrder(Comparator.comparing(Cat::getAge))
*/
orderedAgeOfCatList.stream().forEach(cat -> {
System.err.println(cat.toString());
});
result:
Cat{age=4, cnName='七夕', enName='seventh', isMale=true}
Cat{age=3, cnName='曾三妹', enName='sisterThree', isMale=false}
Cat{age=2, cnName='奶油', enName='cream', isMale=true}
Cat{age=2, cnName='奶酪', enName='cheese', isMale=true}
** 对上面的排序我们使用如下方式有异曲同工之妙**:
// 使用java8 list.sort(Comparator<? super E> c)
catList.sort(Comparator.comparing(Cat::getAge).thenComparing(Cat::getCnName).reversed());
catList.stream().forEachOrdered(System.out::println);
/*
总结:
1. 无参的sorted()是对某一项的排序,默认返回natural order,返回的是Stream<T>
2. 带参的sorted(Comparator<? super T> comparator)可对实体类中的某项排序,默认natural order,可通过reversed()方法倒序,返回的是实体类的集合.
3. 亦可使用java8 新的排序方式 list.sort(Comparator<? super E> c),配合Comparator进行排序
*/
result:
Cat{age=4, cnName='七夕', enName='seventh', isMale=true}
Cat{age=3, cnName='曾三妹', enName='sisterThree', isMale=false}
Cat{age=2, cnName='奶酪', enName='cheese', isMale=true}
Cat{age=2, cnName='奶油', enName='cream', isMale=true}
Stream limit(long maxSize);
limit方法很容易理解,即返回多少个符合条件的对象。简单连接下流程图即可:

List<Cat> limitCatList = catList.stream().limit(2).collect(toList());
limitCatList.stream().forEach(cat -> {
System.err.println(cat.toString());
});
result:
Cat{age=4, cnName='七夕', enName='seventh', isMale=true}
Cat{age=3, cnName='曾三妹', enName='sisterThree', isMale=false}
Stream skip(long n);
skip即跳过指定个数,取其后的数据。也很容易理解,示意图如下:

List<Cat> skipCatList = catList.stream().skip(1).collect(toList());
skipCatList.parallelStream().forEach(cat -> {
System.err.println(cat.toString());
});
result:
Cat{age=2, cnName='奶酪', enName='cheese', isMale=true}
Cat{age=2, cnName='奶油', enName='cream', isMale=true}
Cat{age=3, cnName='曾三妹', enName='sisterThree', isMale=false}
Optional min(Comparator<? super T> comparator);
求集合中的最小值,返回值为Optional,我们来看例子:
Cat minAgeOfCat = catList.parallelStream().min(Comparator.comparing(Cat::getAge)).get();
System.err.println(minAgeOfCat.toString());
result:
Cat{age=2, cnName='奶酪', enName='cheese', isMale=true}
Optional max(Comparator<? super T> comparator);
求集合中的最大值,很方便理解,我们接着看例子:
Cat maxAgeOfCat = catList.parallelStream().max(Comparator.comparing(Cat::getAge)).get();
System.err.println(maxAgeOfCat.toString());
result:
Cat{age=4, cnName='七夕', enName='seventh', isMale=true}
long count();
求集合中元素的数量,与list.size()有异曲同工之妙。
long count = catList.parallelStream().count();
System.err.println(count);
result:
4
boolean anyMatch(Predicate<? super T> predicate);
根据规则,若集合中有匹配该规则的元素,则直接结束流程,并返回true;若集合遍历结束还没有找到匹配的元素,则返回false;
boolean isAnyMatch = catList.parallelStream().anyMatch(cat -> cat.getCnName().contains("奶"));
System.err.println(isAnyMatch);
result:
true
Optional findAny();
亦是根据规则进行匹配,找到符合条件的则返回该元素;
Cat findAnyOfCat = catList.parallelStream().filter(cat -> cat.getCnName().contains("奶")).findAny().get();
System.err.println(findAnyOfCat.toString());
result:
Cat{age=2, cnName='奶油', enName='cream', isMale=true}
很多时候anyMatch与findAny可以相互替换,使用方式类似
boolean allMatch(Predicate<? super T> predicate);
判断某个集合是否全部符合某个规则,若是,则返回true;反之则返回false;
boolean isAllMatch = catList.parallelStream().allMatch(cat -> Objects.equals(cat.getCnName(), "奶酪"));
System.err.println(isAllMatch);
result:
false
关键代码如下:
private static Map<Integer, Integer> initMap() {
return new HashMap<Integer, Integer>() {{
put(1, 10);
put(3, 12);
put(2, 11);
put(4, 13);
}};
}
private static void readMeFirst() {
/*
1.Stream 流的常规操作可归类如下:
. intermediate(中间流 [可一个或多个])
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
. terminal(终结流 [只可有一个])
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
. short-circuiting(短路 [可随时终止])
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
*/
}
private static List<Integer> dupIntegerInitList() {
return new ArrayList<Integer>() {{
add(1);
add(1);
add(2);
add(3);
}};
}
private static List<Cat> dupCatInitList() {
return new ArrayList<Cat>() {{
add(new Cat(1, "刘啵啵儿", "liuboboer", true));
add(new Cat(1, "刘啵啵儿", "liuboboer", true));
}};
}
private static List<Cat> initList() {
return new ArrayList<Cat>() {{
add(new Cat(2, "奶油", "cream", true));
add(new Cat(2, "奶酪", "cheese", true));
add(new Cat(3, "曾三妹", "sisterThree", false));
add(new Cat(4, "七夕", "seventh", true));
}};
}
private static List<List<Integer>> flatMapInitList() {
return new ArrayList<List<Integer>>() {{
add(Arrays.asList(1, 2, 3));
add(Arrays.asList(4, 5));
add(Arrays.asList(6, 7, 8));
}};
}
class Cat {
/** * 年龄 */
private Integer age;
/** * 中文名称 */
private String cnName;
/** * 英文名称 */
private String enName;
/** * 是否为男性 */
private Boolean isMale;
public Integer getAge() { return age;}
public void setAge(Integer age) { this.age = age;}
public String getCnName() { return cnName;}
public void setCnName(String cnName) { this.cnName = cnName;}
public String getEnName() { return enName;}
public void setEnName(String enName) { this.enName = enName;}
public Boolean getMale() { return isMale;}
public void setMale(Boolean male) { isMale = male;}
public Cat() {}
public Cat(Integer age, String cnName, String enName, Boolean isMale) {
this.age = age;
this.cnName = cnName;
this.enName = enName;
this.isMale = isMale;
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
", cnName='" + cnName + '\'' +
", enName='" + enName + '\'' +
", isMale=" + isMale +
'}';
}
}