Stream流
前言
Stream流初体验
什么是Stream流呢?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
数据源
:流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。聚合操作
:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
来个小案例,初步体验一下Stream流
package com.features.Stream;
// JDK8新特性 --- Stream
// Stream流来处理集合类的问题
import java.util.Arrays;
import java.util.List;
// 打印所有姓张的且名字是三位数的所有人的信息
// 打印所有姓李的且名字是两位数的所有人的信息
public class StreamTest02 {
public static void main(String[] args) {
List<String> list = Arrays.asList("张三", "李四", "李五", "张飒飒");
System.out.println("姓张的且名字是三位数的人:");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
System.out.println("姓李的且名字是两位数的人:");
list.stream()
.filter(s -> s.startsWith("李"))
.filter(s -> s.length()==2)
.forEach(System.out::println);
}
}
复制代码
结果:
姓张的且名字是三位数的人:
张飒飒
姓李的且名字是两位数的人:
李四
李五
复制代码
Stream流采用链式写法,其中:
- stream 获取流
- filter 过滤
- forEach 打印
我们点进去Stream的源码可以发现,它包含了以下这些方法,Stream流的操作和这些方法密切相关
当然,在使用Stream流之前,我们要先学会怎么去获取它
Stream流的获取
Stream的获取方式:通过collection接口获取
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
复制代码
因为java.util.Collection接口中加入了default方法stream,也就是说所有的实现了Collection接口下的类都可以通过steam方法来获取Stream流。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.stream();
Set<String> set = new HashSet<>();
set.stream();
Vector<String> vector = new Vector<>();
vector.stream();
Map map = new HashMap<String,Integer>();
// map.stream(); 报错
// map 没有实现collection接口,但是它可以通过下面这种方式来获取对应的key,value集合
map.keySet().stream(); // key集合
map.values().stream(); // value集合
map.entrySet().stream(); // entry集合
Hashtable<String, Integer> hashtable = new Hashtable<>();
//hashtable.stream(); 报错
// hashtable和map同理
hashtable.keySet().stream(); // key集合
hashtable.values().stream(); // value集合
hashtable.entrySet().stream(); // entry集合
复制代码
注意:
- list,set,vector实现了collection接口,map和hashtable没有
- map和hashtable要想获取Stream流,就必须把key和value分开,就可以得到对应的Stream流
Stream的获取方式:通过Stream的of方法
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
复制代码
在实际开发中我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("1", "2", "3", "4", "5");
System.out.println("String类型的数组:");
stream1.forEach(System.out::print);
String[] s = {"a","b","c","d","e"};
Stream<String> stream2 = Stream.of(s);
System.out.println("\n"+"String类型的数组:");
stream2.forEach(System.out::print);
Integer[] i = {1,2,3,4,5,6,7};
Stream<Integer> stream3 = Stream.of(i);
System.out.println("\n"+"Integer类型的数组:");
stream3.forEach(System.out::print);
int[] a = {1,2,3,4,5,6,7};
Stream<int[]> stream4 = Stream.of(a);
System.out.println("\n"+"int类型的数组:");
// 得到异常结果 --> 基本数据类型的数组不能正确获取到stream流
stream4.forEach(System.out::print);
}
复制代码
注意:基本数据类型的数组不能正确获取到stream流
学会了如何获取Stream流,我们现在来看一下他们的用法
Stream流的方法详解
forEach
遍历流中的数据
void forEach(Consumer<? super T> action);
复制代码
使用:
public static void main(String[] args) {
Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.forEach(System.out::println);
}
复制代码
结果:
a
b
c
d
a
b
a
c
复制代码
filter
用来过滤数据,返回符合条件的数据。通过filter方法将一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
复制代码
使用:
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.filter(s -> !s.equals("a"));
stream.forEach(System.out::println);
}
复制代码
结果:
b
c
d
b
c
复制代码
limit
对流进行截取处理,截取前n个数据
Stream<T> limit(long maxSize);
复制代码
使用:
public static void main(String[] args) {
Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.limit(5)
//.limit(10)
//.limit(-1) 报错
.forEach(System.out::println);
}
复制代码
结果:
a
b
c
d
a
复制代码
注意:
- 当n大于等于流中的数据个数时,截取全部数据。
- 当n小于0时,报错
skip
跳过前面n个元素,进行截取
Stream<T> skip(long n);
复制代码
使用:
public static void main(String[] args) {
Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.skip(5)
//.skip(-1) 报错
//.skip(10)
.forEach(System.out::println);
}
复制代码
结果:
b
a
c
复制代码
注意:
- 当n大于等于流中的数据个数时,没有数据被截取
- 当n小于0时,报错
count
用来计算元素的个数,返回一个long类型的值,代表元素的个数
long count();
复制代码
使用:
public static void main(String[] args) {
long count = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.count();
System.out.println("元素的个数为:"+count);
}
复制代码
结果:
元素的个数为:8
复制代码
concat
将两个流合并成一个流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
复制代码
使用:
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<String> stream2 = Stream.of("1", "2", "3");
Stream.concat(stream1,stream2)
.forEach(System.out::println);
}
复制代码
结果:
a
b
c
1
2
3
复制代码
distinct
除去重复的元素
Stream<T> distinct();
复制代码
使用:
public static void main(String[] args) {
Stream<String> distinct = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.distinct();
distinct.forEach(System.out::println);
// 需要在Student类中重写equals和hashcode方法,才能去掉重复的数据
Stream.of(new Student("张三",18),
new Student("李四",20),
new Student("张三",18),
new Student("王五",22),
new Student("李四",20))
.distinct()
.forEach(System.out::println);
}
复制代码
结果:
a
b
c
d
Student{name='张三', age=18}
Student{name='李四', age=20}
Student{name='王五', age=22}
复制代码
注意:
- 基本数据类型的数据,可以直接去重
- 如果是引用类型的,需要重写equals和hashcode方法
如这一题中的Student类为:
public class Student {
private String name;
private int age;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 重写了equals和hashcode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
复制代码
find
找到指定元素
Optional<T> findFirst();//找到第一个元素
Optional<T> findAny();//返回的元素是不确定的,对于同一个列表多次调用findAny()有可能会返回不同的值。使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,如果是并行的情况,那就不能确保是第一个
复制代码
使用:
public static void main(String[] args) {
Optional<String> first = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.findFirst();
System.out.println(first.get());
Optional<String> any = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
.findAny();
System.out.println(any.get());
}
复制代码
结果:
a
a
复制代码
max,min
max:获取最大值
Optional<T> max(Comparator<? super T> comparator);
复制代码
min:获取最小值
Optional<T> min(Comparator<? super T> comparator);
复制代码
使用:
public static void main(String[] args) {
Optional<Integer> max = Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.max(Comparator.comparingInt(o -> o));
// .max((o1,o2)->o1-o2);等价
System.out.println("max = "+max.get());
Optional<Integer> min = Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.min((o1,o2)->o1-o2);
System.out.println("min = "+min.get());
}
复制代码
结果:
max = 5
min = 1
复制代码
map
将流中的元素映射到另一个流中,该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
复制代码
使用:
public static void main(String[] args) {
Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.forEach(System.out::println);
Stream.of(6,7,8,9,10)
.map(String::valueOf)
.forEach(System.out::println);
}
复制代码
结果:
1
2
3
4
5
6
7
8
9
10
复制代码
reduce
将所有数据归纳得到一个数据
T reduce(T identity, BinaryOperator<T> accumulator);
复制代码
使用:
public static void main(String[] args) {
Integer sum = Stream.of("1", "2", "3", "4", "5","1","1","2")
.map(Integer::parseInt)
// identity:默认值
// 第一次的时候会将默认值赋值给a
// 之后每次都会将上次的结果赋值给a,b接收流中的数据
.reduce(0, (a, b) -> {
System.out.print("a = " + a);
System.out.println(" b = " + b);
return a + b;
});
System.out.println("sum = " + sum);
// 获取最大值:
Integer max = Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.reduce(0, (a, b) -> a > b ? a : b);
//.reduce(1, Math::max);
System.out.println("max = "+max);
// 统计 1 出现的次数
Integer count = Stream.of("1", "2", "3", "4", "5", "1", "1", "2")
.map(s -> "1".equals(s) ? 1 : 0)
.reduce(0, Integer::sum);
System.out.println(count);
}
复制代码
结果:
a = 0 b = 1
a = 1 b = 2
a = 3 b = 3
a = 6 b = 4
a = 10 b = 5
a = 15 b = 1
a = 16 b = 1
a = 17 b = 2
sum = 19
max = 5
3
复制代码
maptoInt
将流中的Integer类型转换为int类型
IntStream mapToInt(ToIntFunction<? super T> mapper);
复制代码
使用:
public static void main(String[] args) {
Integer[] a = {1,2,6,4,2};
// Integer类型占用的内存比int多,流中会自动装箱和拆箱
Stream.of(a)
.distinct()
.forEach(System.out::println);
System.out.println("============");
// 但是为了效率,我们先将Integer转换为int
Stream.of(a)
.mapToInt(Integer::intValue)
.distinct()
.forEach(System.out::println);
}
复制代码
结果:
1
2
6
4
============
1
2
6
4
复制代码
match
判断数据是否符合匹配指定的条件 是一个终结方法
boolean allMatch(Predicate<? super T> predicate);//全部匹配才为true
boolean noneMatch(Predicate<? super T> predicate);//只要有一个匹配就为true
boolean anyMatch(Predicate<? super T> predicate);//取反操作,true->false,false->true
复制代码
使用:
public static void main(String[] args) {
boolean b = Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.allMatch(s -> s > 0);
System.out.println(b);
boolean b1 = Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.anyMatch(s -> s > 4);
System.out.println(b1);
boolean b2 = Stream.of("1", "2", "3", "4", "5")
.map(Integer::parseInt)
.noneMatch(s->s>5);
System.out.println(b2);
}
复制代码
结果:
true
true
true
复制代码
到了这里,看到这么多方法,你可以有点晕了,迷糊了,没事,下面我们来一个综合案例,来综合使用一下这些方法
综合案例
定义两个集合,然后在集合中存储多个用户名称。然后完成如下的操作:
- 第一个队伍只保留姓名长度为3的成员
- 第一个队伍筛选之后只要前3个人
- 第二个队伍只要姓张的成员
- 第二个队伍筛选之后不要前两个人
- 将两个队伍合并为一个队伍
- 根据姓名创建Student对象
- 打印整个队伍的Student信息
实现:
public static void main(String[] args) {
List<String> list1 = Arrays.asList("张无忌","张三丰","金毛狮王","虚竹","段誉");
List<String> list2 = Arrays.asList("杨过","郭靖","黄蓉","黄老邪","小龙女");
Stream<String> stream1 = list1.stream()
.filter(s -> s.length() == 3)//第一个队伍只保留姓名长度为3的成员
.limit(3);//第一个队伍筛选之后只要前3个人
Stream<String> stream2 = list2.stream()
.filter(s -> s.startsWith("张"))//第二个队伍只要姓张的成员
.skip(2);//第二个队伍筛选之后不要前两个人
//将两个队伍合并为一个队伍
Stream<String> stream = Stream.concat(stream1, stream2);
stream.map(Student::new) // 根据姓名创建Student对象 (Student中要有一个只要name属性的构造器)
.forEach(System.out::println);// 打印整个队伍的Student信息
}
复制代码
结果:
Student{name='张无忌', age=0}
Student{name='张三丰', age=0}
复制代码