引入
Lambda的衍生物Stream
- 回顾
几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。
传统做法:
public class D01List {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> listA = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张")) {
listA.add(name);
}
}
List<String> listB = new ArrayList<>();
for (String name : listA) {
if (name.length()==3) {
listB.add(name);
}
}
for (String s : listB) {
System.out.println(s);
}
}
}
- Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),我们使用循环是为了遍历,对元素逐一处理,但循环只是一种方式,我么应该关注的在怎么处理(过滤)
改进:使用Stream流进行过滤输出
/*
使用Stream流的方式,遍历集合,对集合中的数据进行过滤
Stream是jdk1.8之后出现的,它关注的是做什么而不是怎么做
Stream<T> filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流
*/
public class D02Stream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以"张"开头,存储到一个新的集合中去
//对list集合进行过滤,只要名字长度为3的人,存储到一个新的集合中去
//遍历list集合
list.stream() //两次过滤,一次输出
.filter(name->name.startsWith("张"))
.filter(name->name.length()==3)
.forEach(name -> System.out.println(name));
}
}
流式思想
概述: Stream(流)是一个来自数据源的元素队
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组 等。
(补充:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值))
- 和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluentstyle)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
-
当使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。 -
获取流
- java.util.stream.Stream 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
- 获取一个流非常简单,有以下几种常用的方式:
- 所有的 Collection 集合 都可以通过 stream 默认方法获取流;
default Stream stream() 返回一个序列 Stream与此集合作为其来源 - Stream 接口的静态方法 of 可以获取数组 对应的流。
static Stream of(T… values) 返回其元素是指定值的顺序排序流
参数是一个可变参数,那么我们就可以传递一个数组
- 所有的 Collection 集合 都可以通过 stream 默认方法获取流;
public class D03GetStream {
public static void main(String[] args) {
//把集合转换为Stream流(注意:必须是单列集合): Collection 集合通过 stream 默认方法获取流
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> stream2 = set.stream();
//map集合需要转换
//获取键存储到一个set集合中
Map<String, String> map = new HashMap<String, String>();
Set<String> keyset = map.keySet();
Stream<String> stream3 = keyset.stream();
//获取值,存储到一个collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射)
Set<Entry<String, String>> entrySet = map.entrySet();
Stream<Entry<String, String>> stream5 = entrySet.stream();
//把数组转换为Stream流:Stream 接口的静态方法 of 可以获取数组对应的流。
Stream<Integer> stream6 = Stream.of(1,2,3,4,5);
//参数是一个可变参数,那么我们就可以传递一个数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
String[] arr2 = {"a","bb","ccc"};
Stream<String> stream8 = Stream.of(arr2);
}
}
Stream流的常用方法
- 延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
- 终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。(count \ forEach)
- Stream是一个管道流,只能被消费(使用)一次
- forEach(终结)
void forEach(Consumer<? super T> action)
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。
Consumer接口是一个消费型接口,可以传递Lambda表达式,消费数据。
- 简单记:
forEach方法,用来遍历流中的数据,是一个终结方法,遍历之后不能再继续调用Stream流中的其他方法
public class D04ForEach {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("张三","李四","王五","马六");
//调用tream流中的forEach对Stream流中的数据进行遍历
/*
stream.forEach((String name)->{
System.out.println(name);
});
*/
//优化
stream.forEach(name->System.out.println(name));
}
}
- filter
Stream filter(Predicate<? super T> predicate);
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件
- Predicate:boolean test(T t)
该方法将会产生一个boolean值结果,代表指定的条件是否满足。
- 如果结果为true,那么Stream流的 filter 方法将会留用元素;
- 如果结果为false,那么 filter 方法将会舍弃元素
public class D05Filter {
public static void main(String[] args) {
Stream<String> stream = Stream.of("赵敏","张翠山","张三丰","周芷若","张无忌");
Stream<String> stream2 = stream.filter((String name)->{return name.startsWith("张");}); //筛选出新的流
stream2.forEach(name->System.out.println(name)); //张翠山,张三丰,张无忌
/*
Stream是一个管道流,只能被消费(使用)一次
第一个Stream流调用完方法,数据就会流转到下一个Stream流
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream就不能再调用方法了
*/
// stream.forEach(name->System.out.println(name)); stream has already been operated upon or closed
}
}
- map
如果需要将流中的元素映射到另一个流中,可以使用 map 方法
-
Stream map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。java.util.stream.Function 函数式接口,其中唯一的抽象方法为:R apply(T t);
这可以将一种T类型转换成为R类型,而这种 转换的动作,就称为“映射” 。
public class D06Function {
public static void main(String[] args) {
//获取一个String类型的Stream流
Stream<String> stream = Stream.of("1","2","3","4");
Stream<Integer> stream2 = stream.map((String s)->{
return Integer.parseInt(s);
});
stream2.forEach(in->System.out.println(in+1)); //2 3 4 5
}
}
- count(终结)
正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来统计Stream流中元素的个数。
long count():该方法返回一个long值代表元素个数(不再像旧集合那样是int值)
```java
public class D07Count {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Stream<Integer> stream = list.stream();
long count = stream.count();
System.out.println(count); //5
}
}
- limit
- limit 方法用于截取流中的元素,只取用前n个:Stream limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。 - limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法。
public class D08Limit {
public static void main(String[] args) {
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
//使用Stream流的limit方法对流中元素进行截取,只要前三个
Stream<String> stream = Stream.of(arr).limit(3);
stream.forEach(str->System.out.println(str)); //美羊羊、喜羊羊、懒羊羊
}
}
- skip
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:Stream skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
public class D09Skip {
public static void main(String[] args) {
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
//使用Stream流的limit方法对流中元素进行截取,只要前三个
Stream<String> stream = Stream.of(arr).skip(3);
stream.forEach(str->System.out.println(str)); //灰太狼、红太狼
}
}
需要截取前面的元素用limit,需要截取后面的元素则用skip(跳过)
- concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
static Stream concat(Stream<? extends T> a, Stream<? extends T> b)
public class D10concat {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("赵敏","张翠山","张三丰","周芷若","张无忌");
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream2 = Stream.of(arr);
//把以上两个流合并成一个流
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(name->System.out.println(name)); //包含两个流中所有元素
}
}
- 练习:集合元素处理
- 传统方式:
/*
现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以
下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建 Person 对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息。
*/
public class D11Test1 {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
ArrayList<String> one1 = new ArrayList<String>();
ArrayList<String> one2 = new ArrayList<String>();
for (String name : one) {
if(name.length()==3) {
one1.add(name);
}
}
for (int i = 0; i < 3; i++) {
one2.add(one1.get(i)); //0 1 2
}
//第二支队伍
ArrayList<String> two = new ArrayList<>();
ArrayList<String> two1 = new ArrayList<>();
ArrayList<String> two2 = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
for (String name : two) {
if(name.startsWith("张")){
two1.add(name);
}
}
for (int i = 2; i < two1.size(); i++) {
two2.add(two1.get(i)); //i 不包含0 1
}
//5 将两个队伍合并为一个队伍;存储到一个新集合中。
ArrayList<String> all = new ArrayList<String>();
all.addAll(one2);
all.addAll(two2);
//6 根据姓名创建 Person 对象;存储到一个新集合中。
ArrayList<Person> list = new ArrayList<>();
for (String name : all) {
list.add(new Person(name));
}
//打印整个队伍Person的对象信息
for (Person person : list) {
System.out.println(person);
}
}
}
- 使用Stream流
public class D11Test2 {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
Stream<String> oneStream = one.stream().filter(name->name.length()==3).limit(3);
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);
//合并流
Stream.concat(oneStream, twoStream).map(name -> new Person(name)).forEach(p -> System.out.println(p));
}
}