Java中的Stream流
一、 初识Stream流
Stream流是Java8后发布的一系列新特性中的一种,Stream流和我们以前学习过的IO流并不一样,Stream流的特性支持程序的开发人员以函数式的方式、更为简单高效的操作集合、数组等数据结构,大大提高了程序的效率和可阅读性。
1.1、为什么要引入Stream流,它的好处在哪?
我们以前学习过的集合框架中的两大接口:Collection和Map都支持直接或间接的遍历操作,我们对集合中的元素进行访问时,无非就是遍历、增删改查几种操作,而其中最常用到的就是集合的遍历。但是当我们要对一个集合进行多步操作时,这种循环遍历的方式就显得比较臃肿。
看下面这个案例:
/**
* 使用以前学习的集合遍历对名字进行筛选
*/
import java.util.ArrayList;
/**
* 案例:存在一个五个人姓名的集合,
* 现在要做的操作:
* 1、将集合中名字以德开开始的人添加到一个集合中
* 2、新集合中的名字长度大于4的名字在添加到另一个新集合中
* 3、遍历最后的新集合,查看其中的元素
*/
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("卡萨丁");
list1.add("卡莎");
list1.add("阿狸");
list1.add("德莱厄斯");
list1.add("德玛西亚皇子");
//把以德开头的名字挑出来,存储到一个新集合
ArrayList<String> list2 = new ArrayList<>();
for (String name : list1) {
if (name.startsWith("德")){
list2.add(name);
}
}
//把名字长度大于4的挑出来,存储到一个新集合
ArrayList<String> list3 = new ArrayList<>();
for (String name : list2) {
if (name.length() > 4){
list3.add(name);
}
}
//遍历最后得到的这个集合
for (String name : list3) {
System.out.println(name);
}
}
}
执行结果:
我们再使用Stream流对上面的案例进行实现:
代码:
import java.util.ArrayList;
import java.util.stream.Stream;
/**
* 使用Stream实现对集合元素的筛选和遍历
*/
public class StreamDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("卡萨丁");
list.add("卡莎");
list.add("阿狸");
list.add("德莱厄斯");
list.add("德玛西亚皇子");
/*Java8 Colleation新加的方法:
default Stream<E> stream()
返回一个序列 Stream与集合的来源。*/
list.stream()
//挑选符合条件的元素
.filter(name -> name.startsWith("德"))
.filter(name -> name.length() > 4)
//遍历得到的结果
.forEach((name)-> System.out.println(name));
}
}
执行结果:
我们可以看到使用Stream流的方式实现上述案例,代码中并没有体现出多次循环的遍历,而是把要做的操作交给了Stream去做。
二、什么是流式思想 ?
流式思想就类似于工厂的 “生产流水线” 。
当你要对多个元素进行相同的多步操作时,应该先拼接好一个操作的模型步骤,然后再按照这个模型去执行。
就拿Stream来说:
上图就是Stream中的过滤、映射、跳过、计数多步操作,这就是一种处理集合元素的模型,图中的每一个矩形都是一个 “流” ,调用指定的方法,可以从一个流模型转换到另一个模型。
上面的filter、map、skip 都是在对模型进行操作,但执行这些模型的是时候,集合元素并没有被真正的处理,只有当终结方法count执行的时候,整个模型才会按照流程去执行,这些延迟操作的原因是来自Lambda表达式的特性。
三、Stream流的特性
3.1 Stream流 其实是一个集合元素的函数模型,它不是集合也不是基本的数据结构,本身不存储任何的元素。
3.2 Stream流是一个来自数据源的元素队列
1、元素是特定类型的对象,形成一个队列;
2、数据源可以是 数组、集合等。
3.3 Stream流的两个基础特性
1、Pipelining:中间操作都会返回流对象的本身,这样的话多个操作就会形成一个类似于生产链的模型,就像流式风格,这么做的好处就是
可以对操作进行优化,比如延迟执行或者短路。
2、forEach:对Collection接口中的集合进行遍历的话可以通过迭代器或者for循环进行遍历,这样显式的在集合外部进行的遍历,被叫做外
部迭代,而Stream流内部提供了内部迭代的方法,流可以直接调用遍历的方法。
3.4 使用Stream流的一般步骤:
1、获取一个数据源;
2、对数据进行转换;
3、执行操作获取结果。
每次执行数据转换原有的 Stream 对象不变,返回一个新的 Stream 对象,这就允许操作才能像生产车间一样的函数模型。
四、获取流的方式
方式1:通过 Collection 接口的默认方法获取:
default Stream<E> stream()
返回一个序列 Stream与集合的来源。
方式2:通过 Stream 接口的静态方法 of 获取
static <T> Stream<T> of(T... values) 参数为可变参,可以传数组
返回一个元素为指定值的顺序排列的流。
static <T> Stream<T> of(T t)
返回一个包含一个元素的顺序 Stream。
代码演示:
import java.util.*;
import java.util.stream.Stream;
/**
* 获取Stream流的方式
*/
public class StreamDemo1 {
public static void main(String[] args) {
//把集合转换为流
//1、通过Collection接口中的集合获取
ArrayList<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
HashSet<Integer> set = new HashSet<>();
Stream<Integer> stream2 = set.stream();
//2、通过Map获取到键和值,在获取Stream
HashMap<Integer, String> hashMap = new HashMap<>();
Set<Integer> keySet = hashMap.keySet();
Stream<Integer> stream3 = keySet.stream();
Collection<String> values = hashMap.values();
Stream<String> stream4 = values.stream();
Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
Stream<Map.Entry<Integer, String>> stream5 = entries.stream();
//把数组转换为Stream
Stream<Integer> stream6 = Stream.of(1, 2, 3);
String[] strArr = {"a","ab","abc"};
Stream<String> stream7 = Stream.of(strArr);
}
}
Stream流中的常用方法:
Stream 流中的方法可以被分为两种:延迟方法和终结方法
● 延迟方法 :方法的返回值类型还是 Stream 类型,所以才支持链式调用。
● 终结方法 :方法的返回值类型不是 Stream 类型,所以不再支持链式调用。
方法 forEach: 用来遍历流中的数据,是终结方法。
void forEach(Consumer<? super T> action):方法的参数是一个Consumer接口,可以使用Lambda表达式。
代码演示:
import java.util.stream.Stream;
public class StreamDemo2 {
public static void main(String[] args) {
String[] strArr = {"Harden", "Curry", "Kobe", "Durant"};
Stream<String> stream = Stream.of(strArr);
stream.forEach((String name)->{
System.out.println(name);
});
}
}
执行结果:
方法 filter: 将一个流转换为另一个流
Stream<T> filter(Predicate<? super T> predicate) :参数是函数式接口,可使用Lambda表达式
返回由该流的元素组成的流,该元素与给定的谓词匹配。
代码演示:
import java.util.stream.Stream;
public class StreamDemo3 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Lebron", "Daniels", "Davis", "Pope", "Caruso");
Stream<String> stream1 = stream.filter((String name) -> {
return name.startsWith("D");
});
stream1.forEach(name -> System.out.println(name));
}
}
在这里我们要注意的:Stream属于管道流,只能使用一次,像上面的代码中,如果第一个Stream流调用方法完毕,流中的数据就会转移到下一个流中,这时间第一个Stream流就已经关闭了,不能再被使用了。
执行结果:
方法 map: 将一个流中的数据映射到另一个流中
<R> Stream<R> map(Funcation<? super T,? extends R> mapper):
该接口需要一个Function函数式接口参数,可以将当前流中的 T 类型数据转换为另一种 R 类型的流。
代码演示:
import java.util.stream.Stream;
public class StreamDemo4 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
Stream<Integer> stream1 = stream.map((String s) -> {
return Integer.parseInt(s);
});
stream1.forEach((integer -> System.out.println(integer)));
}
}
执行结果:
终结方法 count:统计个数
long count(): 用来统计流中的数据个数
代码演示:
import java.util.stream.Stream;
public class StreamDemo5 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 5, 4, 6, 9, 8, 7);
Stream<Integer> stream1 = stream.filter((Integer integer) -> {
//筛选出比5大的元素
return integer > 5;
});
long count = stream1.count();
System.out.println(count);
}
}
执行结果:
方法 limit:用于截取流中的数据
Stream<T> limit(long maxSize)
返回一个包含该流的元素流,截断长度不超过 maxSize。
代码演示:
import java.util.ArrayList;
import java.util.stream.Stream;
public class StreamDemo6 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
list.add("zhaoliu");
Stream<String> stream = list.stream();
Stream<String> stream1 = stream.limit(2);
stream1.forEach(name-> System.out.println(name));
}
}
执行结果:
方法 skip:跳过流中的前几个元素
Stream<T> skip(long n)
返回一个包含此流的其余部分丢弃的流的第一 n元素后流。如果这个流包含少于 n元素然后将返回空流。
代码演示:
import java.util.stream.Stream;
public class StreamDemo7 {
public static void main(String[] args) {
String[] strings = {"a", "b", "c", "d", "e"};
Stream<String> stream = Stream.of(strings);
Stream<String> stream1 = stream.skip(3);
stream1.forEach(s-> System.out.println(s));
}
}
执行结果:
方法 concat:把流连接到一起
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
创建一个懒洋洋的级联流的元素的所有元素的第一流通过第二个流的元素。将得到的流排序,
如果这两个输入流的命令,和并行,如果任一的输入流是平行的。当所产生的流被关闭时,两个输入流的关闭处理程序被调用。
代码演示:
import java.util.stream.Stream;
public class StreamDemo8 {
public static void main(String[] args) {
String[] strings1 = {"张三", "李四", "王五", "旺财", "大黄"};
String[] strings2 = {"A", "B", "C"};
Stream<String> stream1 = Stream.of(strings1);
Stream<String> stream2 = Stream.of(strings2);
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(s -> System.out.println(s));
}
}
执行结果:
Stream流的用法基本就是以上这些,Java中常用的函数式接口见上篇。