目录
说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
Stream流是用在集合上面
几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,典型的就是集合遍历。
循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行 了对比说明。现在,我们仔细体会一下上例代码,可以发现:
for循环的语法就是“怎么做” for循环的循环体才是“做什么”
为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从 第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
试想一下,如果希望对集合中的元素进行筛选过滤:
1. 将集合A根据条件一过滤为子集B; 2. 然后再根据条件二过滤为子集C。
import java.util.ArrayList;
import java.util.List;
/**
* Created by asus on 2020/6/10.
*/
//现在有一个需求,就是1,找出名字姓张的,名字的长度是3,将名字输出到控制台
public class StreamTest {
public static void getName()
{
List<String>list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//一般的做法是使用多次for循环进行筛选
List<String>list1=new ArrayList<>();
List<String>list2=new ArrayList<>();
for(String s:list)
{
if(s.startsWith("张"))
{
list1.add(s);
}
}
for(String n:list1)
{
if(n.length()==3)
{
list2.add(n);
}
}
for(String name:list2)
{
System.out.println(name);
}
}
public static void main(String[] args) {
getName();
}
}
这段代码中含有三个循环,每一个作用不同:
1. 首先筛选所有姓张的人; 2. 然后筛选名字有三个字的人; 3. 后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循 环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
Stream的更优写法
import java.util.ArrayList;
import java.util.List;
/**
* Created by asus on 2020/6/10.
*/
//现在有一个需求,就是1,找出名字姓张的,名字的长度是3,将名字输出到控制台
public class StreamTest {
public static void getName()
{
List<String>list=new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//一般的做法是使用多次for循环进行筛选
List<String>list1=new ArrayList<>();
List<String>list2=new ArrayList<>();
for(String s:list)
{
if(s.startsWith("张"))
{
list1.add(s);
}
}
for(String n:list1)
{
if(n.length()==3)
{
list2.add(n);
}
}
for(String name:list2)
{
System.out.println(name);
}
//这里我们可以使用Stream流的方式
list.stream()
.filter((name)->name.startsWith("张"))
.filter((name)->name.length()==3)
.forEach((name)-> System.out.println(name));
}
public static void main(String[] args) {
getName();
}
}
流式思想概述
整体来看,流式思想类似于工厂车间的“生产流水线”。
当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤 方案,然后再按照方案去执行它。
这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模 型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而右侧的数字 3是终结果。
这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count 或者forEach执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。
获取流
java.util.stream.Stream<T> 是Java 8新加入的常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
- 所有的 Collection 集合都可以通过 stream 默认方法获取流;
- Stream 接口的静态方法 of 可以获取数组对应的流。
import java.util.*;
import java.util.stream.Stream;
/**
* Created by asus on 2020/6/10.
*/
/*
java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
- 所有的Collection集合都可以通过stream默认方法获取流;
default Stream<E> stream()
- Stream接口的静态方法of可以获取数组对应的流。
static <T> Stream<T> of(T... values)
参数是一个可变参数,那么我们就可以传递一个数组
*/
public class GetStream {
public static void main(String[] args) {
//把集合转换为stream流
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> stream2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,存储到一个Set集合中
Set<String> keySet = map.keySet();
Stream<String> stream3= keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射关系entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5, 6);
//可变参数可以传递数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
String[] arr1 = {"a","b"};
Stream<Object> stream8 = Stream.of(arr1);
}
}
延迟方法
1.filter(过滤)
package com.qjx.functioninterface;
import java.util.stream.Stream;
/*
过滤筛选出偶数
*/
public class FilterTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7);
Stream<Integer> stream1 = stream.filter(num -> num % 2 == 0);
stream1.forEach(num-> System.out.println(num));
}
}
2.map(映射)
(对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素)
如果需要将流中的元素映射到另一个流中,可以使用map方法
import java.util.stream.Stream;
/**
* Created by asus on 2020/6/11.
*/
/*
Stream流中的常用方法_map:用于类型转换
如果需要将流中的元素映射到另一个流中,可以使用map方法.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Function中的抽象方法:
R apply(T t);
*/
//这里我们将String类型的数据转换为整数类型的
public class StreamMap {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("1", "2", "3", "4");
Stream<Integer> stream2 = stream1.map((String s) -> Integer.parseInt(s));
stream2.forEach((Integer n)-> System.out.println(n));
}
}
3.limit
只取前几个元素
package com.qjx.functioninterface;
import java.util.stream.Stream;
/*
只取前三个数
*/
public class LimitTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1","2","3","4","5");
Stream<String> limit = stream.limit(3);
limit.forEach(num-> System.out.println(num));
}
}
4.skip
跳过几个元素
package com.qjx.functioninterface;
import java.util.stream.Stream;
/*
跳过前三个数字,只要后面两个
*/
/*
Stream流中的常用方法_skip:用于跳过元素
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
*/
public class SkipTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1","2","3","4","5");
Stream<String> skip = stream.skip(3);
skip.forEach(num-> System.out.println(num));
}
}
5.concat
将两个流进行合并
package com.qjx.functioninterface;
import java.util.stream.Stream;
/*
Stream流中的常用方法_concat:用于把流组合到一起
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
*/
public class ConcatTest {
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.of(6, 7, 8, 9, 10);
Stream<Integer> stream = Stream.concat(stream1, stream2);
stream.forEach(num-> System.out.println(num));
}
}
6.distinct
去除重复元素
package com.qjx.functioninterface;
import java.util.stream.Stream;
public class DistantTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 1, 2, 3, 4, 11, 1, 2);
Stream<Integer> distinct = stream.distinct();
distinct.forEach(num-> System.out.println(num));
}
}
7.sorted(排序)
package com.qjx.functioninterface;
import java.util.stream.Stream;
public class SortedTest {
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7);
//正序
Stream<Integer> sorted1 = stream1.sorted(((o1, o2) -> o1.compareTo(o2)));
sorted1.forEach(num-> System.out.println(num));
//结果是 1 2 3 4 5 6 7
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6, 7);
//倒序
Stream<Integer> sorted2 = stream2.sorted(((o1, o2) -> o2.compareTo(o1)));
sorted2.forEach(num-> System.out.println(num));
//结果是 7 6 5 4 3 2 1
}
}
终结方法
1.count(计数)
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Created by asus on 2020/6/11.
*/
/*
Stream流中的常用方法_count:用于统计Stream流中元素的个数
long count();
count方法是一个终结方法,返回值是一个long类型的整数
所以不能再继续调用Stream流中的其他方法了
*/
public class StreamCount {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Stream<Integer> stream = list.stream();
System.out.println(stream.count());
}
}
2.forEach
public static void main(String[] args) {
//forEach方法用来遍历流中的数据
Stream<String> stream = Stream.of("张三","李四","王五");
stream.forEach(name-> System.out.println(name));
}
3.collect
package com.qjx.functioninterface;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CollectTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7);
List<Integer> collect = stream.collect(Collectors.toList());
System.out.println(collect);
//[1, 2, 3, 4, 5, 6, 7]
}
}
案例
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建Person对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息。
/**
* Created by asus on 2020/6/11.
*/
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
传统模式
package com.itheima.demo03.Stream;
import java.util.ArrayList;
/*
练习:集合元素处理(传统方式)
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建Person对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息。
*/
public class Demo01StreamTest {
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("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
ArrayList<String> one1 = new ArrayList<>();
for (String name : one) {
if(name.length()==3){
one1.add(name);
}
}
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
ArrayList<String> one2 = new ArrayList<>();
for (int i = 0; i <3 ; i++) {
one2.add(one1.get(i));//i = 0,1,2
}
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
ArrayList<String> two1 = new ArrayList<>();
for (String name : two) {
if(name.startsWith("张")){
two1.add(name);
}
}
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
ArrayList<String> two2 = new ArrayList<>();
for (int i = 2; i <two1.size() ; i++) {
two2.add(two1.get(i)); //i 不包含0 1
}
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
ArrayList<String> all = new ArrayList<>();
all.addAll(one2);
all.addAll(two2);
//6. 根据姓名创建Person对象;存储到一个新集合中。
ArrayList<Person> list = new ArrayList<>();
for (String name : all) {
list.add(new Person(name));
}
//7. 打印整个队伍的Person对象信息。
for (Person person : list) {
System.out.println(person);
}
}
}
Stream流的模式
package com.itheima.demo03.Stream;
import java.util.ArrayList;
import java.util.stream.Stream;
/*
练习:集合元素处理(Stream方式)
将上一题当中的传统for循环写法更换为Stream流式处理方式。
两个集合的初始内容不变,Person类的定义也不变。
*/
public class Demo02StreamTest {
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("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
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("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
//6. 根据姓名创建Person对象;存储到一个新集合中。
//7. 打印整个队伍的Person对象信息。
Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));
}
}