Stream流
- Stream流的好处
- 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义
- Stream流把真正的函数式编程风格引入到Java中
- 代码简洁
- Stream流的三类方法
- 获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作
- 一次操作完毕之后,还可以继续进行其他操作
- 终结方法
- 一个Stream流只能有一个终结方法
- 是流水线上的最后一个操作
- 生成Stream流的方式
- Collection体系集合
使用默认方法stream()生成流, default Stream<E> stream()
- Map体系集合
把Map转成Set集合,间接的生成流
- 数组
通过Arrays中的静态方法stream生成流
- 同种数据类型的多个数据
通过Stream接口的静态方法of(T... values)生成流
package yyeye;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.stream.Stream;
public class test1 {
public static void main(String[] args) {
//Collection体系的集合可以使用默认方法stream()生成流
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d", "e");
list.stream().forEach(s -> System.out.println(s));
System.out.println("--------------------------");
//Map体系的集合间接的生成流
HashMap<String, Integer> hm = new HashMap<>();
hm.put("a", 1);
hm.put("b", 2);
hm.put("c", 3);
hm.put("d", 4);
hm.put("e", 5);
hm.keySet().stream().forEach(System.out::print);
System.out.println();
hm.values().stream().forEach(System.out::print);
System.out.println();
hm.entrySet().stream().forEach(System.out::println);
System.out.println("--------------------------");
//数组可以通过Arrays中的静态方法stream生成流
int[] arr = {1, 2, 3, 4, 5};
String []arr2= {"a","b","c"};
Arrays.stream(arr).forEach(System.out::println);
System.out.println();
Arrays.stream(arr2).forEach(System.out::println);
System.out.println("--------------------------");
//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
Stream.of(1, 2, 3, 4, 5).forEach(System.out::println);
Stream.of("a", "b", "c").forEach(System.out::println);
//数组也可以直接用第四种方法
//有使用前提
//Stream接口中静态方法of的细节
//参数是一个可变参数,可以传递多个参数,也可以传递数组
//但是数组必须是引用数据类型的,如果传递基本数据类型,是会把整个数组当作一个元素,放到stream当中
Stream.of(arr).forEach(System.out::println);
Stream.of(arr2).forEach(System.out::println);
}
}
Stream流中间操作方法
- 概念
中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作
filter代码演示
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布", "貂蝉", "诸葛亮", "赵云", "关羽", "张飞","曹操","曹丕","曹植");
list.stream().filter(s -> s.startsWith("曹")).forEach(System.out::println);
}
}
limit&skip代码演示
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布", "貂蝉", "诸葛亮", "赵云", "关羽", "张飞","曹操","曹丕","曹植");
//需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(s-> System.out.println(s));
System.out.println("--------");
//需求2:从第3个开始取,取到第6个在控制台输出
list.stream().skip(2).limit(4).forEach(s-> System.out.println(s));
System.out.println("--------");
//需求3:将集合中的元素倒序输出
list.stream().sorted(Collections.reverseOrder()).forEach(s-> System.out.println(s));
}
}
concat&distinct代码演示
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布", "貂蝉", "诸葛亮", "赵云", "关羽", "张飞","曹操","曹丕","曹植");
//需求1:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
//需求2:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
//需求3:合并需求1和需求2得到的流,并把结果在控制台输出
// Stream.concat(s1,s2).forEach(s-> System.out.println(s));
//需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
}
}
map代码演示
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布-15","貂蝉-12", "关羽-18", "张飞-20", "赵云-16", "诸葛亮-14", "曹操-17", "孙权-19", "周瑜-13", "黄盖-11");
//需求:只获取里面的年龄并打印
list.stream().map(s->s.split("-")[1]).forEach(System.out::println);
}
}
map的底层原理
//Function中第一个参数表示流对象的数据类型,第二个参数表示要强制转换的数据类型 //apply的形参s:依次表示流里面的每一个数据 //返回值:表示转换之后的数据 // Stream.concat(boy, girl).map(new Function<String, Actor>() { // @Override // public Actor apply(String s) { // String name =s.split(",")[0]; // int age = Integer.parseInt(s.split(",")[1]); // return new Actor(name,age); // } // }).forEach(s-> System.out.println(s)); Stream.concat(boy,girl) .map(s -> new Actor( s.split(",")[0], Integer.parseInt(s.split(",")[1] ))) .collect(Collectors.toList()) .forEach(s-> System.out.println(s));
Stream流终结操作方法
- 概念
终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
forEach&count代码演示
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布", "貂蝉", "诸葛亮", "赵云", "关羽", "张飞", "曹操", "孙权", "周瑜", "黄盖");
//method1(list);
// long count():返回此流中的元素数
long count = list.stream().count();
System.out.println(count);
}
private static void method1(ArrayList<String> list) {
// void forEach(Consumer action):对此流的每个元素执行操作
// Consumer接口中的方法void accept(T t):对给定的参数执行此操作
//在forEach方法的底层,会循环获取到流中的每一个数据.
//并循环调用accept方法,并把每一个数据传递给accept方法
//s就依次表示了流中的每一个数据.
//所以,我们只要在accept方法中,写上处理的业务逻辑就可以了.
list.stream().forEach(
new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}
);
System.out.println("====================");
//lambda表达式的简化格式
//是因为Consumer接口中,只有一个accept方法
list.stream().forEach(
(String s)->{
System.out.println(s);
}
);
System.out.println("====================");
//lambda表达式还是可以进一步简化的.
list.stream().forEach(s->System.out.println(s));
}
}
toArray代码演示
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "吕布", "貂蝉", "诸葛亮", "赵云", "关羽", "张飞", "曹操", "孙权", "周瑜", "黄盖");
// toArray()
//Object[] objects = list.stream().toArray();
//System.out.println(Arrays.toString(objects));
//IntFunction的泛型,具体类型的数组
//apply的形参:流中数据的个数,要跟数组的长度保持一致
//apply的返回值:具体类型的数组
//方法体:就是创建数组
//toArray方法的参数的作用:负责创建一个指定类型的数组
//toArray方法的底层,会以此得到流里面的每一个数据,并把数据放到数组中
//toArray方法的返回值:是一个装着流里面所有数据的数组
// String[] arr = list.stream().toArray(new IntFunction<String[]>() {
// @Override
// public String[] apply(int value) {
// return new String[value];
// }
// });
//
// System.out.println(Arrays.toString(arr));
//lambda表达式简化
String[] arr = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(arr));
}
}
Stream流的收集操作代码演示
// toList和toSet方法演示
public class MyStream7 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list1.add(i);
}
list1.add(10);
list1.add(10);
list1.add(10);
list1.add(10);
list1.add(10);
//filter负责过滤数据的.
//collect负责收集数据.
//获取流中剩余的数据,但是他不负责创建容器,也不负责把数据添加到容器中.
//Collectors.toList() : 在底层会创建一个List集合.并把所有的数据添加到List集合中.
List<Integer> list = list1.stream().filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println(list);
Set<Integer> set = list1.stream().filter(number -> number % 2 == 0)
.collect(Collectors.toSet());
System.out.println(set);
}
}
/**
Stream流的收集方法 toMap方法演示
创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
"zhangsan,23"
"lisi,24"
"wangwu,25"
保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
*/
public class MyStream8 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan,23");
list.add("lisi,24");
list.add("wangwu,25");
Map<String, Integer> map = list.stream().filter(
s -> {
String[] split = s.split(",");
int age = Integer.parseInt(split[1]);
return age >= 24;
}
// collect方法只能获取到流中剩余的每一个数据.
//在底层不能创建容器,也不能把数据添加到容器当中
//Collectors.toMap 创建一个map集合并将数据添加到集合当中
// s 依次表示流中的每一个数据
//第一个lambda表达式就是如何获取到Map中的键
//第二个lambda表达式就是如何获取Map中的值
).collect(Collectors.toMap(
s -> s.split(",")[0],
s -> Integer.parseInt(s.split(",")[1]) ));
System.out.println(map);
}
}
Stream流综合练习
练习1
- 案例需求
现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
- 男演员只要名字为3个字的前三人
- 女演员只要姓林的,并且不要第一个
- 把过滤后的男演员姓名和女演员姓名合并到一起
- 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法
演员类
public class Actor {
private String name;
public Actor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类
public static void main(String[] args) {
ArrayList<String> Boylist = new ArrayList<>();
ArrayList<String> Girllist = new ArrayList<>();
Collections.addAll(Boylist, "鲁智深","关羽","诸葛亮","郭襄","黄药师", "洪七公");
Collections.addAll(Girllist, "小龙女", "黄蓉", "郭芙", "黄月英", "林黛玉","林志玲");
Stream<String> a=Boylist.stream().filter(s -> s.length() == 3).limit(3);
Stream<String> b = Girllist.stream().filter(s->s.startsWith("林")).skip(1);
Stream<String> concat = Stream.concat(a, b);
concat.forEach(name ->{
Actor actor =new Actor(name);
System.out.println(actor.getName());
});
}
练习2
数据过滤
定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
过滤奇数,只留下偶数
并将结果保存起来
public static void main(String[] args) {
ArrayList <Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9,10);
List<Integer> integers = list.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
integers.forEach(System.out::println);
}
练习3
数据操作
创建一个ArrayList集合,并添加以下字符串,字符串中前面是姓名,后面是年龄
“zhangsan,23”
“lisi,24”
“wangwu,25”
保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"zhangsan,23","lisi,24","wangwu,25");
// Map<String,Integer> collect = list.stream()
// .filter((s -> Integer.parseInt(s.split(",")[1]) >= 24))
// .collect(Collectors.toMap(new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s.split(",")[0];
// }
// }, new Function<String, Integer>() {
// @Override
// public Integer apply(String s) {
// return Integer.parseInt(s.split(",")[1]);
// }
// }));
Map<String, Integer> collect = list.stream()
.filter((s -> Integer.parseInt(s.split(",")[1]) >= 24))
.collect(Collectors.toMap(
s -> s.split(",")[0],
s -> Integer.parseInt(s.split(",")[1])));
System.out.println(collect);
}
}
练习4
数据操作
现在有两个ArrayList集合,
第一个集合中:存储6个男演员的名字和年龄。第二个集合中:存储6名女演员的名字和年龄。
姓名和年龄中间用逗号隔开。比如:张三,23
要求完成如下的操作:
1、男演员只要名字为3个字的前两人
2、女演员只要姓杨的,并且不要第一个
3、把过滤后的男演员姓名和女演员姓名合并在一起
4、将上一步的演员信息封装成Actor对象
5、将所有的演员对象都保存到List集合中
备注:演员类:Actor,属性有:name,age
public class test1 {
public static void main(String[] args) {
ArrayList<String> Boylist = new ArrayList<>();
ArrayList<String> Girllist = new ArrayList<>();
Collections.addAll(Boylist,"蔡徐坤,24","易烊千玺,24","王源,23","王俊凯,22","朱正廷,22","刘昊然,23");
Collections.addAll(Girllist,"迪丽热巴,24","杨幂,24","赵丽颖,23","杨超越,22","周冬雨,22","刘诗诗,23");
Stream<String> boy = Boylist.stream().filter(s -> s.split(",")[0].length()==3).limit(2);
Stream<String> girl = Girllist.stream().filter(s->s.startsWith("杨")).skip(1);
//第一个参数表示流对象的数据类型,第二个参数表示要强制转换的数据类型
// Stream.concat(boy, girl).map(new Function<String, Actor>() {
// @Override
// public Actor apply(String s) {
// String name =s.split(",")[0];
// int age = Integer.parseInt(s.split(",")[1]);
// return new Actor(name,age);
// }
// }).forEach(s-> System.out.println(s));
Stream.concat(boy,girl)
.map(s -> new Actor(
s.split(",")[0],
Integer.parseInt(s.split(",")[1]
)))
.collect(Collectors.toList())
.forEach(s-> System.out.println(s));
}
}
方法引用
方法引用的出现原因
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作
那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢?答案肯定是没有必要
那我们又是如何使用已经存在的方案的呢?
这就是我们要讲解的方法引用,我们是通过方法引用来使用已经存在的方案
方法引用符
:: 该符号为引用运算符,而它所在的表达式被称为方法引用
推导与省略
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
如果使用方法引用,也是同样可以根据上下文进行推导
方法引用是Lambda的孪生兄弟
方法引用的注意点
需要有函数式接口
被引用方法必须已经存在
被引用方法的形参和返回值需要跟抽象方法保持一致
被引用方法的功能需要满足当前的需求
代码演示:
public interface Printable {
void printString(String s);
}
public class PrintableDemo {
public static void main(String[] args) {
//在主方法中调用usePrintable方法
// usePrintable((String s) -> {
// System.out.println(s);
// });
//Lambda简化写法
usePrintable(s -> System.out.println(s));
//方法引用
usePrintable(System.out::println);
}
private static void usePrintable(Printable p) {
p.printString("爱生活爱Java");
}
}
方法引用的分类
引用静态方法
格式:类名::静态方法
范例:Integer::parseInt
练习:
集合中有以下数字,要求把他们都变成int类型
"1" "2" "3" "4" "5"
代码演示:
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"1","2","3","4","5");
//匿名内部类
list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
}).forEach(System.out::println);
//方法引用
list.stream().map(Integer::parseInt).forEach(System.out::println);
}
}
引用成员方法
格式:对象::成员方法
其他类:其他类对象::方法名
本类:this::方法名
父类:super::方法名
引用其他类的成员方法
练习1:
集合中有一些名字,按照要求过滤数据
代码演示:
定义类
public class StringOperation {
public boolean stringJudge(String s){
return s.startsWith("曹")&&s.length()==2;
}
}
测试类
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布","貂蝉","诸葛亮","赵云","关羽","张飞","曹操","曹丕","曹爽","曹植");
//使用lambda表达式
list.stream().filter(s -> s.startsWith("曹") && s.length() == 2).forEach(System.out::println);
//匿名内部类
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("曹") && s.length() == 2;
}
}).forEach(System.out::println);
//方法引用
list.stream().filter(new StringOperation()::stringJudge).forEach(System.out::println);
}
}
引用构造方法
格式:类名::new
范例:Student::new
练习:
集合里面存储姓名和年龄,比如:张无忌,15
要求:将数据封装成Student对象并收集到List集合中
代码演示:
定义类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String str) {
String[] arr=str.split(",");
this.name=arr[0];
this.age=Integer.parseInt(arr[1]);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布,24","貂蝉,22","诸葛亮,23","赵云,25","关羽,24","张飞,23");
//匿名内部类
list.stream().map(new Function<String, Student>() {
@Override
public Student apply(String s) {
return new Student(s.split(",")[0],Integer.parseInt(s.split(",")[1]));
}
}).collect(Collectors.toList()).forEach(System.out::println);
//lambda表达式
list.stream()
.map(s->new Student(s.split(",")[0],Integer.parseInt(s.split(",")[1])))
.collect(Collectors.toList())
.forEach(System.out::println);
//方法引用
list.stream()
.map(Student::new)
.collect(Collectors.toList())
.forEach(System.out::println);
}
}
使用类名引用成员方法
格式:类名::成员方法
范例:String::substring
练习:
集合里面一些字符串。要求变成大写后进行输出
独有的规则:
1.需要有函数式接口
2.被引用的方法必须已经存在
3.被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致
4.被引用方法的功能需要满足当前的需求
抽象方法形参的详解:
第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法
在Stream流当中,第一个参数一般都表示流里面的每一个数据
假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法
第二个参数到最后一个参数:
跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
public class test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"aaa","bbb","ccc");
//匿名内部类
list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(System.out::println);
//方法引用
list.stream().map(String::toUpperCase).forEach(System.out::println);
}
}
引用数组的构造方法
格式:数据类型[]::new
范例:int[]::new
练习
集合中存储一些整数,收集到数组当中
细节:
数组的类型,需要跟流中数据的类型保持一致
public class test1 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5);
//匿名内部类
Integer[] integer=list.stream().toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});
System.out.println(Arrays.toString(integer));
//方法引用
Integer[] integers = list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(integers));
}
}
方法引用综合小练习
转成自定义对象并收集到数组
练习1
集合中存储一些字符串的数据,比如:张三,23
收集到Student类型的数组中(使用方法引用完成)
练习2
创建集合添加学生对象,学生对象属性:name,age
只获取姓名并放到数组当中(使用方法引用完成)
练习3
创建集合添加学生对象,学生对象属性:name,age
把姓名和年龄拼接成:张三-23的字符串,并放到数组当中(使用方法引用完成)
练习1
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"吕布,23","貂蝉,22","关羽,24","张飞,23");
//先把字符串变成student对象,再把Student对象收集起来
Student[] students = list.stream().map(Student::new).toArray(Student[]::new);
System.out.println(Arrays.toString(students));
练习2
ArrayList<Student> list = new ArrayList<>();
Collections.addAll(list,new Student("吕布",23),new Student("貂蝉",22),new Student("关羽",24));
//技巧
//1、现在有没有一个方法符合我当前的需求
//2、如果有的话,这个方法是否满足引用的规则
//静态 类名::方法名
//成员方法 对象名::方法名
//构造方法 类名::new
//3、如果满足引用的规则,那么就可以直接使用
String[] strings = list.stream().map(Student::getName).toArray(String[]::new);
System.out.println(Arrays.toString(strings));
练习3
测试类
ArrayList<Student> list = new ArrayList<>();
Collections.addAll(list,new Student("吕布",23),new Student("貂蝉",22),new Student("关羽",24));
String[] strings = list.stream().map(Student::subcat).toArray(String[]::new);
System.out.println(Arrays.toString(strings));
定义类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String str) {
String[] arr=str.split(",");
this.name=arr[0];
this.age=Integer.parseInt(arr[1]);
}
public String subcat(){
return this.name+"-"+this.age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}