一、需要学会什么
二、不可变集合
1、什么是不可变集合?
- 不可变集合,就是不可被修改的集合。
- 集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变 。否则报错!
2、不可变原理
- 比如:想将 ”古力娜扎“ 修改成 ”老王“,或者想添加 ”老王“ 这个元素,都是不行的,因为是不可变集合。
3、为什么要创建不可变集合?
- 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
- 或者当集合对象被不可信的库调用时,不可变形式是安全的。
4、如何创建不可变集合?
- 在
List、Set、Map
接口中,都存在of
方法,可以创建一个不可变的集合。
方法名称 | 说明 |
---|---|
static < E > List< E > of(E…elements) | 创建一个具有指定元素的List集合对象 |
static < E > Set< E > of(E…elements) | 创建一个具有指定元素的Set集合对象 |
static <K, V> Map<K, V> of(E…elements) | 创建一个具有指定元素的Map集合对象 |
- 不可变集合:不能添加、修改、删除数据。
总结
1、不可变集合的特点?
- 定义完成后不可修改、删除、添加。
2、如何创建不可变集合?
- List、Set、Map接口中,都存在of方法可以创建不可变集合。
三、Stream流的概述
1、什么是Stream流?
-
在Java8中, 得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。
-
目的:用于简化集合和数组操作的API。
2、体验Stream流的作用
-
需求:按照下面的要求完成集合的创建和遍历
-
创建一个集合,存储多个字符串元素。
-
把集合中所有以 “张” 开头的元素存储到一个新的集合中。
-
把 “张” 开头的集合中的长度为3的元素存储到一个新的集合。
-
遍历上一步得到的集合。
-
package com.app.d2_stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
目标:初体验Stream流的便捷
*/
public class StreamTest {
public static void main(String[] args) {
// 1、原始方式
// a、定义一个集合,用于存储姓名
List<String> names = new ArrayList<>();
// b、往集合中添加多个元素
Collections.addAll(names, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
System.out.println("过滤前:" + names);
// c、找出 以姓"张"开头 且 长度为3的 姓名放到新集合中
List<String> zhangThreeList = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张") && name.length() == 3) {
zhangThreeList.add(name);
}
}
System.out.println("过滤后:" + zhangThreeList);
System.out.println("---------------------------------------------");
// 2、使用Stream流实现
// Stream流支持链式编程
names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
// names.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(s -> System.out.println(s));
}
}
过滤前:[张无忌, 周芷若, 赵敏, 张强, 张三丰]
过滤后:[张无忌, 张三丰]
---------------------------------------------
张无忌
张三丰
Process finished with exit code 0
-
可以看到,不使用Stream流 和 使用Stream流来实现这个需求,区别是非常明显的!
-
因此,在开发中,Stream流是被大量使用的。
3、Stream流的思想
-
就像是一条线,比如工厂上的那条流水线。
- Stream流式思想的核心:
- 1、先得到集合或者数组的Stream流(就是一根传送带);
- 2、把元素放上去;
- 3、然后使用这个Stream流简化的API来方便的操作元素。
- Stream流式思想的核心:
-
这些数据会沿着这根线往后流
总结
1、Stream流的作用是什么,结合了什么技术?
- 简化集合、数组操作的API;
- 结合了Lambda表达式。
2、说说Stream流的思想和使用步骤。
- 先得到集合或者数组的Stream流(就是一根传送带);
- 把元素放上去;
- 然后就用这个Stream流简化的API来方便操作元素。
四、Stream流的三类方法
1、获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作。
集合获取Stream流的方式:
-
可以使用Collection接口中的默认方法stream()生成流。
方法名称 说明 default Stream< E > stream() 获取当前集合对象的Stream流
数组获取Stream流的方式:
方法名称 | 说明 |
---|---|
public static < T > Stream< T > stream(T[] array) | 获取当前数组的Stream流 |
public static < T > Stream< T > of(T… values) | 获取当前数组/可变数据的Stream流 |
2、中间方法
- 流水线上的操作:一次操作完毕之后,还可以继续进行其他操作。
Stream流的常用API(中间操作方法)
方法名称 | 说明 |
---|---|
Stream< T > filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤 |
Stream< T > limit(long maxSize) | 获取前几个元素 |
Stream< T > skip(long n) | 跳过前几个元素 |
Stream< T > distinct() | 去除流中重复的元素。依赖(hashCode和equals方法) |
static < T > Stream< T > concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
package com.app.d2_stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
目标:掌握Stream流的常用API
*/
public class StreamDemo3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张三丰");
System.out.println(list);
// forEach:逐一处理(遍历)
System.out.println("---------------------");
// 1、filter:过滤元素
// 取姓张的名字
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
System.out.println("---------------------");
// 2、limit:取前几个元素
// 取前2个姓张的名字
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));
// list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);
System.out.println("---------------------");
// 3、skip:跳过前几个元素
// 跳过前2个姓张的名字
// list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);
System.out.println("---------------------");
// 4、map:加工方法:第一个参数是原材料 -> 第二个参数是加工后的结果
// 在所有的名字前面加上一个:"奥利给:"
list.stream().map(s -> "奥利给:" + s).forEach(System.out::println);
// list.stream().map(s -> "奥利给:" + s).forEach(a -> System.out.println(a));
// 需求:把所有的名字 都加工成一个学生对象
// a、首先需要创建一个学生对象类
System.out.println("----------");
// b、将所有名字加工成一个学生对象
list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
// list.stream().map(Student::new).forEach(System.out::println); // 构造器引用 ——> 方法引用
System.out.println("---------------------");
// 6、count:统计个数
// 统计名字长度为3的个数
long size = list.stream().filter(s -> s.length() == 3).count();
System.out.println(size);
System.out.println("---------------------");
// 7、concat:合并流(每次只能合并两次)
// public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
// 合并两个相同数据类型的流
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张")); // 取姓张的名字来合并
Stream<String> s2 = Stream.of("Java1", "Java2");
Stream<String> s3 = Stream.concat(s1 , s2);
s3.forEach(s -> System.out.println(s));
System.out.println("----------");
// 合并两个不同数据类型的流(开发中用的不多)
Stream<String> s4 = list.stream().filter(s -> s.startsWith("张"));
Stream<Integer> s5 = Stream.of(55, 100, 66);
Stream<Object> s6 = Stream.concat(s4 , s5);
s6.forEach(s -> System.out.println(s));
System.out.println("---------------------");
// 8、去除流中重复的元素。依赖(hashCode和equals方法)
list.stream().distinct().forEach(s -> System.out.println(s));
}
}
/**
a、首先需要创建一个学生对象类
*/
class Student{
private String name;
public Student(){
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "学生[" +
"姓名:" + name +
']';
}
}
[张无忌, 周芷若, 赵敏, 张强, 张三丰, 张三丰]
---------------------
张无忌
张强
张三丰
张三丰
---------------------
张无忌
张强
---------------------
张三丰
张三丰
---------------------
奥利给:张无忌
奥利给:周芷若
奥利给:赵敏
奥利给:张强
奥利给:张三丰
奥利给:张三丰
----------
学生[姓名:张无忌]
学生[姓名:周芷若]
学生[姓名:赵敏]
学生[姓名:张强]
学生[姓名:张三丰]
学生[姓名:张三丰]
---------------------
4
---------------------
张无忌
张强
张三丰
张三丰
Java1
Java2
----------
张无忌
张强
张三丰
张三丰
55
100
66
---------------------
张无忌
周芷若
赵敏
张强
张三丰
Process finished with exit code 0
- 注意:
- 中间方法也称为非终结方法,调用完成后会返回新的Stream流可以继续使用,支持链式编程。
- 在Stream流中无法直接修改集合、数组中的元素(因为Stream流只用于数据的分析和处理,要修改集合、数组中的元素还是使用原有的API即可)。
3、终结方法
-
一个Stream流只能有一个终结方法,是流水线上的最后一个操作。
方法名称 说明 void forEach(Consumer action) 对此流的每个元素执行遍历操作 long count() 返回此流中的元素个数 -
注意:终结方法,调用完成后流就无法继续使用了(因为是不会返回Stream了)。
总结
1、终结和非终结方法的含义是什么?
- 终结方法后流不可以继续使用,非终结方法会返回新的Stream流,支持链式编程。
五、Stream流的综合应用
1、案例
需求:
- 某个公司的开发部门,分为开发一部和二部,现在需要进行年终数据结算。
分析:
- 1、员工信息至少包含:名称、性别、工资、奖金、处罚记录。
- 2、开发一部有4名员工,二部有5名员工。
- 3、分别筛选出2个开发部门的最高工资的员工信息,封装成优秀员工对象TopPerformers。
- 4、分别统计出2个开发部门的平均月收入,要求去掉最高和最低工资。
- 5、统计2个开发部门整体的平均工资,去掉最高和最低工资的平均值。
package com.app.d3_stream_test;
/**
1、员工类
*/
public class Employee {
/*
员工属性:姓名、性别、工资、奖金、处罚记录
*/
private String name;
private char sex;
private double salary;
private double bonus;
private String punish;
public Employee(){
}
public Employee(String name, char sex, double salary, double bonus, String punish) {
this.name = name;
this.sex = sex;
this.salary = salary;
this.bonus = bonus;
this.punish = punish;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public String getPunish() {
return punish;
}
public void setPunish(String punish) {
this.punish = punish;
}
@Override
public String toString() {
return "员工[" +
"姓名:" + name +
", 性别:" + sex +
", 工资:" + salary +
", 奖金:" + bonus +
", 处罚记录:" + punish +
']';
}
}
package com.app.d3_stream_test;
/**
2、最佳员工类
*/
public class TopPerformers {
/*
优秀员工属性:姓名、工资
*/
private String name;
private double salary;
public TopPerformers(){
}
public TopPerformers(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "最佳员工[" +
"姓名:" + name +
", 工资:" + salary +
']';
}
}
package com.app.d3_stream_test;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
/**
3、实现类
*/
public class Test {
public static void main(String[] args) {
// a、开发一部有4名员工
List<Employee> oneDepartment = new ArrayList<>();
Collections.addAll(oneDepartment,
new Employee("唐僧", '男', 30000, 2000, null),
new Employee("孙悟空", '男', 15000, 5000, null),
new Employee("猪八戒", '男', 5000, 2000, "偷奸耍滑"),
new Employee("沙和尚", '男', 7000, 3000, null)
);
// b、开发二部有5名员工
List<Employee> twoDepartment = new ArrayList<>();
Collections.addAll(twoDepartment,
new Employee("关羽", '男', 40000, 5000, null),
new Employee("张飞", '男', 30000, 4000, "喝酒打架"),
new Employee("赵云", '男', 40000, 3000, null),
new Employee("马超", '男', 35000, 6000, "顶撞老板"),
new Employee("黄忠", '男', 20000, 3500, null)
);
/* c、分别筛选出2个开发部门的最高工资的员工信息,封装成优秀员工对象TopPerformers。
(1)员工工资 = 薪水 + 奖金
(2)升序排序
*/
// 开发一部的最高工资的员工信息
TopPerformers t1 = oneDepartment.stream()
.max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.map(e -> new TopPerformers(e.getName(), e.getSalary() + e.getBonus())).get();
System.out.println("开发一部的" + t1);
// 开发二部的最高工资的员工信息
TopPerformers t2 = twoDepartment.stream()
.max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.map(e -> new TopPerformers(e.getName(), e.getSalary() + e.getBonus())).get();
System.out.println("开发二部的" + t2);
System.out.println();
/*
d、分别统计出2个开发部门的平均月收入,要求去掉最高和最低工资。
(1)员工工资 = 薪水 + 奖金
(2)升序排序
(3)找出最低、最高工资
(4)之后将剩余的员工工资进行求和
*/
// 开发一部的平均月收入
oneDepartment.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(oneDepartment.size() - 2)
.forEach(e -> {
// 求出开发一部剩余的员工工资的总和
oneAllMoney += (e.getSalary() + e.getBonus());
});
System.out.println("开发一部的平均月收入是:" + oneAllMoney / (oneDepartment.size() - 2));
// 开发二部的平均月收入
twoDepartment.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(twoDepartment.size() - 2)
.forEach(e -> {
// 求出开发二部剩余的员工工资的总和
twoAllMoney += (e.getSalary() + e.getBonus());
});
System.out.println("开发二部的平均月收入是:" + String.format("%.2f", twoAllMoney / (twoDepartment.size() - 2)));
System.out.println();
/*
e、统计2个开发部门整体的平均工资,去掉最高和最低工资的平均值。
(1)先得到2个开发部门的Stream流
(2)合并2个开发部门的Stream流
(3)根据员工工资指定生序排序规则
(4)找出最低、最高工资
(5)累加剩余的员工的工资
(6)计算平均工资
*/
// (1)
Stream<Employee> oneEmployee = oneDepartment.stream();
Stream<Employee> twoEmployee = twoDepartment.stream();
// (2)
Stream<Employee> allEmployee = Stream.concat(oneEmployee, twoEmployee);
// (3)、(4)
allEmployee.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(oneDepartment.size() + twoDepartment.size() - 2)
.forEach(e -> {
// (5)
allMoney += (e.getSalary() + e.getBonus());
});
// (6)
// 方式一:使用String类的API:format(),给计算结果保留2位小数,并进行四舍五入
// System.out.println("2个开发部门的平均月收入是:" + String.format("%.2f", allMoney / (oneDepartment.size() + twoDepartment.size() -2 )));
// 方式二:使用BigDecimal类
BigDecimal d1 = BigDecimal.valueOf(allMoney);
BigDecimal d2 = BigDecimal.valueOf(oneDepartment.size() + twoDepartment.size() - 2);
System.out.println("2个开发部门的平均月收入是:" + d1.divide(d2, 2, RoundingMode.HALF_UP));
}
// 定义两个共享的变量,分别用于累加开发一部、二部剩余的员工工资,进行求和
public static double oneAllMoney;
public static double twoAllMoney;
// 定义一个共享的变量,用于累加2个开发部门的员工工资,进行求和
public static double allMoney;
}
开发一部的最佳员工[姓名:唐僧, 工资:32000.0]
开发二部的最佳员工[姓名:关羽, 工资:45000.0]
开发一部的平均月收入是:15000.0
开发二部的平均月收入是:39333.33
2个开发部门的平均月收入是:29071.43
Process finished with exit code 0
六、收集Stream流
1、含义
-
收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合、数组中去。
-
Stream流:方便操作集合/数组的 手段。
-
集合/数组:才是开发中的 目的。
2、Stream流的收集方法
方法名称 | 说明 |
---|---|
R collect(Collector collector) | 开始收集Stream流,指定收集器 |
3、Collectors工具类提供了具体的收集方法
方法名称 | 说明 |
---|---|
public static < T > Collector toList() | 把元素收集到List集合中 |
public static < T > Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 |
package com.app.d2_stream;
import java.util.*;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
目标:Stream流的收集方式
*/
public class StreamDemo4 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names, "张飞", "张三丰", "貂蝉", "关羽", "张翠山", "张三丰");
// 把元素收集到List集合中
Stream<String> s1 = names.stream().filter(s -> s.startsWith("张"));
List<String> zhangList = s1.collect(Collectors.toList()); // 得到一个可变集合
zhangList.add("Web");
System.out.println("List集合的内容:" + zhangList);
// List<String> list = s1.toList(); // 得到一个不可变集合
// list.add("Java"); // 报错!
// 注意!!流只能使用一次!
// 把元素收集到Set集合中
Stream<String> s2 = names.stream().filter(s -> s.startsWith("张"));
Set<String> zhangSet = s2.collect(Collectors.toSet());
System.out.println("Set集合的内容:" + zhangSet);
// 把元素收集到数组中
Stream<String> s3 = names.stream().filter(s -> s.startsWith("张"));
Object[] arrs = s3.toArray();
// 拓展
// String[] arrs = s3.toArray(s -> new String[s]);
// Integer[] arrs = s3.toArray(s -> new Integer[s]);
System.out.println("Array数组中的内容:" + Arrays.toString(arrs));
}
}
List集合的内容:[张飞, 张三丰, 张翠山, 张三丰, Web]
Set集合的内容:[张飞, 张翠山, 张三丰]
Array数组中的内容:[张飞, 张三丰, 张翠山, 张三丰]
Process finished with exit code 0
总结
1、收集Stream流的作用?
- Stream流是操作集合/数组的手段;
- 操作的结果数据最终要恢复到集合或者数组中去