在 Java 1.8 中,得益于 Lambda 表达式所带来的函数式编程,引入了 Stream 流,用于解决已有集合类库的弊端
几乎所有的集合(如Collection
接口或Map
接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历,如:
package cn_lemon;
import java.util.ArrayList;
import java.util.List;
public class ForeachDemo {
public static void main(String[] args) {
List<String> l = new ArrayList<>();
l.add("李彦宏");
l.add("马化腾");
l.add("刘强东");
l.add("马云");
l.add("张朝阳");
l.add("马冬梅");
l.add("马东敏");
for (String s : l) {
System.out.println(s);
}
}
}
1、循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现:
为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
试想一下,如果希望对集合中的元素进行筛选过滤:
package cn_lemon;
import java.util.ArrayList;
import java.util.List;
public class ForeachDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("李彦宏");
list.add("马化腾");
list.add("刘强东");
list.add("马云");
list.add("张朝阳");
list.add("马冬梅");
list.add("马东敏");
List<String> malist = new ArrayList<>();
for (String name1 : list) {
if (name1.startsWith("马")) {
malist.add(name1);
}
}
List<String> threelist = new ArrayList<>();
for (String name2 : malist) {
if (name2.length() == 3) {
threelist.add(name2);
}
}
for (String name3 : threelist) {
System.out.println(name3);//输出:马化腾 马冬梅 马东敏
}
}
}
在上面的代码中,有三个循环:
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?
使用 Stream 的写法
package cn_lemon;
import java.util.ArrayList;
import java.util.List;
public class ForeachDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("李彦宏");
list.add("马化腾");
list.add("刘强东");
list.add("马云");
list.add("张朝阳");
list.add("马冬梅");
list.add("马东敏");
list.stream()
.filter(s -> s.startsWith("马"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
在这段代码中,并没有体现使用线性循环或是其他任何算法进行遍历,直接:获取流、过滤姓马、过滤长度为3、逐一打印
当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。
这张图展示了过滤、映射、跳过、计数等多步操作,每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。这里的 filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理,只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。这得益于 Lambda 表达式延迟执行的特性
备注:”Stream 流“ 其实是一个集合元素的函数模型,它不是集合,也不是数据结构
Stream(流)是一个来自数据源的元素队列
和 Collection 操作不同, Stream操作还有两个基础的特征:
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
java.util.stream.Stream
是Java 8新加入的最常用的流接口(这并不是一个函数式接口)
获取一个流非常简单,有以下几种常用的方式:
Collection
集合都可以通过Stream
默认方法获取流Stream
接口的静态方法of
可以获取数组对应的流1、根据 Collection
获取流
java.util.Collection
接口中加入了default方法stream
用来获取流,所以其所有实现类均可获取流
package cn_lemon;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
class ConDemo {
public static void main(String[] args) {
List<String> list = null;
list.stream();//List 的Stream 方法
Set<String> set = null;
set.stream();// Set 的 Stream 方法
Map map = null;
map.values().stream();//Map 集合中 value 的stream 方法
map.keySet().stream();
map.entrySet().stream();
int[] array = null;//数组是需要用工具类获取流的
Arrays.stream(array);//获取流
}
}
2、Map 获取流
java.util.Map
接口不是Collection
的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
package cn_lemon;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
class MapDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
//......
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
3、数组获取流
由于数组对象不可能添加默认方法,所以Stream
接口中提供了静态方法of
,of
方法的参数其实是一个可变参数,所以支持数组
package cn_lemon;
import java.util.Arrays;
import java.util.stream.Stream;
class ArrayDemo {
public static void main(String[] args) {
String[] array = {"马化腾", "马云", "张一鸣", "刘强东", "李彦宏", "张朝阳", "张志东"};
Stream<String> stream = Stream.of(array);//数组获取流
Arrays.stream(array).filter(s -> s.startsWith("马")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
}
}
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
Stream
接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那样的链式调用。本小节中,终结方法包括count
和forEach
方法。1-1、forEach 逐一处理
Consumer
接口函数,会将每一个流元素交给该函数进行处理package cn_lemon;
import java.util.stream.Stream;
class ForEachDemo {
public static void main(String[] args) {
Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "刘强东", "李彦宏", "张朝阳", "张志东");
stream.forEach(s -> System.out.println(s));
}
}
1-2、Consumer 接口
Consumer
原意:消费者,就是消费一个数据,其数据类型由泛型决定accept
andThen
package cn_lemon;
import java.util.function.Consumer;
class ConsumerDemo {
public static void consumerStream(Consumer<Integer> con) {
con.accept(4317622);
}
public static void main(String[] args) {
consumerStream(s -> System.out.println(s));
}
}
package cn_lemon;
import java.util.function.Consumer;
class ConsumerDemo {
public static void consumerStream(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello Lemon");
}
public static void main(String[] args) {
consumerStream(
s -> System.out.println(s.toLowerCase()),
s -> System.out.println(s.toUpperCase())
);
}
}
package cn_lemon;
import java.util.function.Consumer;
class ConsumerDemo {
public static void man(Consumer<String> one, Consumer<String> two, String[] array) {
for (String s : array) {
one.andThen(two).accept(s);
}
}
public static void main(String[] args) {
String[] array = {"马化腾,腾讯", "马云,阿里巴巴", "李彦宏,百度"};
man(
s -> System.out.print("姓名:" + s.split(",")[0]),
s -> System.out.println("---,来自:" + s.split(",")[1]), array
);
}
}
2-1、filter 过滤
filter
方法是将一个流转换成另一个子集流Stream filter(Predicate super T> predicate);
Predicate
函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件2-2、Predicate 接口
Predicate
原意 : 谓语 ,是对某种类型的数据进行判断,得到一个 boolean
值test
package cn_lemon;
import java.util.function.Predicate;
class PredicateDemo {
private static void predi(Predicate<String> p) {
boolean b = p.test("让世界充满爱");
System.out.println("字符串的长度大于5吗??? ---" + b);
}
public static void main(String[] args) {
predi(s -> s.length() > 5);
}
}
3-1、Map 映射
Stream map(Function super T, ? extends R> mapper);
Function
函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流package cn_lemon;
import java.util.ArrayList;
import java.util.List;
public class MapDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("10");
list.add("20");
list.add("30");
list.add("40");
list.add("50");
list.stream().map(s -> Integer.parseInt(s)).forEach(s -> System.out.println(s));
}
}
3-2、Function 接口
Function
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件apply
andThen
package cn_lemon;
import java.util.function.Function;
class FunctionDemo {
private static void method(Function<String, Integer> fun) {//将 String 类型转换成 Integer 类型
int num = fun.apply("10");//抽象方法 apply
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s -> Integer.parseInt(s));
}
}
package cn_lemon;
import java.util.function.Function;
class FuncDemo {
private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20);//结果输出 220
}
public static void main(String[] args) {
method(str -> Integer.parseInt(str) + 30, i -> i = i * 5);
}
}
4、count 统计个数
package cn_lemon;
import java.util.Arrays;
import java.util.stream.Stream;
public class CountDemo {
public static void main(String[] args) {
String[] array = {"马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏"};
Stream<String> result = Arrays.stream(array).filter(s -> s.startsWith("马"));
System.out.println(result.count());
}
}
或者
package cn_lemon;
import java.util.stream.Stream;
public class CountDemo {
public static void main(String[] args) {
Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏");
Stream<String> result = stream.filter(s -> s.startsWith("马"));
System.out.println(result.count());
}
}
5、limit 取用前几个
long
型,如果集合当前长度大于参数则进行截取;否则不进行操作package cn_lemon;
import java.util.stream.Stream;
public class CountDemo {
public static void main(String[] args) {
Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏");
Stream<String> result = stream.limit(2);
System.out.println(result.count());//输出 2
}
}
6、skip 跳过前几个
skip
方法获取一个截取之后的新流package cn_lemon;
import java.util.stream.Stream;
public class CountDemo {
public static void main(String[] args) {
Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏");
Stream<String> result = stream.skip(3);
System.out.println(result.count());//输出 3
}
}
7、concat 组合
Stream
接口的静态方法concat
java.lang.String
当中的concat
方法是不同的package cn_lemon;
import java.util.stream.Stream;
public class CountDemo {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张志东", "李彦宏", "马东敏");
Stream<String> streamB = Stream.of("马化腾", "马云", "张一鸣");
Stream<String> result = Stream.concat(streamA, streamB);//把两个流合并成一个流
}
}
package cn_lemon;
import java.util.stream.Stream;
public class CountDemo {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张志东", "李彦宏", "马东敏");
Stream<String> streamB = Stream.of("马化腾", "马云", "张一鸣");
Stream.concat(streamA, streamB).forEach(s -> System.out.println(s));
}
}
有两个ArrayList
集合存储队伍当中的多个成员姓名,依次进行以下若干操作步骤:
Person
对象;存储到一个新集合中。方法一:(传统集合遍历)
封装一个 Person.java
package cn_lemon;
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "人员名单:{姓名---“" + name + "”}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建 NameDemo.java
package cn_lemon;
import java.util.ArrayList;
import java.util.List;
public class NameDemo {
public static void main(String[] args) {
List<String> listA = new ArrayList<>();
listA.add("马云");
listA.add("马化腾");
listA.add("张一鸣");
listA.add("王健林");
listA.add("程维");
listA.add("比尔盖茨");
listA.add("马赛克");
List<String> listB = new ArrayList<>();
listB.add("张英");
listB.add("马东敏");
listB.add("李彦宏");
listB.add("刘强东");
listB.add("雷军");
listB.add("李开复");
listB.add("张强");
listB.add("张朝阳");
List<String> threelist = new ArrayList<>();
for (String three_name : listA) {//筛选 A 集合中名字为 3 个字的人,存储到集合中
if (three_name.length() == 3) {
threelist.add(three_name);
}
}
List<String> limitlist = new ArrayList<>();
for (int i = 0; i < 3; i++) {//在筛选A之后的集合中,选前三个人
limitlist.add(threelist.get(i));
}
List<String> zhanglist = new ArrayList<>();
for (String zhang_name : listB) {//筛选集合 B 中姓张的
if (zhang_name.startsWith("张")) {
zhanglist.add(zhang_name);
}
}
List<String> twolist = new ArrayList<>();
for (int i = 2; i < zhanglist.size(); i++) {//在筛选B之后的集合中,不要前两个
twolist.add(zhanglist.get(i));
}
List<String> totalnames = new ArrayList<>();//将筛选之后的集合合并
totalnames.addAll(limitlist);
totalnames.addAll(twolist);
List<Person> totalPersonname = new ArrayList<>();//根据姓名创建Person 对象
for (String name : totalnames) {
totalPersonname.add(new Person(name));
}
for (Person per : totalPersonname) {//打印Person对象的信息
System.out.println(per);
}
}
}
方法二:(使用 Stream 流的方式)
封装一个 Person.java
package cn_lemon;
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "人员名单:{姓名---“" + name + "”}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建 NameDemo.java
package cn_lemon;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class NameDemo {
public static void main(String[] args) {
List<String> listA = new ArrayList<>();
listA.add("马云");
listA.add("马化腾");
listA.add("张一鸣");
listA.add("王健林");
listA.add("程维");
listA.add("比尔盖茨");
listA.add("马赛克");
List<String> listB = new ArrayList<>();
listB.add("张英");
listB.add("马东敏");
listB.add("李彦宏");
listB.add("刘强东");
listB.add("雷军");
listB.add("李开复");
listB.add("张强");
listB.add("张朝阳");
Stream<String> streamA = listA.stream().filter(s -> s.length() == 3).limit(3);//A集合中,三个字的姓名,取前三个
Stream<String> streamB = listB.stream().filter(s -> s.startsWith("张")).skip(2);//B集合中,姓张的,不要前两个
Stream<String> totalstream = Stream.concat(streamA, streamB);//合并流
totalstream.map(s -> new Person(s)).forEach(p -> System.out.println(p));
}
}
或者: