外观
stream流
Optional容器
Optional是一个容器,它的成员变量是value,代表此容器中的元素。Optional可以存储任何类型的数据。
Optional的构造方法都是私有的,所以不能通过构造方法获取Optional对象。
可通过以下方法创建一个Optional对象:
- 使用
Optional.empty()创建一个空容器。 - 使用
Optional.of(value)创建一个值为value的容器。
Optional对象的成员方法:
get():返回容器的成员,若容器为空会抛出NoSuchElementException异常filter(Predicate<? super T> rule):判断容器存储的值是否符合判断规则,若符合则返回此对象,若不符合则返回空容器,参数是一个Predicate接口,该接口中只有一个抽象方法test用于比较要匹配的对象,返回布尔值,可以用lambda表达式引用其他已有方法isPresent():判断当前容器是否有元素,若有则返回true,否则返回falseorElse(T e):若容器为空则返回e,否则返回容器本身
package ClassStream;
import java.util.NoSuchElementException;
import java.util.Optional;
public class DemoOptional {
public static void main(String[] args) {
//此代码演示如何使用Optional类 Optional是一个容器类 并且对空指针异常做了优化 该类有泛型 因此可以保存任意类型的值
//Optional类不能使用new创建 需使用Optional类的三个静态方法创建实例
Optional<?> optional1 = Optional.empty(); //使用empty()方法创建存储空值的容器 此容器的值为null
try {
Object value = optional1.get();
System.out.println("容器1存储的值为" + value);
} catch (NoSuchElementException e) {
System.out.println("容器1是null值");
}
Optional<Double> optional2 = Optional.of(123.456); //使用of()方法创建实例 若此值是null则抛出空指针异常
//还可使用ofNullable()方法创建实例 使得此容器对象可以存储null值
//下面演示一些容器对象的常用方法
System.out.println("optional1是空值吗?" + optional1.isPresent()); //isPresent()方法可以判断该容器是否为null值
Double obj = optional2.get(); //使用get方法可以获得此容器存储的实例
Optional<Double> result = optional2.filter(e -> e > 123);
//使用filter()方法可判断容器存储的值是否符合判断规则 若符合则返回此对象 若不符合则返回空容器
System.out.println("optional2的元素:" + result.get());
//还有orElse(T t)方法 若此容器是null则返回参数值 若此容器不为空则返回此容器本身
}
}运行结果:
容器1是null值
optional1是空值吗?false
optional2的元素:123.456流的基本操作
将数据转换成流
因为流是接口,所以不能使用构造方法实例化。将数据转换成流的方法是使用集合Set或列表List及其子类的对象的成员方法stream() 实例化。 流中还分为两种操作:中间操作和终端操作。中间操作的返回值仍是一个流对象,可以对此对象继续使用流方法;终端操作的返回值是其他对象,该对象的类型不是流,因此不能继续进行流操作。
流对象的成员方法如下:
collect(Collector<? super T, A, R> collector):按照收集器规则将流元素重新封装成其他数据类型,比如列表、集合等,因为参数和返回值都是泛型,因此返回值是与参数类型一致的。参数我们一般选择Collectors类提供的toXXX()方法,如想要封装成列表就写Collectors.toList()。forEach(Consumer<? super T> action):遍历流,并对其中的所有元素都执行action方法。如我们想输出流的所有元素,就可以用stream.forEach(e -> System.out.println(e))。filter(Predicate<? super T> predicate):将该对象中的数据按照参数中的规则筛选,返回值为新stream对象,其中保存的是符合条件的数据。distinct():将流中重复元素删除,返回一个没有重复元素的新流对象。limit(long MaxSize):取流中的MaxSize个元素,返回一个新流对象。skip(long n):去除流中的前n个元素,并返回一个新流对象。map(Function<? super T, ? extends R> mapper):按照mapper规则将流中的某一元素单独映射出来并且放在一个新流对象中返回。allMatch(Predicate<? super T> predicate):判断流中的元素是否都符合规则,若全部符合规则则返回true,否则返回false。anyMatch(Predicate<? superT> predicate):判断流中的元素是否都不符合规则,若全部不符合规则则返回true,否则返回false。noneMatch(Predicate<? super T> predicate):判断流中的元素是否有任一个符合规则,若有至少一个符合规则则返回true,否则返回false。collect(Collector<? super T, A, R> collector):按照收集器规则返回指定元素,详细介绍请见下面的介绍及示例代码。
流的几种使用场景如下:
- 过滤数组元素:使用
filter(rule)方法过滤元素,其中参数rule需要的是一个可以返回布尔值的判断方法,返回值是全部符合条件的流对象。 - 去重:使用
distinct()方法去除重复元素,该方法返回的是无重复元素的流对象,因此可以继续进行其他操作。 - 取流中的前n个元素或者去除前n个元素:使用
limit(n)或者skip(n)即可,返回值是包含前几个元素或去除前几个元素的流对象。 - 数据映射:使用
map(mapper)将流中的某一具体元素提取出来,参数mapper是映射规则,因为参数是Function类型,所以最好配合getter方法使用。如将有员工编号、性别、姓名、工资等的流中将姓名单独提取出来做成一个名单,就可以使用此方法。 - 数据校验:使用
allMatch(rule)、anyMatch(rule)、noneMatch(rule)等方法判断元素符不符合规则,其中参数rule需要的是一个能够返回布尔值的判断方法,和上面的filter(rule)类似,方法的介绍请看上面。 - 查找流中的最大和最小元素:使用流对象的
collect(Collectors.minBy(cmpRule))实现。其中collect(rule)表示按照rule方法将流中的元素封装成参数指定的参数对象,这里使用Collectors.minBy(rule)和Collectors.maxBy(rule)作为参数,Collectors是一个final类,它里面的每个方法都代表一个收集器规则。这里的这两个方法的返回值都是Optional容器,又因为使用了泛型,collect()方法的返回值是和参数一致的,所以该方法的返回值也是Optional容器。参数rule是一个比较器对象,这里我们直接使用jdk已经提供好的比较器Comparator.comparing(element),参数element表示要比较什么对象。 - 分组:如按照部门分组,或先按照部门分组再按照性别分组。使用流对象的
collect(Collectors.groupingBy(element1,...))实现。内部参数Collectors.groupingBy(element1,...)表示按照元素element1或更多元素分组,若要进行多级分组就继续添加元素,配合getter方法使用。该方法的返回值是一个Map对象,所以collect()的返回值也是Map对象,其中Map的键类型与element1的类型一致,对应的值类型是List类,List的泛型为该元素的类;若有多级分组,那么第一层的值类型就是Map对象,此Map对象的键类型与分组的第二个参数element2一致,以此类推。若要访问其中的元素,就可以按照访问List和Map对象的方法访问即可,实例请见StreamOperation3。
package ClassStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamOperation1 {
public static void main(String[] args) {
//本文件演示流处理的第一类操作
//这里同时会一起介绍如何把数据集合转换成流
//将数据集合转换成流后可以对数据进行非常复杂的处理 下面演示如何使用流 演示的同时也会演示常用方法
//stream对象的filter()方法可以将该对象中的数据按照参数中的规则筛选,返回值为新stream对象,其中保存的是符合条件的数据
System.out.println("过滤--1.将所有年龄大于25岁的人列成名单:"); //过滤
List<Employee> list1 = Employee.getEmpList(); //先创建集合对象 可以是Set List和Map的子类 因为它们都实现了Collection接口
list1 = list1.stream() //先获取流对象 因为流是接口 所以只能通过这个方法创建流对象 创建流对象后才可以进行流操作
.filter(e -> e.getAge() > 25) //因为上一行代码返回的是流对象 因此可以对这个流对象进行操作 这里调用了filter方法 由于此方法是中间操作
//在操作后仍然是一个流对象 因此还可以调用其他流方法 此过滤方法的参数是一个lambda表达式 lambda表达式的参数会循环代替流中的元素
//lambda表达式的代码块是执行判断的 需要是能返回布尔值的表达式 这样符合条件的就会留在流中 不符合条件的会被删去
.collect(Collectors.toList()); //Collectors类提供的静态方法toList()方法可以将流中元素封装成list集合
// stream对象的collect对象可以返回参数指定类型的集合对象
System.out.println("过滤后的名单为:");
list1.forEach(System.out::println); //此forEach方法相当于增强的for循环 里面是一个lambda表达式 表达式的参数将会循环代替数组中的元素
//stream对象的distinct()方法可以去除流中的重复元素,返回值为新的stream对象
System.out.println("去重--删除所有重复的数字");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
list2.add(4);
list2.add(3);//这里重复添加了一个3
System.out.print("原始数据:");
list2.forEach(e -> System.out.print(e + " "));
System.out.println();
list2 = list2.stream() //此对象是可变的数组对象 首先还是调用这个方法创建stream对象
.distinct() //去重 由于去重是中间操作 返回的还是一个stream对象 因此可以使用此对象继续执行其他操作
.collect(Collectors.toList()); //这里还是选择封装成数组
System.out.print("去重后数据:");
list2.forEach(e -> System.out.print(e + " "));
System.out.println();
//limit()方法会只取流中前N个元素,skip()方法则会跳过流中前N个元素 它们也是中间操作
System.out.println("limit()方法和skip()方法");
List<Employee> list3 = Employee.getEmpList();
System.out.println("limit()方法--取表中前3个人");
list3.stream().limit(3).collect(Collectors.toList()).forEach(System.out::println);
System.out.println("skip()方法--忽略表中前3个人");
list3.stream().skip(3).collect(Collectors.toList()).forEach(System.out::println);
}
}运行结果:
过滤--1.将所有年龄大于25岁的人列成名单:
过滤后的名单为:
name=老张, age=40, salary=9000.0, sex=男, dept=运营部
name=大刚, age=32, salary=7500.0, sex=男, dept=销售部
name=翠花, age=28, salary=5500.0, sex=女, dept=销售部
name=老王, age=35, salary=6000.0, sex=女, dept=人事部
去重--删除所有重复的数字
原始数据:1 2 3 4 3
去重后数据:1 2 3 4
limit()方法和skip()方法
limit()方法--取表中前3个人
name=老张, age=40, salary=9000.0, sex=男, dept=运营部
name=小刘, age=24, salary=5000.0, sex=女, dept=开发部
name=大刚, age=32, salary=7500.0, sex=男, dept=销售部
skip()方法--忽略表中前3个人
name=翠花, age=28, salary=5500.0, sex=女, dept=销售部
name=小马, age=21, salary=3000.0, sex=男, dept=开发部
name=老王, age=35, salary=6000.0, sex=女, dept=人事部
name=小王, age=21, salary=3000.0, sex=女, dept=人事部package ClassStream;
import java.util.List;
import java.util.stream.Collectors;
public class StreamOperation2 {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList();
//数据映射--map()方法可以将流中数据按大多数照参数的逻辑映射出来 不要和封装成Map对象的方法混淆了
System.out.println("映射--将所有的人名提取出来");
List<String> list1 = list.stream().map(Employee::getName) //map()的参数是一个具体的字段 返回值就是所有该字段元素组成的流对象
.collect(Collectors.toList()); //由于是流对象 我们依然可以将它当成流对象操作它 也可以封装成数组或集合
System.out.print("将所有的人名提取出来的结果:");
list1.forEach(e -> System.out.print(e + " "));
System.out.println();
//数据查找
//allMatch()方法会判断流中的数据是否全部符合条件,若是返回true,有一个不符合就返回false
System.out.println("员工都大于40岁吗? " + list.stream().allMatch(e -> e.getAge() > 40));
//anyMatch()方法会判断流中的数据是否至少有一个符合条件,若是返回true,都不符合就返回false
System.out.println("员工有25岁以下的吗? " + list.stream().anyMatch(e -> e.getAge() < 25));
//noneMatch()方法会判断流中的数据是否全部不符合条件,若是返回true,有一个不符合就返回false
System.out.println("员工的薪资都不低于2000元吗? " + list.stream().noneMatch(e -> e.getSalary() < 2000));
}
}运行结果:
映射--将所有的人名提取出来
将所有的人名提取出来的结果:老张 小刘 大刚 翠花 小马 老王 小王
员工都大于40岁吗? false
员工有25岁以下的吗? true
员工的薪资都不低于2000元吗? truepackage ClassStream;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class StreamOperation3 {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList();
//数据统计
//注: 以下的所有方法都是流对象的成员方法 有些方法的操作为中间操作,返回的仍然可能为流对象
System.out.println("员工总人数:" + list.stream().count());
//count()方法返回当前流的元素个数
Optional<Employee> op1 = list.stream().collect(Collectors.minBy(Comparator.comparing(Employee::getAge))); //按照比较器规则返回最小值
System.out.println("公司年龄最小的员工为:" + op1.get());
Optional<Employee> op2 = list.stream().collect(Collectors.maxBy(Comparator.comparing(Employee::getAge)));
System.out.println("公司年龄最大的员工为:" + op2.get());
Double avgSalary = list.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println("公司薪资的平均值为:" + avgSalary);
Double sumSalary = list.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println("公司薪资的总和为:" + sumSalary);
String nameList = list.stream().map(Employee::getName).collect(Collectors.joining(","));
System.out.println("公司人员名单:" + nameList);
//数据分组
//一级分组
//方法返回值是一个Map集合 键是分组的规则 对应的元素就是分组后的结果
Map<String, List<Employee>> map1 = list.stream().collect(Collectors.groupingBy(Employee::getDept));
for (String name : map1.keySet()) {
System.out.println("[" + name + "]的员工列表如下:");
map1.get(name).stream().forEach(e -> System.out.println("\t" + e));
}
//二级分组:先按照部门分组,再按性别分组
//groupingBy()方法的重载形式有第二个参数 第二个参数就是第二个分组规则 这个函数的重载是递归的 所以可以看到第二次分组又做了第一次分组的参数
//这样分组的返回值就是一个Map对象 第一个Map对象的键就是第一次分组的规则 每个键对应的值又是一个新的Map对象 这个Map对象的键就是第二次分组的规则 元素就是最后的元素了
Map<String, Map<String, List<Employee>>> map2 = list.stream().collect(Collectors.groupingBy(Employee::getDept, Collectors.groupingBy(Employee::getSex)));
for (String name1 : map1.keySet()) {
System.out.println("[" + name1 + "]");
for (String name2 : map2.get(name1).keySet()) {
System.out.println("\t" + name2 + "性的员工列表如下:");
map2.get(name1).get(name2).stream().forEach(e -> System.out.println("\t\t" + e));
}
}
}
}运行结果:
员工总人数:7
公司年龄最小的员工为:name=小马, age=21, salary=3000.0, sex=男, dept=开发部
公司年龄最大的员工为:name=老张, age=40, salary=9000.0, sex=男, dept=运营部
公司薪资的平均值为:5571.428571428572
公司薪资的总和为:39000.0
公司人员名单:老张,小刘,大刚,翠花,小马,老王,小王
[销售部]的员工列表如下:
name=大刚, age=32, salary=7500.0, sex=男, dept=销售部
name=翠花, age=28, salary=5500.0, sex=女, dept=销售部
[人事部]的员工列表如下:
name=老王, age=35, salary=6000.0, sex=女, dept=人事部
name=小王, age=21, salary=3000.0, sex=女, dept=人事部
[开发部]的员工列表如下:
name=小刘, age=24, salary=5000.0, sex=女, dept=开发部
name=小马, age=21, salary=3000.0, sex=男, dept=开发部
[运营部]的员工列表如下:
name=老张, age=40, salary=9000.0, sex=男, dept=运营部
[销售部]
女性的员工列表如下:
name=翠花, age=28, salary=5500.0, sex=女, dept=销售部
男性的员工列表如下:
name=大刚, age=32, salary=7500.0, sex=男, dept=销售部
[人事部]
女性的员工列表如下:
name=老王, age=35, salary=6000.0, sex=女, dept=人事部
name=小王, age=21, salary=3000.0, sex=女, dept=人事部
[开发部]
女性的员工列表如下:
name=小刘, age=24, salary=5000.0, sex=女, dept=开发部
男性的员工列表如下:
name=小马, age=21, salary=3000.0, sex=男, dept=开发部
[运营部]
男性的员工列表如下:
name=老张, age=40, salary=9000.0, sex=男, dept=运营部