文章目录
Stream流 JDK 1.8
说到Stream容易想到 IO Stream,而流不一定是IO流。在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库有的弊端。
引言 :通过案例展示Stream流的作用
需求分析:
- 在ArrayList集合中,筛选出姓张的数据;
- 从筛选出姓张的数据中,再筛选出名字长度为3;
- 将结果打印出来。
普通操作:
public class PrefaceNormal {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("张杰");
arrayList.add("张还行");
arrayList.add("王小");
arrayList.add("张怡");
arrayList.add("唐山");
// 筛选姓张的
ArrayList<String> listA = new ArrayList<>();
for(String str: arrayList){
if(str.startsWith("张")){
listA.add(str);
}
}
// 筛选3个字的
ArrayList<String> listB = new ArrayList<>();
for(String str: arrayList){
if(str.length() == 3){
listB.add(str);
}
}
// 遍历结果
for (String str : listB){
System.out.println(str); // 输出:张还行
}
}
}
可见用了很多次增强for循环,代码量很多,很冗杂,不美观,效率低。
Stream操作:
public class PrefaceStream {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("张杰");
arrayList.add("张还行");
arrayList.add("王小");
arrayList.add("张怡");
arrayList.add("唐山");
arrayList.stream()
// Lambda实现Predicate接口中的test方法,返回Boolean值
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
// Lambda实现Customer接口中的accept方法
.forEach(name -> System.out.println(name));
// 不对原有的集合内容做改变,只是对符合要求的数据进行筛选
}
}
1、流式思想
这里的filter
、map
、skip
都是对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count
执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
备注:Stream流 其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身不存储任何元素(或地址值)
2、Stream特点
Stream是一个来数据源的元素队列
- 元素式特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算,使用一次之后,再次调用就会报异常。
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张还行", "heroC", "yikeX");
stream.filter(name -> name.startsWith("张"))
.forEach(name -> System.out.println(name));
// stream使用了一次了,再使用一次,会报IllegalStateException异常
// 说明之前的流已经关闭,不能再使用了。
stream.forEach(name -> System.out.println(name));
}
}
- 数据源 流的来源。可以是集合,数组等。
Stream操作两个基础的特征:
- Pipelining(流水线):中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路。
- 内部迭代:以前对集合遍历都是通过Iterator或者增强for的方式。显示的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源 → 数据转换 → 执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象,这就允许对其操作可以像链条一样排列,变成一个管道。
3、获取Stream流对象
java.util.stream Interface Stream<T>
接口
获取流的方式:
- 所有
Collection
集合都可以通过stream
默认方法获取流 stream
的静态方法of
可以获取对应的流,of方法是可变参数,可变参数底层就一数组
public class CollectionStream {
public static void main(String[] args) {
// List集合获取流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// Set集合获取流
Set<String> set = new HashSet<>();
Stream<String> stream1 = set.stream();
// 键获取流
Map<String,String> map1 = new HashMap<>();
Collection<String> collKey = map1.keySet();
Stream<String> stream2 = collKey.stream();
// 值获取流
Map<String,String> map2 = new HashMap<>();
Collection<String> collValue = map2.values();
Stream<String> stream3 = collValue.stream();
// 键值对获取流
Map<String,String> map3 = new HashMap<>();
Collection<Map.Entry<String, String>> entries = map3.entrySet();
Stream<Map.Entry<String, String>> stream4 = entries.stream();
// 流的静态方法of,可变参数获取流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
// 流的静态方法of,数组获取流
Integer[] num = {1,2,3,4};
Stream<Integer> num1 = Stream.of(num);
String[] str = {"a","b"};
Stream<String> str1 = Stream.of(str);
}
}
4、常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以分为两种:
- 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。
- 终结方法:返回类型不再是Stream接口自身类型的方法,因此不再支持链式调用。常见
count
和forEach
方法,更多见API文档。
逐一处理:forEach
forEach与增强for是不同的
void forEach(Consumer<? super T> action)
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张还行", "heroC", "yikeX");
stream.forEach(name -> System.out.println(name)); // 对流中的每一个数据进行处理
}
}
过滤:filter
可通过filter将一个流转换成另一个流
Stream<T> filter(Predicate<? super T> predicate)
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张还行", "heroC", "yikeX");
stream.filter(name -> name.startsWith("张")) // 可以将流过滤转换成另一个只符合要求的流
.forEach(name -> System.out.println(name)); // 对新的流每个数据进行遍历处理
}
}
// 输出:张还行
映射:map
将流中的方法映射到另一个流中
<R> Stream<R> map(Function<? super T,? extends R> mapper)
将T类型
的数据转换成R类型
的数据就是“映射”。
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4");
Stream<Integer> stream1 = stream.map(str -> Integer.parseInt(str));
stream1.forEach(num -> System.out.print(num+" "));
}
}
// 输出:1 2 3 4
统计个数:count
long count()
返回long类型数据,统计流中元素的个数
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "12", "23", "1234");
long count = stream.filter(str -> str.startsWith("1")).count();
System.out.println(count);
}
}
// 输出:3
取用前几个:limit
limit方法可以对流中的数据进行截取,只取用前n个。
Stream<T> limit(long maxSize)
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "12", "23", "1234");
stream.limit(3).forEach(str -> System.out.print(str+" "));
}
}
// 输出:1 12 23
跳过前几个:skip
如果流的当前长度大于n,则跳过前n个元素;否则将会得到一个长度为0的空流。
Stream<T> skip(long n)
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "12", "23", "1234");
stream.skip(2).forEach(str-> System.out.print(str+" "));
}
}
// 输出:23 1234
组合:concat
将有两个流,合并成一个流
static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T> b)
备注:这是一个静态方法,与
java.lang.String
当中的concat
方法是不同的。
public class PrefaceStream {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("heroC");
Stream<String> stream2 = Stream.of("yikeX");
Stream.concat(stream1,stream2).forEach(str -> System.out.print(str+" "));
}
}
// 输出:heroC yikeX
练习:集合元素的处理
题目:
现在有两个ArrayList集合存储队伍当中的多个成员姓名,使用Stream流完成以下操作:
- 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
- 第一个队伍筛选后只要前3个人名;存储到一个新集合中。
- 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
- 第二个队伍筛选之后不要前2个人名;存储到一个新集合中。
- 将两个筛选后的队伍合并为一个队伍;存储到一个新集合中。
- 根据姓名创建
Person
对象;存储到一个新集合中。 - 打印整个队伍的
Person
对象信息。
public class StreamExample {
public static void main(String[] args) {
ArrayList<String> one = new ArrayList<>();
one.add("张还行");
one.add("张还");
one.add("张行");
one.add("施柏宇");
one.add("林俊杰");
one.add("张一山");
// 1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
Collection<String> nameThree = new ArrayList<>();
one.stream().filter(name -> name.length()==3).forEach(name -> nameThree.add(name));
// 2. 第一个队伍筛选后只要前3个人名;存储到一个新集合中。
Collection<String> nameThird = new ArrayList<>();
nameThree.stream().limit(3).forEach(name -> nameThird.add(name));
ArrayList<String> two = new ArrayList<>();
two.add("张一山");
two.add("黄轩");
two.add("张行");
two.add("张若昀");
two.add("罗晋");
two.add("张子枫");
// 3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
Collection<String> nameZhang = new ArrayList<>();
two.stream().filter(name -> name.startsWith("张")).forEach(name -> nameZhang.add(name));
// 4. 第二个队伍筛选之后不要前2个人名;存储到一个新集合中。
Collection<String> nameSkip = new ArrayList<>();
nameZhang.stream().skip(2).forEach(name -> nameSkip.add(name));
// 5. 将两个筛选后的队伍合并为一个队伍;存储到一个新集合中。
Collection<String> nameConcat = new ArrayList<>();
Stream.concat(nameThird.stream(), nameSkip.stream()).forEach(name -> nameConcat.add(name));
// 6. 根据姓名创建`Person`对象;存储到一个新集合中。
Collection<Person> namePerson = new ArrayList<>();
nameConcat.stream().forEach(name -> namePerson.add(new Person(name)));
// 7. 打印整个队伍的`Person`对象信息。
long count = namePerson.stream().count();
System.out.println("筛选后的队伍总人数为:"+count);
namePerson.stream().forEach(namePer -> System.out.println(namePer));
}
}
输出:
筛选后的队伍总人数为:5
Person{name='张还行'}
Person{name='施柏宇'}
Person{name='林俊杰'}
Person{name='张若昀'}
Person{name='张子枫'}