行为抽象和Lambda流
文章目录
前言
今天聊聊 stream 流,其实之前我对 stream 流是有点的偏见,奈何公司用得比较多,我也开始使用起来,才感觉真香,在此做出分享。
一、什么是 Stream?
-
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
-
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
-
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
-
Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
以上概念来源于:菜鸟教程 Java 8 Stream
先说说之前的偏见吧
二、我的偏见
1.数据准备
我这里准备了 Student 实体类和一个 TestStream 测试类,TestStream 类中现在什么内容都没有
Student 类中有 4 个属性,以及 一个无参、一个全参的构造方法,其次就是 get 和 set 方法 和 toString 方法。
Student.java
代码如下(示例):
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private Integer id;
private String name; // 姓名
private Integer age; // 年龄
private Integer score; // 成绩
}
2.效率测试
接下来我使用传统的 for 循环和 stream 操纵集合进行对比看看效率
测试代码如下(示例):
@Test
public void performanceTesting() {
// 先创建 1000 个 Student 对象
List<Student> students = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Student student = new Student();
// 给学生对象设置成绩,就取 i 值
student.setScore(i);
students.add(student);
}
// 接下来我使用 for 循环获取 students 集合中每个学生的成绩,加入到集合之中,并记录执行时间
long startByFor = System.currentTimeMillis();
List<Integer> scoresByFor = new ArrayList<>();
for (Student student : students) {
scoresByFor.add(student.getScore());
}
long endByFor = System.currentTimeMillis();
System.out.println("for 循环执行的时间:" + (endByFor - startByFor));
System.out.println("====================================");
// 接下来我使用 stream 流获取 students 集合中每个学生的成绩,加入到集合之中,并记录执行时间
long startByStream = System.currentTimeMillis();
List<Integer> scoresByStream = students.stream().map(Student::getScore).collect(Collectors.toList());
long endByStream = System.currentTimeMillis();
System.out.println("stream 流执行的时间:" + (endByStream - startByStream));
}
运行该测试方法,结果如下:
可以看到 for 循环的效率是要比 stream 流高的,那可能你们要说,你才处理 1000 个对象,那十万个对象呢,一百万个对象呢
那我再试试处理 十万 个对象
处理十万个对象运行结果:
处理一千万对象运行结果:
都可以看出其实 for 循环的效率还是蛮高的,当初大概就是因为这个原因不怎么用 stream 流,感觉是有点较真了,虽然 stream 流在简单的集合操作中效率可能真不如 for 循环,但是差距几乎没有,而且不得不说 stream 处理集合是真的很方便,以下就是展示一些 stream 流常用的方法的用法展示(有部分是工作中学到的,有的是在网上看视频学到的)。
三、stream 流的用法
1.数据准备
在测试类中先添加一个初始化的方法,创建一些不同的集合,并且赋初值
private List<String> strings;
private List<Integer> ints;
private List<String> names;
private List<Student> students;
// 数据准备,在执行测试方法之前先初始化数据
@Before
public void init() {
strings = Arrays.asList("abc", "0", "bc", "for","bc", "e fg", "jse", "Ff", "jkl","886");
ints = Arrays.asList(100,84,66,5,41,2,0,-7,88,-201);
names = Arrays.asList("张三","李四","王二","麻子","翠花","壮壮","狗蛋","小红");
students = new ArrayList<>();
students.add(new Student(1,"米大傻",18,90));
students.add(new Student(2,"米二傻",18,91));
students.add(new Student(3,"米三傻",19,92));
students.add(new Student(4,"米四傻",18,null));
students.add(new Student(5,"米五傻",20,88));
students.add(new Student(6,"米六傻",18,98));
students.add(new Student(7,"米七傻",21,100));
students.add(new Student(7,"米七傻",21,100));
students.add(new Student(9,"米九傻",19,73));
students.add(new Student(10,"米十傻",22,90));
}
2.API 的使用
获取流对象
/**
* 获取流对象
* 1. 获取串行流
* 2. 获取并行流
*/
@Test
public void getStream() {
// 1. 获取串行流
Stream<String> stream = strings.stream();
// 2. 获取并行流
Stream<String> stringStream = strings.parallelStream();
}
至于 串行流 和 并行流 有什么区别,这里我也不多说,以下链接有说明,平日里基本上都是获取串行流对象
foreach
作用:遍历集合
/**
* foreach:遍历集合 -- 访问者模式
*/
@Test
public void foreach() {
// 遍历下 names 集合
names.forEach(System.out::println);
System.out.println("====================================");
// 遍历下 students 集合
students.forEach(System.out::println);
}
打印结果如下:
collect
作用:收集器,能够将流转换为其他形式,比如:list set map
/**
* collect:收集器。将流转换为其他形式 : list set map
*/
@Test
public void collect() {
System.out.println("strings = " + strings);
// 将 strings 集合转化成 set 集合
Set<String> set = strings.stream().collect(Collectors.toSet());
System.out.println("set = " + set);
// 将 strings 转换成 map 集合,key 为 prod_ + 集合中的值,value 为集合中的值
/**
* v -> "prod_" + v key
* v -> v value
* (oldValue, newValue) -> newValue 遇到重复的选 newValue
*/
Map<String, String> toMap = strings.stream().collect(Collectors.toMap(v -> "prod_" + v, v -> v, (oldValue, newValue) -> newValue));
System.out.println("toMap = " + toMap);
// 将 student 集合按照 id(唯一) 封装成 map 集合
Map<String, Student> studentMap = students.stream().collect(Collectors.toMap(student -> student.getId().toString(), student -> student));
System.out.println("studentMap = " + studentMap);
// 将 students 集合按照 年龄 封装成 map 集合
Map<String, List<Student>> studentListMap = students.stream().collect(Collectors.groupingBy(student -> String.valueOf(student.getAge())));
System.out.println("studentListMap = " + studentListMap);
}
打印结果如下:
这个方法一般跟 stream 中其它方法聚合使用,之后的例子都会使用到
filte
作用:用于通过设置的条件过滤出元素
/**
* filter:用于通过设置的条件过滤出元素
*/
@Test
public void filter() {
// 将 strings 集合中带 f 的字符串筛选出来放如另外一个集合
List<String> filterStrings = strings.stream().filter(str -> str.contains("f")).collect(Collectors.toList());
filterStrings.forEach(System.out::println);
System.out.println("====================================");
// 将 students 集合中叫年龄为 18 的同学找出来
List<Student> filterStudents = students.stream().filter(student -> 18 == student.getAge()).collect(Collectors.toList());
filterStudents.forEach(System.out::println);
}
打印结果如下:
distinct
作用:去除集合中重复的元素
/**
* distinct:去重
*/
@Test
public void distinct() {
// 去除集合中重复的元素
List<String> distinct = strings.stream().distinct().collect(Collectors.toList());
distinct.forEach(System.out::println);
System.out.println("====================================");
List<Student> distinctStudents = students.stream().distinct().collect(Collectors.toList());
distinctStudents.forEach( student -> System.out.println(student.toString()));
}
打印结果如下:
limit
作用:获取流中前 n 元素
/**
* limit:获取流中前 n 元素
*/
@Test
public void limit() {
List<String> limitStrings = strings.stream().limit(2).collect(Collectors.toList());
limitStrings.forEach(System.out::println);
System.out.println("====================================");
Random random = new Random();
random.ints().limit(5).forEach(System.out::println);
}
打印结果如下:
skip
作用:获取流中除去前 n 个元素的其它所有元素
/**
* skip:获取流中除去前 n 个元素的其它所有元素
*/
@Test
public void skip() {
List<String> skipNames = names.stream().skip(2).collect(Collectors.toList());
skipNames.forEach(System.out::println);
}
打印结果如下:
map
作用:用于映射每个元素到对应的结果
/**
* map:接受一个函数作为参数,这个函数会被应用到每一个元素上,并将其映射成一个新的元素
* 使用映射一词,是因为它和转换类似,但其中的细微差别在于它是 创建一个新版本而不是去 修改
*
* 对流中所有元素做处理
*/
@Test
public void map() {
// 将集合 names 中的每个元素都拼接一个 A- str -A
// 会将箭头函数处理出来的结果作为集合中的元素返回
List<String> mapNames = names.stream().map( str -> "A-"+ str.concat("-A")).collect(Collectors.toList());
mapNames.forEach(System.out::println);
System.out.println("====================================");
// 获取 students 集合中每个同学的 名字
List<String> mapStudents = students.stream().map(Student::getName).collect(Collectors.toList());
mapStudents.forEach(System.out::println);
}
打印结果如下:
flatMap
作用:流的扁平化处理
/**
* flatMap:使用 flatMap 方法的效果是:各个数组并不是分别映射成一个流,而是映射成流的内容
* 所有使用 map(Arrays::stream) 时生成的单个流被合并起来,即扁平化为一个流
*
* 流的扁平化处理
*/
@Test
public void flatMap() {
List<Character> flatMap = strings.stream().flatMap(TestStream::getCharacterByString)
.collect(Collectors.toList());
System.out.println(flatMap);
List<Stream<Character>> map = strings.stream().map(TestStream::getCharacterByString)
.collect(Collectors.toList());
System.out.println(map);
// 对比这 map 和 flatMap 两个方法
/**
* map 解析:
* 1) [a,b,c] , [0] ,..., [8,8,6]
* 2) [a,b,c,0,b,c,...,8,8,6]
* flatMap 解析:
* 1) [a,b,c] , [0] ,..., [8,8,6]
*
* map 是返回一个值,而 flatMap 返回一个流,多个值
*/
Stream<Stream<Character>> mapStream = strings.stream().map(TestStream::getCharacterByString);
System.out.println("==========================");
Stream<Character> flatMapStream = strings.stream().flatMap(TestStream::getCharacterByString);
}
/**
* 字符串转换为字符流
*/
public static Stream<Character> getCharacterByString(String str) {
List<Character> characterList = new ArrayList<>();
for (Character character : str.toCharArray()) {
characterList.add(character);
}
return characterList.stream();
}
打印结果如下:
map 和 flatMap 的区别主要体现在返回对象中
map 的返回对象是 Stream<Stream<Object>>
而 flatMap 返回的对象是 Stream<Object>
如果想明白的更加深入点可参见 面试官:Java 8 map 和 flatMap 的区别?大部分人答不上来 这篇博客
mapToXxx
作用:转换类型
/**
* mapToXxx: 转换
*/
@Test
public void mapToXxx() {
// 提取出 students 集合的 age 属性并转换成 Long 类型
List<Long> mapToLongStudents = students.stream().mapToLong(Student::getAge).boxed().collect(Collectors.toList());
System.out.println("mapToLongStudents = " + mapToLongStudents);
}
打印结果如下:
sorted
作用:用于对流进行排序
/**
* sorted:返回排序后的流
*/
@Test
public void sorted() {
// 默认排序
List<String> defaultSortedStrings = strings.stream().sorted().collect(Collectors.toList());
List<Integer> defaultSortedInts = ints.stream().sorted().collect(Collectors.toList());
List<String> defaultSortedNames = names.stream().sorted().collect(Collectors.toList());
System.out.println("defaultSortedStrings = " + defaultSortedStrings);
System.out.println("defaultSortedInts = " + defaultSortedInts);
System.out.println("defaultSortedNames = " + defaultSortedNames);
// 使用比较器
// 正向按中文排序
List<String> collatorNames = names.stream().sorted(Collator.getInstance(Locale.CHINA)).collect(Collectors.toList());
System.out.println("collatorNames = " + collatorNames);
// 反向按中文排序
List<String> reverseOrderNames = names.stream().sorted(Collections.reverseOrder(Collator.getInstance(Locale.CHINA))).collect(Collectors.toList());
System.out.println("reverseOrderNames = " + reverseOrderNames);
// 将 students 集合按照年龄进行排序,年龄相同则通过 id 进行排序
List<Student> collectStudents = students.stream().sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getId))
.collect(Collectors.toList());
collectStudents.forEach(System.out::println);
}
打印结果如下:
anyMatch
作用:检查集合中是否至少匹配一个元素,返回 boolean
/**
* anyMatch:检查集合中是否至少匹配一个元素,返回 boolean
*/
@Test
public void anyMatch() {
// 检查集合 strings 中是否有包含 bc 的元素
boolean anyMatchBcStrings = strings.stream().anyMatch(s -> s.contains("bc"));
System.out.println("anyMatchBcStrings = " + anyMatchBcStrings);
// 检查集合 students 中是否有同学成绩不存在
boolean anyMatchNullStudents = students.stream().anyMatch(student -> student.getScore() == null);
System.out.println("anyMatchNullStudents = " + anyMatchNullStudents);
}
打印结果如下:
allMatch
作用:检查是否匹配所有元素,返回 boolean
/**
* allMatch:检查是否匹配所有元素,返回 boolean
*/
@Test
public void allMatch() {
// 检查集合中的元素是否都大于 0
boolean result = ints.stream().allMatch(s -> s>0);
System.out.println("result = " + result);
}
打印结果如下:
noneMatch
作用:检查是否没有匹配所有元素,返回 boolean
/**
* noneMatch:检查是否没有匹配所有元素,返回 boolean
*/
@Test
public void noneMatch() {
// 检查集合中的元素长度是否都不大于 5
boolean result = strings.stream().noneMatch(s -> s.length()<5);
System.out.println("result = " + result);
}
打印结果如下:
findAny
作用:将返回当前流中的任意元素
/**
* findAny:将返回当前流中的任意元素
*/
@Test
public void findAny() {
Optional<String> any = strings.stream().findAny();
// 获取一下值,如果是 串行的流 默认会返回集合中的第一个(效率快)
//if (any.isPresent()) System.out.println(any.get());
any.ifPresent(System.out::println);
Optional<String> any1 = strings.parallelStream().findAny();
// 获取一下值,如果是 并行的流 集合里的元素都有可能拿得到
any1.ifPresent(System.out::println);
}
打印结果如下:
findFirst
作用:将返回集合中的第一元素
/**
* findFirst:将返回集合中的第一元素
*/
@Test
public void findFirst() {
Optional<String> first = names.stream().findFirst();
first.ifPresent(System.out::println);
}
打印结果如下:
reduce
作用:可以将流中元素反复结合起来,得到一个值
/**
* reduce:可以将流中元素反复结合起来,得到一个值
*/
@Test
public void reduce() {
/**
* acc:初始化值,默认为 null
* item:集合中的元素
*/
Optional<String> reduce = strings.stream().reduce((acc,item) -> {return acc+item;});
//if (reduce.isPresent()) System.out.println(reduce.get());
reduce.ifPresent(System.out::println);
// 获取集合 students 的总体成绩
Optional<Integer> reduceScore = students.stream().map(student -> student.getScore() == null ? 0: student.getScore()).reduce(Integer::sum);
reduceScore.ifPresent(System.out::println);
}
打印结果如下:
count
作用:获取集合中元素的数量
/**
* count:获取集合中元素的数量
*/
@Test
public void count() {
// 查看 strings 集合中有多少元素
long count = strings.stream().count();
System.out.println("count = " + count);
// 统计成绩大于 90 的分同学的个数
long countStudents = students.stream().filter(student -> (student.getScore() == null ? 0 : student.getScore()) > 90).count();
System.out.println("countStudents = " + countStudents);
}
打印结果如下:
peek
作用:接收一个没有返回值的λ表达式,可以做一些输出,外部处理等
/**
* 接收一个没有返回值的λ表达式,可以做一些输出,外部处理等
*/
@Test
public void peek() {
// 将学生成绩为空的同学记上 0 分 返回
List<Student> peekStudents = students.stream().filter(student -> student.getScore() == null).peek(student -> student.setScore(0)).collect(Collectors.toList());
System.out.println("peekStudents = " + peekStudents);
}
打印结果如下:
Stream.of
作用:初始化集合
/**
* 初始化集合
*/
@Test
public void streamOf() {
List<String> list = Stream.of("1", "2", "3").collect(Collectors.toList());
System.out.println("list = " + list);
}
打印结果如下:
四、总结
以上大概就是我在工作中经常使用到的 stream 操作,可能不是很齐全,今有若使用到其它的操作,我也会更新上去,欢迎大家指出我的不足。
- 点赞
- 收藏
- 关注作者
评论(0)