Stream是Java8中处理集合的关键抽象概念,它可以指定希望对集合的操作,可以执行复杂的查找、过滤和映射数据等操作。
(1)、实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
(2)、Stream和 Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
使用Stream API 对集合的数据进行操作,类似于SQL执行的数据库查询,也可以用来并行执行操作,其提供了一种高效且易于使用的处理数据方式。
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,Stream讲的是计算
!
注意:
(1)创建Stream
一个数据源(如:集合、数组〉,获取一个流
(2)中间操作
一个中间操作链,对数据源的数据进行处理3-终止操作(终端操作)
(3)终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
Java8中的Collection接口被扩展,提供了两个获取流的方法:
Java8中的Arrays的静态方法 stream()可以获取数组流:
static Stream stream(T array):返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
可以调用Stream类静态方法 of(),通过显示值创建一个流。它可以接收任意数量的参数。
可以使用静态方法Stream.iterate()和 Stream.generate(),创建无限流。
public static Stream iterate(final T seed, final UnaryOperator f)
public static Streamgenerate(Supplier s)
@Test
public void test1(){
// 1、通过Collection集合提供的stream()或parallelStream()
List<String> list = new ArrayList<String>();
Stream<String> stream = list.parallelStream();
// 2、通过Arrays的静态方法stream()获取
Student[] students = new Student[10];
Stream<Student> stream2 = Arrays.stream(students);
// 3、通过Stream类的静态方法of()
Stream<String> stream3 = Stream.of("1","2");
// 4、创建无限流(迭代)
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x+2);
stream4.limit(5).forEach(System.out::println);
// 5、生成
Stream.generate(() -> Math.random()).limit(4).forEach(System.out::println);
}
package com.shenmazong.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* @author 军哥
* @version 1.0
* @description: 演示创建流的四种方式
* @date 2021/10/8 11:18
*/
class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class StreamApiTest {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student("武大郎", 50));
list.add(new Student("潘金莲", 19));
list.add(new Student("武松", 29));
list.add(new Student("西门庆", 35));
//--1 通过集合创建
// default Stream<E> stream():返回一个顺序流
Stream<Student> stream = list.stream();
// default Stream<E> parallelStream():返回一个并行流
Stream<Student> studentStream = list.parallelStream();
//--2 通过数据创建
Student[] students = {};
Stream<Student> stream1 = Arrays.stream(students);
//--3 通过Stream的of()函数创建
Stream<Integer> integerStream = Stream.of(1, 2, 3);
//--4 创建无限流
// 迭代:输出10个偶数
Stream<Integer> iterate = Stream.iterate(0, t -> t + 2);
iterate.limit(10).forEach(System.out::println);
// 生成:输出10个随机数
Stream<Double> generate = Stream.generate(Math::random);
generate.limit(10).forEach(System.out::println);
}
}
package com.shenmazong.lambda;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* @program: server-java-demo
* @description: 验证是否是延迟加载
* @author: 亮子说编程
* @create: 2020-10-19 16:33
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
class Student {
private String name;
private Integer age;
}
public class StreamApiTest {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
//
list.add(new Student("武大郎", 50));
list.add(new Student("潘金莲", 19));
list.add(new Student("武松", 29));
list.add(new Student("西门庆", 35));
//
Stream<Student> stream = list.stream().filter((i) -> {
System.out.println("验证是否是延迟加载");
return i.getAge() > 30;
});
//
System.out.println("=== end ===");
//stream.forEach(System.out::println);
//System.out.println("=== finish ===");
}
}
generate
创建无限流使用Stream类的静态方法 generate创建无限流
generate方法参数为Supplier 供给型接口
package com.shenmazong.lambda;
import java.util.Random;
import java.util.stream.Stream;
/**
* @program: server-java-demo
* @description: 演示创建无限流
* @author: 亮子说编程
* @create: 2020-10-19 16:33
**/
public class StreamApiTest2 {
public static void main(String[] args) {
//--1
Stream<Double> doubleStream = Stream.generate(() -> Math.random());
doubleStream.limit(5).forEach(System.out::println);
//--2
Stream<Integer> integerStream = Stream.generate(() -> new Random().nextInt(100));
integerStream.limit(5).forEach(System.out::println);
}
}
iterate
创建无限流public class StreamApiTest3 {
public static void main(String[] args) {
//使用Stream类的静态方法 iterate 创建无限流
Stream<Integer> stream = Stream.iterate(0, (x) -> x + 2);
//中间操作和终止操作
stream.limit(5).forEach(System.out::println);
}
}
of
创建有限流public class StreamApiTest3 {
public static void main(String[] args) {
Stream<String> stream3 = Stream.of("hxh", "aj", "hhh");
stream3.forEach(System.out::println);
}
}
流:是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列;
集合讲的是数据,流讲的是计算;
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
多个中间操作
可以连接起来形成一个流水线
,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
package com.shenmazong.stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @author 军哥
* @version 1.0
* @description: StreamAPI测试类
* @date 2021/10/8 19:44
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class Student {
private String name;
private Integer age;
}
public class StreamApiTest {
@Test
public void testFilter() {
// 初始化数据
List<Student> list = new ArrayList<>();
list.add(new Student("武大郎", 50));
list.add(new Student("潘金莲", 19));
list.add(new Student("武松", 29));
list.add(new Student("西门庆", 35));
list.add(new Student("西门庆", 35));
System.out.println("0 ==================");
// 筛选年龄大于30岁的人
list.stream().filter((s)->{
return s.getAge()>30;
}).forEach(System.out::println);
System.out.println("1 ==================");
// 筛选出1个年龄大于30岁的人
list.stream().limit(1).filter((s)->{
return s.getAge()>30;
}).forEach(System.out::println);
System.out.println("2 ==================");
// 跳过前2个元素,然后筛选出1个年龄大于30岁的人
// 这里需要注意skip函数的位置
list.stream().skip(2).limit(10).filter((s)->{
return s.getAge()>30;
}).forEach(System.out::println);
// 实现去重:如果是自定义对象需要实现hashCode()和equals()函数
System.out.println("3 ==================");
list.stream().distinct().forEach(System.out::println);
}
}
完善Student代码如下:
@Override
public boolean equals(Object obj) {
TbStudent o = (TbStudent)obj;
if(!this.getName().equals(o.getName())) {
return false;
}
if(!this.getAge().equals(o.getAge())) {
return false;
}
return true;
}
@Override
public int hashCode() {
return this.getName().hashCode()+this.getAge().hashCode();
}
一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选
择一列。Stream API也通过map和flatMap方法提供了类似的工具。
map方法接收一个函数作为函数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
1、下面实例,把每个字符串转换成大写字母,把所有的序列组成一个流
@Test
public void test4(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
/*map方法中接收一个Function类型的对象,该对象用Lambda表达式实现,输入一个字符串类型,返回一个字符串类型,并把返回的字符串放到流中*/
Stream<String> stream = list.stream()
.map((x) -> {
String s = x.toUpperCase();
return s;
});
stream.forEach(System.out::println);
}
运行上述方法,输出结果:
AAA
BBB
CCC
2、把每个字符串中的字符转化为大写字母,把每个序列转化为一个流
public static Stream<Character> filterString(String str){
List<Character> list = new ArrayList<Character>();
for(Character ch : str.toCharArray()){ //char[]
list.add(Character.toUpperCase(ch));
}
return list.stream();
}
@Test
public void test5(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
/*每个序列生成一个流,然后分别把每个流组成一个大流*/
Stream<Stream<Character>> stream = list.stream()
.map(TestStream::filterString);
/*遍历流中的流的每个序列*/
stream.forEach((s) -> {
s.forEach(System.out::println);
});
}
运行上述方法,输出结果:
A
A
A
B
B
B
C
C
C
3、映射后再筛选
@Test
public void testMap() {
//--1 字符转换为大写
List<String> list = Arrays.asList("aa", "bb", "cc");
list.stream().map((s)->{
return s.toUpperCase();
}).forEach(System.out::println);
//--2 筛选名字长度大于2的人
List<Student> students = new ArrayList<>();
students.add(new Student("武大郎", 50));
students.add(new Student("潘金莲", 19));
students.add(new Student("武松", 29));
students.add(new Student("西门庆", 35));
students.add(new Student("西门庆", 35));
Stream<String> nameStream = students.stream().map(Student::getName);
nameStream.filter((s) -> {return s.length()>2;}).forEach(System.out::println);
}
上面一个实例,把每个子流放入一个流中,相当于流中嵌套了一个流,如果用flatMap方法的话,会把每个子流中的元素组成一个流,然后遍历一个流就可以了,而不是遍历一个流中的每个流。
这个描述是非常难以理解的,但是我们可以通过list接口的add和addAll来理解,代码如下:
@Test
public void testArrayDemo() {
ArrayList<Object> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
ArrayList<Object> list2 = new ArrayList<>();
list2.add(4);
list2.add(5);
list2.add(6);
// list1.add(list2);
list1.addAll(list2);
System.out.println(list1);
}
下面是具体的例子:
public static Stream<Character> filterString(String str){
List<Character> list = new ArrayList<Character>();
for(Character ch : str.toCharArray()){ //char[]
list.add(Character.toUpperCase(ch));
}
return list.stream();
}
@Test
public void test6(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
Stream<Character> stream = list.stream()
.flatMap(TestStream::filterString);
stream.forEach(System.out::println);
}
运行上述方法,输出结果:
A
A
A
B
B
B
C
C
C
Stream通道流支持两种排除方式:sorted()自然排序;sorted(Comparator comp)。
sorted()自然排序 :产生一个新流,流中每个序列按自然排序的方式进行排序。
sorted(Comparator comp) :产生一个新流,流中的每个序列按自定义的比较器排序进行排序。
1、 sorted()自然排序
@Test
public void test1(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
Stream<String> stream = list.stream()
.sorted(); //把流中的每个序列进行自然排序
stream.forEach(System.out::println);
}
运行方法,输出结果如下
aaa
bbb
ccc
2、sorted(Comparator comp)定制排序
首先按用户的年龄进行比较,如果年龄相等,则按照用户的姓名进行比较。
@Test
public void test2(){
List<User> users = Arrays.asList(
new User("lzj", 28),
new User("zhangsan", 30),
new User("lisi", 28),
new User("wanger", 28));
Stream<User> stream = users.stream()
.sorted((x, y) -> { //使流中的序列两两进行比较
if (x.getAge() == y.getAge()) {
return x.getName().compareTo(y.getName());
}else{
return x.getAge() > y.getAge() ? 1 : -1;
}
});
stream.forEach(System.out::println);
}
Sorted方法接受一个Comparator类型的参数,实例用Lambda表达式进行实现了该接口,重写的该接口中的compare方法。
运行方法,输出结果如下:
User [name=lisi, age=28]
User [name=lzj, age=28]
User [name=wanger, age=28]
User [name=zhangsan, age=30]
终端操作会从通道流的流水线生成结果。其结果可以是任何不是流的值,例如int、List、void等。如果流操作后返回的值还是Stream流类型的,则是开始操作和中间操作。
有以下经常用到的查找与匹配操作:
下面进行示例演示,首先创建数据源为:
private static List<User> usrs = Arrays.asList(
new User("lzj", 20),
new User("zhangsan", 22),
new User("lisi", 25),
new User("suner", 30));
检查是否流中的所有User对象的年龄是否大于18.
@Test
public void test1(){
Boolean flag = usrs.stream()
.allMatch((x) -> x.getAge() > 18);
System.out.println(flag); //输出true
}
检查是否流中的User对象有没有年龄大于25岁的。
@Test
public void test2(){
Boolean flag = usrs.stream()
.anyMatch((x) -> x.getAge() > 25);
System.out.println(flag); //输出true
}
检查流中是否User类型的对象年龄都不大于35岁。
@Test
public void test3(){
Boolean flag = usrs.stream()
.noneMatch((x) -> x.getAge() > 35);
System.out.println(flag); //输出true
}
或得满足条件的流中的第一个元素并放到Optional容器中。
@Test
public void test4(){
Optional<User> user = usrs.stream()
.filter((x) -> x.getName().contains("s"))
.findFirst();
System.out.println(user.get());//输出:User [name=zhangsan, age=22]
}
过滤出姓名包含s字符的User类型对象,获取满足条件的任何一个对象,并放到Optional容器中。
@Test
public void test5(){
Optional<User> user = usrs.stream()
.filter((x) -> x.getName().contains("s"))
.findAny();
System.out.println(user.get());//输出:User [name=zhangsan, age=22]
}
过滤出名字包含s字符的所有User类型对象,并返回总个数。
@Test
public void test6(){
Long total = usrs.stream()
.filter((x) -> x.getName().contains("s"))
.count();
System.out.println(total);
}
获取年龄最大的那个User类型对象,并放到Optional容器中
@Test
public void test7(){
Optional<User> user = usrs.stream()
.max((x, y) -> x.getAge().compareTo(y.getAge()));
System.out.println(user);
}
获取年龄最小的User类型对象,并放置到Optional容器中
@Test
public void test8(){
Optional<User> user = usrs.stream()
.min((x, y) -> x.getAge().compareTo(y.getAge()));
System.out.println(user);
}
reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。
获取流中User对象年龄的总和。
/*reduce*/
@Test
public void test9(){
Integer total = usrs.stream()
.map(User::getAge)
.reduce(0, (x, y) -> x + y);
/*其中0、x、y和total的类型一致,都是Integer类型*/
System.out.println(total);//输出:97
}
开始流中放置的是每个user对象,经过map后,把每个user对象的年龄获取到放在流中,然后分别把年龄进行加起来,起始值是0,加总后的结果放在赋给total变量。
collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。
获取流中User类型对象的年龄,并把年龄重组成List类型
@Test
public void test10(){
List<String> names = usrs.stream()
.map(User::getName)
.collect(Collectors.toList());
System.out.println(names);
}
运行方法,输出结果:
[lzj, zhangsan, lisi, suner]