lambda表达式
lambda表达式简介
lambda表达式可以用非常少的代码实现抽象方法。lambda表达式不能独立执行,因此必须实现函数式接口,并且会返回一个函数式接口的对象
()->结果表达式
参数->结果表达式
(参数1,参数2,....,参数n)->结果表达式
第1行实现无参方法,单独写一对圆括号表示方法无参数,操作符右侧的结果表达式表示方
法的返回值。
第2行实现只有一个参数的方法,参数可以写在圆括号里,或者不写圆括号。
第3行实现多参数的方法,所有参数按顺序写在圆括号里,且圆括号不可以省略。
lambda表达式也可以实现复杂方法,将操作符右侧的结果表达式换成代码块即可,语法格式如下:
()->{代码块
参数->{代码块}
(参数1,参数2,.,参数n)->{代码块)
第1行实现无参方法,方法体是操作符右侧代码块。
第2行实现只有一个参数的方法,方法体是操作符右侧代码块。
第3行实现多参数的方法,方法体是操作符右侧代码块。
lambda 表达式的语法非常抽象,并且有着非常强大的自动化功能,如自动识别泛型、自动数据类塑转换等,这会让初学者很难掌握。如果将lambda 表达式的功能归纳总结,可以将lambda 表达式语法用如下方式理解:
()
这个方法
->
按照
{代码块}
这样的代码来实现
简单总结:操作符左侧的是方法参数,操作符右侧的是方法体。
lambda表达式实现函数式接口
lambda 表达式可以实现函数式接口,本节将讲解函数式接口概念以及用lambda 表达式实现不同类型的函数式接口。
1.函数式接口
函数式接口指的是仅包含一个抽象方法的接口,接口中的方法简单明了地说明了接口的用途,如线程接口Runnable、动作事件监听接口 ActionListener 等。开发者可以创建自定义的函数式接口,例如:
interface Mylnterface {
void method();
如果接口中包含一个以上的抽象方法,则不符合函数式接口的规范,这样的接口不能用 lambda表达式创建匿名对象。本章内容中所有被lambda 表达式实现的接口均为函数式接口。
2.lambda 表达式实现无参抽象方法
很多函数式接口的抽放方法是无参数的,如线程接口 Runnable 接口只有一个run0方法,这样的无
参抽象方法在 lambda表达式中使用“()”表示。
interface SayHi{ //接口
String say();
}
public class NoParamterDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//使用匿名内部类补全方法体
SayHi sh1 = new SayHi() { //重写say,
public String say() { //say()方法体
return "这是匿名内部类";
}
};
System.out.println(sh1.say());
//使用lambda表达式补全方法体
SayHi sh2 = ()->{ //指代SayHi里面Say的方法体
return "这是lambda表达式";
};
System.out.println(sh2.say());
}
}
3.lambda 表达式实现有参抽象方法
抽象方法中有一个或多个参数的函数式接口也是很常见的,lambda 表达式中可以用“(a1,a2.a3)”的方法表示有参抽象方法,圆括号里标识符对应抽象方法的参数。如果抽象方法中只有一个参数,lambda表达式则可以省略圆括号。
interface AddInt{ //加法接口
int add(int a,int b); //加法的抽象方法
}
public class ParamDemo {
int ad(int x,int y) {
return x + y;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//用匿名内部类来补全方法体
AddInt ai1 = new AddInt() {
public int add(int a,int b) {
return a+b;
}
};
System.out.println("匿名内部类:"+ai1.add(3, 5)); //输出结果 */
//用lambda表达式补全方法体
AddInt ai2 = (a,b)->{
return a+b;
};
System.out.println("lambda表达式:"+ai2.add(3, 5)); //输出结果
ParamDemo pd = new ParamDemo();
AddInt ai3 =pd::ad;
System.out.println("方法的引用:"+ai3.add(3, 5)); //输出结果
}
}
4.lambda 表达式使用代码块
当函数式接口的抽象方法需要实现复杂逻辑而不是返回一个简单的表达式的话,就需要在 lambd
表达式中使用代码块。lambda 表达式会自动判断返回值类型是否符合抽象方法的定义。
interface CheckGrade{
String check(int grade); //查询成绩结果
}
public class GradeDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
CheckGrade g=(n)->{ //用lambda表达式实现代码块
if(n>=90&&n<=100) { //如果成绩在90-100
return "成绩为优"; //输出为优
}else if(n>=80&&n<90) {
return "成绩为良";
}else if(n>=60&&n<80) {
return "成绩为中";
}else if(n>=0&&n<60) {
return "成绩为差";
}else { //其他数字不是有效成绩
return"成绩无效"; //输出成绩无效
}
};
System.out.println(g.check(89)); //输出结果
}
}
lambda 表达式调用外部变量
lambda 表达式除了可以调用定义好的参数,还可以调用表达式以外的变量。但是,这些外部的变量有些可以被更改,有些则不能。例如,lambda 表达式无法更改局部变量的值,但是却可以更改外部类的成员变量(也可以叫作类属性)的值。
1.lambda 表达式无法更改局部变量
局部变量在lambda 表达式中默认被定义为final(静态)的,也就是说,lambda表达式只能调用局部变量,却不能改变其值。
interface Variablelnterface1 {//测试接口
void method();
}
public class VariableDemo1 { //测试类
public static void main(String[] args){//创建局部变量
int value = 100;
Variablelnterface1 v=()->{ //实现测试接口
int num = value - 90; //使用局部变量赋值
value=12; //更改局部变量,此处会报错,无法通过编译
};
}
}
2.lambda 表达式可以更改类成员变量
类成员变量是在lambda 表达式中不是被final修饰的,所以lambda 表达式可以改变其值。
interface Variablelnterface2 {//测试接口
void method();//测试方法
}
public class VariableDemo2{//测试类
int value = 100; //创建类成员变量
public void action() {//创建类成员方法
Variablelnterface2 v= ()->{//实现测试接口
value =-12;//更改成员变量,没提示任何错误
};
System.out.println("运行接口方法前value="+ value);//运行接口方法前先输出成员变量值
v.method();
System.out.println("运行接口方法后value="+ value);//运行接口方法
}
public static void main(String[] args){//运行接口方法后再输出成员变量值
VariableDemo2 demo = new VariableDemo2();
//创建测试类对象
demo.action();//执行测试类方法
}
}
从这个结果可以看出以下几点:
lambda表达式可以调用并修改类成员变量的值。
lambda 表达式只是描述了抽象方法是如何实现的,在抽象方法没有被调用前,lambda表达式
中的代码并没有被执行,所以运行抽象方法之前类成员变量的值不会发生变化。
只要抽象方法被调用,就会执行 lambda 表达式中的代码,类成员变量的值就会被修改。
lambda 表达式与异常处理
很多接口的抽象方法为了保证程序的安全性,会在定义时就抛出异常。但是lambda表达式中并没有抛出异常的语法,这是因为lambda 表达式会默认抛出抽象方法原有的异常,当此方法被调用时则需要进行异常处理。
import java.util.Scanner;
interface Antiaddictinterface {//防沉迷接口
boolean check(int age) throws UnderAgeException;//||抽象检查方法,抛出用户未成年异常
}
class UnderAgeException extends Exception {//自定义未成年异常
public UnderAgeException(String message) {//|/有参构造方法
super(message);//|/调用原有父类构造方法
}
}
public class ThrowExceptionDemo{//|/测试类
public static void main(String[] args) {//|/主方法
//lambda 表达式创建 AntiaddictInterface对象,默认抛出原有异常
Antiaddictinterface ai = (a)->{
if (a< 18){///如果年龄小于18岁
throw new UnderAgeException("未满 18 周岁,开启防沉迷模式!");///抛出异常
}else {//|/否则
return true; //验证通过
}
};
Scanner sc = new Scanner(System.in);// //创建控制台扫描器
System.out.println("请输入年龄:"); //控制台提示
int age = sc.nextInt();// /获取用户输入的年龄
try {
if (ai.check(age)){// /因为接口方法抛出异常,所以此处必须捕捉异常
System.out.println("欢迎进入XX世界");//验证年龄
}
} catch (UnderAgeException e){
System.err.println(e);//控制台打印异常警告
}
sc.close();
//关闭扫描器
}
}
方法的引用
lambda 表达式还添加了一类新语法,用来引用方法,也就是说方法也可以作为一个对象被调用。
根据不同的方法类型,方法的引用包括引用静态方法、引用成员方法和引用构造方法等
引用静态方法
引用静态方法的语法如下:
类名:静态方法名
这个语法中出现了一个新的操作符“::”,这是由两个英文冒号组成的操作符,冒号之间没有空格。这个操作符左边表示方法所属的类名,右边是方法名。需要注意的是,这个语法中方法名是没有圆括号的。
interface StaticMethodInterface {//测试接口
int method(int a, int b);//抽象方法
}
public class StaticMethodDemo {//静态方法,返回两个参数相加的结果
static int add(int x, int y){//返回相加结果
return x+y;
}
public static void main(String[] args){
StaticMethodInterface sm = StaticMethodDemo::add;//1/引用StaticMethodDemo类的静态方法
int result = sm.method(15,16);直接调用接口方法获取结果
System.out.println("接口方法结果:"+result);///输出结果
}
}
引用成员方法
引用成员方法的语法如下
对象名::成员方法名
与引用静态方法语法不同,这里操作符左侧的必须是一个对象名,而不是类名。这种语法也可以达到抽象方法按照类成员方法逻辑来实现的目的。
import java.text.SimpleDateFormat;
import java.util.Date;
interface InstanceMethodInterface{ //创建测试接口
String method(Date date); //带参数的抽象方法
}
public class InstanceMethodDemo {
public String format(Date date) { //格式化方法
//创建日期格式化对象,并指定日期格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date); //返回格式化结果
}
public static void main(String[] args) {
// TODO Auto-generated method stub
InstanceMethodDemo demo=new InstanceMethodDemo(); //创建类对象
InstanceMethodInterface im=demo::format; //引用类对象的方法
Date date=new Date(); //创建日期对象
System.out.println("默认格式:"+date); //输出日期对象默认格式
System.out.println("接口输出的格式:"+im.method(date)); //输出经过接口方法处理过的接口
}
}
引用带泛型的方法
泛型是Java开发经常使用到的功能,“:”操作符支持引用带泛型的方法。除方法外,“:”操作符
支持引用带泛型的类。
import java.util.HashSet;
interface Paradigminterface<T>{//测试接口
int method(T[]t);//抽象方法
}
public class ParadigmDemo {//Ⅱ/测试类
//静态方法,使用泛型参数,在方法名之前定义泛型。此方法用于查找数组中的重复元素个数
static public <T> int repeatCoount(T[] t){
int arrayLength = t.length;//记录数组长度
java.util.HashSet<T> set = new HashSet<>();//创建哈希集合
for (T tmp:t){//遍历数组
set.add(tmp);//将数组元素放入集合中
}
return arrayLength - set.size();//返回数组长度与集合长度的差
}
public static void main(String[] args){
Integer a[]={1, 1, 2, 3,1, 5, 6, 1, 8, 8};//整数数组
String s[]={"王","李","赵","陈","李","孙","张"};//字符串数组
//创建接口对象,Integer作为泛型,引入ParadigmDemo 类的静态方法,方法名要定义泛型
Paradigminterface<Integer> p1 = ParadigmDemo::<Integer> repeatCoount;
System.out.println("整数数组重复元素个数:"+ p1.method(a));//调用接口方法
//创建接口对象,String 作为泛型,引入ParadigmDemo类的静态方法
//方法名若不定义泛型,则默认使用接口已定义好的泛型
Paradigminterface<String> p2 =ParadigmDemo::repeatCoount;
System.out.println("字符串数组重复元素个数:"+p2.method(s));//调用接口方法
}
}
引用构造方法
lambda 表达式有3种引用构造方法的语法,分别是引用无参构造方法、引用有参构造方法和引用数组构造方法,下面分别进行讲解。
1.引用无参构造方法
引用构造方法的语法如下:
类名:.new
因为构造方法与类名相同,如果操作符左右都写类名,会让操作符误以为是在引用与类名相同的静态方法,这样会导致程序出现Bug,所以引用构造方法的语法使用了new 关键字。操作符右侧的写new关键字,表示引用构造方法。
interface Constructorslnterface1 {//构造方法接口
ConstructorsDemo1 action();//调用无参方法
}
public class ConstructorsDemo1 {//测试类
public ConstructorsDemo1() {//无参构造方法
System.out.println("调用无参构造方法");
}
public ConstructorsDemo1(int i){ //有参构造方法
System.out.println("调用有参构造方法");
}
public static void main(String[] args){
Constructorslnterface1 a = ConstructorsDemo1::new;//引用ConstructorsDemo1类的构造方治
ConstructorsDemo1 b = a.action();//通过无参方法创建对象
}
}
引用有参构造方法
例题]使用 lambda 表达式引用有参数的构造方法
interface Constructorsinterface2{ //构造方法接口
ConstructorsDemo2 action(int i);//调用有参方法
}
public class ConstructorsDemo2{//测试类
public ConstructorsDemo2() {//无参构造方法
System.out.println("调用无参构造方法");
}
public ConstructorsDemo2(int i) {//有参构造方法
System.out.println("调用有参构造方法,参数为:"+i);
}
public static void main(String[] args){
Constructorsinterface2 a = ConstructorsDemo2::new;//引1用ConstructorsDemo2类的构造方法
ConstructorsDemo2 b = a.action(123);//通过有参方法创建对象
}
}
3.引用数组构造方法
Java 开发可能出现这样一种特殊场景:把数组类型当作泛型。如果方法返回值是泛型,在这种特殊场景下,方法就应该返回一个数组类型的结果。如果要求抽象方法既引用构造方法,又要返回数组类型结果,这种场景下抽象方法的参数就有了另外一个含义:数组个数。抽象方法的参数可以决定返回的数组长度,但数组中的元素并不是有值的,还需要再次赋值。引用数组构造方法的语法也会有所不同,语法如下:
类名[]::new
interface ArraysConsinterface<T>{//构造方法接口
//抽象方法返回对象数组,方法参数决定数组个数
T action(int n);
}
public class ArraysConsDemo{
public static void main(String[] args){
//引用数组的构造方法
ArraysConsinterface<ArraysConsDemo[]> a = ArraysConsDemo[]::new;
ArraysConsDemo array[]= a.action(3);//接口创建数组,并指定数组个数
array[0] = new ArraysConsDemo();
array[1] = new ArraysConsDemo();
array[2] = new ArraysConsDemo();//给数组元素实例化
//如果调用或给array[3]赋值,代码则会抛出数组下标越界异常
//array[3] = new ArraysConsDemo();
}
}
Fuction 接口
在此之前的所有实例中,想要使用 lambda 表达式都需要先创建或调用己有的函数式接口,但java.util.function 包已经提供了很多预定义函数式接口,就是没有实现任何功能,仅用来封装lambda表达式的对象。该包中最常用的接口是Function<T,R>接口,这个接口有以下两个泛型;
区 T:被操作的类型,可以理解为方法参数类型。
区 R:操作结果类型,可以理解为方法的返回类型。
Function 接口是函数式接口,所以只有一个抽象方法,但是Function 接口还提供了3个已实现的方法以方便开发者对函数逻辑进行更深层的处理。Function 接口方法如表14.1所示。
import java.util.function.Function;
public class FunctioinDemo {
//创建Function 接口对象,参数类型是integerI,返回值类型是String
Function<Integer[], String> function =(n)->{
StringBuffer str = new StringBuffer();//创建字符序列
for (Integer num:n){//遍历参数数组
str.append(num);//字符序列添加数组元素
str.append('.');//字符序列添加字符!
}
str.deleteCharAt(str.length()- 1);//删除末尾的'."
return str.toString();//返回字符串
};
public static void main(String[] args){
Integer[] ip ={ 192, 168, 1,1 };//待处理的数组
FunctioinDemo demo = new FunctioinDemo();
System.out.println(demo.function.apply(ip));//输出处理结果
}
}
流处理
流处理有点类似数据库的SQL语句,可以执行非常复杂的过滤、映射、查找和收集功能,并且代码量很少。唯一的缺点是代码可读性不高,如果开发者基础不好,可能会看不懂流API所表达的含义。
为了能让读者更好地理解流 API的处理过程和结果,本节先创建一个公共类——Employee员工类。员工类包含员工的姓名、年龄、薪资、性别和部门属性,并给这些属性提供了getter方法。重写toStringO可以方便查看员工对象的所有信息。公共类提供了一个静态方法getEmpListO,这个方法已经创建好了一些员工对象,然后将这些员工封装成一个集合并返回。本节将重点对这些员工数据进行流处理。
import java.util.ArrayList;
import java.util.List;
public class Employee {
private String name;
private int age;
private double salary;
private String sex;
private String dept;
public Employee(String name, int age, double salary, String sex, String dept) {
super();
this.name = name;
this.age = age;
this.salary = salary;
this.sex = sex;
this.dept = dept;
}
@Override
public String toString() {
return "Employee [name=" + name + ", age=" + age + ", salary=" + salary + ", sex=" + sex + ", dept=" + dept
+ "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
static List<Employee>getEmpList(){
List<Employee>list=new ArrayList<Employee>();
list.add(new Employee("老张",40,9000,"男","运营部"));
list.add(new Employee("小刘",24,5000,"女","开发部"));
list.add(new Employee("大刚",32,7500,"男","销售部"));
list.add(new Employee("翠花",28,5500,"女","销售部"));
list.add(new Employee("小马",21,3000,"男","开发部"));
list.add(new Employee("老王",35,6000,"女","人事部"));
list.add(new Employee("小王",21,3000,"女","人事部"));
return list;
}
}
Stream接口简介
流处理的接口都定义在 java.uil.stream 包下。BaseStream 接口是最基础的接口,但最常用的BaseStream接口的一个子接口——Stream接口,基本上绝大多数的流处理都是在Stream接口上实现的。
Collection 接口新增两个可以获取流对象的方法。第一个方法最常用,可以获取集合的顺序流,方法如下:
Stream<E> stream();
第二个方法可以获取集合的并行流,方法如下:
Stream<E> parallelstream();
因为所有集合类都是 Collection 接口的子类,如 ArrayList 类、HashSet 类等,所以这些类都可以进行流处理。例如:
List<lnteger> list = new ArrayList<lnteger>();
Stream<lnteger> s = list.stream();
Optional类
Optional 类像是一个容器,可以保存任何对象,并且针对NulIPointerException空指针异常做了优化,保证Optional 类保存的值不会是null。因此,Optional 类是针对“对象可能是null 也可能不是nu的场景为开发者提供了优质的解决方案,减少了烦琐的异常处理。
Optional 类是用 final 修饰的,所以不能有子类。Optional 类是带有泛型的类,所以该类可以保存任何对象的值。
从Optional 类的声明代码中就可以看出这些特性,JDK中的部分代码如下:
public final class Optional<T>{
private final T value;
...
}
//省略其他代码
Optional 类中有一个叫作 value 的成员属性,这个属性就是用来保存具体值的。value 是用泛型下修饰的,并且还用了final 修饰,这表示一个Optional对象只能保存一个值。
Optional类提供了很多封装、校验和获取值的方法,这些方法如表14.4所示。
import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
Optional<String> strValue = Optional.of("Hello");//创建有值对象
boolean haveValueFlag = strValue.isPresent();//判断对象中的值是不是空的
System.out.println("strValue 对象是否有值:"+haveValueFlag);
if (haveValueFlag){//如果不是空的
String str = strValue.get(); //获取对象中的值
System.out.println("strValue 对象的值是:"+ str);
}
Optional<String> noValue = Optional.empty();//创建空值对象
boolean noValueFlag = noValue.isPresent(); //判断对象中的值是不是空的
System.out.println("noValue 对象是否有值:”;"+noValueFlag);
if(noValueFlag){//如果不是空的
String str = noValue.get();//获取对象中的值
System.out.println("noValue对象的值是:" +str);
}else{ //如果是空的
String str = noValue.orElse("使用默认值");//使用默认值
System.out.println("noValue 对象的值是:"+str);
}
}
}
Collectors类
Collectors 类为收集器类,该类实现了java.util.Collector 接口,可以将Stream 流对象进行各种各样
的封装、归集、分组等操作。同时,Collectors类还提供了很多实用的数据加工方法,如数据统计计算等。Collectors 类的常用方法如表 14.5所示。
数据过滤
1.filter()方法
filter0方法是 Stream接口提供的过滤方法。该方法可以将lambda表达式作为参数然后按照lambi表达式的逻辑过滤流中的元素。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FilterOddDemo {
static void printeach(String message,List list) { //输出集合元素
System.out.print(message); //输出文字信息
list.stream().forEach(n->{ //使用forEach方法遍历集合并打印元素
System.out.print(n+"");
});
System.out.println(); //换行
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(); //创建空数组
for(int i = 1; i<=10;i++) { //从1循环到10
list.add(i); //给集合赋值
}
printeach("集合原有元素:",list); //输出集合元素
Stream<Integer> stream = list.stream(); //获取集合流对象
//将集合中的所有奇数过滤出来,把过滤结果重新赋值给流对象
stream = stream.filter(n->n%2==1);
//将流对象重新封装成一个List集合
List<Integer> result = stream.collect(Collectors.toList());
printeach("过滤之后的集合元素:",result); //输出集合元素
}
}
[例14.17]找出年龄大于30的员工
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FilerDemo{ //例题14.17
public static void main(String[] args) {
List<Employee> list=Employee.getEmpList();//获取公共类的测试数据
Stream<Employee> stream = list.stream();//获取集合流对象
//筛选年龄>30岁的员工
stream=stream.filter(sx-> sx.getAge()>30);//将年龄大于30岁的员工过滤出来
//限制条数
stream = stream.limit(2);//将流对象重新封装成一个List集合
List<Employee> result = stream.collect(Collectors.toList());//遍历结果集
for (Employee sx : result) {//输出员工对象信息
System.out.println(sx);
}
}
}
distinct()方法
distinct()方法是Stream接口提供的过滤方法。
[例14.18]去除List 集合中的重复数字
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DistinctDemo { //例题14.18
static void printeach(String message,List list) { //输出集合元素
System.out.print(message); //输出文字信息
list.stream().forEach(n->{ //使用fourEach方法遍历集合并打印元素
System.out.println(n+"");
});
System.out.println(); //换行
}
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer>list=new ArrayList<Integer>(); //创建集合
list.add(1); //添加元素
list.add(2);
list.add(2);
list.add(3);
list.add(3);
printeach("去重前:",list); //打印集合元素
Stream<Integer>stream=list.stream(); //获取集合流对象
stream=stream.distinct(); //取出流中的重复元素
List<Integer>reslut=stream.collect(Collectors.toList());//将流对象重新封装成一个List集合
printeach("去重后:",reslut);
}
}
limit()方法
limit()方法是Stream接口提供的方法,该方法可以获取流中前N个元素
【例题14.19】找出所有员工列表中的前两位女员工
import java.util.List;
import java.util.stream.Collectors;
import java.util. stream.Stream;
public class LimitDemo { //例题14.19
public static void main(String[] args){
List<Employee> list = Employee.getEmpList();//获取公共类的测试数据
Stream<Employee> stream = list.stream();//获取集合流对象
stream = stream.filter(people ->"女".equals(people.getSex()));//将所有女员工过滤出来
stream = stream.limit(2);//取出前两位
List<Employee> result = stream.collect(Collectors.toList());//将流对象重新封装成一个List集合
for (Employee emp : result) {//遍历结果集
System.out.println(emp);//输出员工对象信息
}
}
}
skip()方法
skip()方法是Stream接口提供的方法,该方法可以忽略流中的前N个元素
[例14.20]取出所有男员工,并忽略前两位男员工
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SkipDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 将所有男员工过滤出来
stream = stream.filter(people -> "男".equals(people.getSex()));
// 跳过前两位
stream = stream.skip(2);
// 将流对象重新封装成一个List集合
List<Employee> result = stream.collect(Collectors.toList());
for (Employee emp : result) { // 遍历结果集
System.out.println(emp); // 输出员工对象信息
}
}
}
数据映射
数据的映射和过滤概念不同:过滤是在流中找到符合条件的元素,映射是在流中获得具体的数据。
[例14.21]获取开发部所有员工的名单
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MapDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 将所有开发部的员工过滤出来
stream = stream.filter(people -> "开发部".equals(people.getDept()));
// 将所有员工的名字映射成一个新的流对象
Stream<String> names = stream.map(Employee::getName);
// 将流对象重新封装成一个List集合
List<String> result = names.collect(Collectors.toList());
for (String emp : result) { // 遍历结果集
System.out.println(emp); // 输出所有姓名
}
}
}
import java.util.List;
import java.util.stream.DoubleStream;
import java.util.stream.Stream;
public class MapToInDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 将所有开发部的员工过滤出来
stream = stream.filter(people -> "销售部".equals(people.getDept()));
// 将所有员工的名字映射成一个新的流对象
DoubleStream salarys = stream.mapToDouble(Employee::getSalary);
// 统计流中元素的数学总和
double sum = salarys.sum();
System.out.println("销售部一个月的薪资总额:"+sum);
}
}
数据查询
allMatch()方法
allMatch0方法是 Stream 接口提供的方法,该方法会判断流中的元素是否全部符合某一条件,返回结果是 boolean 值。
[例14.23]检查所有员工是否都大于25岁
import java.util.List;
import java.util.stream.Stream;
public class AllMatchDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 判断所有员工的年龄是否都大于25
boolean result = stream.allMatch(people -> people.getAge() > 25);
System.out.println("所有员工是否都大于25岁:" + result); // 输出结果
}
}
anyMatch()方法
anyMatch0方法是 Stream 接口提供的方法,该方法会判断流中的元素是否有符合某一条件,只个元素符合条件就返回true,如果没有元素符合条件才会返回 false。
[例14.24]检查是否有大于40岁的员工
import java.util.List;
import java.util.stream.Stream;
public class AnyMatchDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 判断员工是否有的年龄大于等于40
boolean result = stream.anyMatch(people -> people.getAge() >= 40);
System.out.println("员工中有年龄在40或以上的吗?:" + result); // 输出结果
}
}
noneMatch()方法
noneMatcb0方法是Stream 接口提供的方法,该方法会判断流中的所有元素是否都不符合某一条件。这个方法的逻辑和allMatch0方法正好相反。
[例14.25]检查公司是否不存在薪资小于 2000元的员工
import java.util.List;
import java.util.stream.Stream;
public class NoneMathchDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 判断公司中是否不存在薪资小于2000的员工?
boolean result = stream.noneMatch(people -> people.getSalary() <2000 );
System.out.println("公司中是否不存在薪资小于2000元的员工?:" + result);// 输出结果
}
}
findFirst()方法
是 Stream 接口提供的方法,这个方法会返回符合条件的第一个元素
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class FindFirstDemo {
public static void main(String[] args) {
// 获取公共类的测试数据
List<Employee> list = Employee.getEmpList();
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 过滤出21岁的员工
stream = stream.filter(people -> people.getAge() == 21);
Optional<Employee> young = stream.findFirst(); // 获取第一个元素
Employee emp = young.get(); // 获取员工对象
System.out.println(emp); // 输出结果
}
}
数据收集
数据统计
例14.27]统计公司各项数据,打印成报表
import java.util.Comparator; // 比较器接口
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ReducingDemo {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取测试数据
long count = list.stream().count(); // 获取总人数
// 下行代码也能实现获取总人数效果
// count = stream.collect(Collectors.counting());
System.out.println("公司总人数为:" + count);
// 通过Comparator比较接口,比较员工年龄,再通过Collectors的maxBy()方法取出年龄最大的员工的Optional对象
Optional<Employee> ageMax = list.stream().collect(Collectors.maxBy(Comparator.comparing(Employee::getAge)));
Employee older = ageMax.get();// 获取员工对象
System.out.println("公司年龄最大的员工是:\n " + older);
// 通过Comparator比较接口,比较员工年龄,再通过Collectors的minBy()方法取出年龄最小的员工的Optional对象
Optional<Employee> ageMin = list.stream().collect(Collectors.minBy(Comparator.comparing(Employee::getAge)));
Employee younger = ageMin.get();// 获取员工对象
System.out.println("公司年龄最小的员工是:\n " + younger);
// 统计公司员工薪资总和
double salarySum = list.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println("公司的薪资总和为:" + salarySum); // 输出结果
// 统计公司薪资平均数
double salaryAvg = list.stream().collect(Collectors.averagingDouble(Employee::getSalary));
// 使用格式化输出,保留2位小数
System.out.printf("公司的平均薪资为:%.2f\n", salaryAvg);
// 创建统计对象,利用summarizingDouble()方法获取员工薪资各方面的统计数据
java.util.DoubleSummaryStatistics s = list.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.print("统计:拿薪资的人数=" + s.getCount() + ", ");
System.out.print("薪资总数=" + s.getSum() + ", ");
System.out.print("平均薪资=" + s.getAverage() + ", ");
System.out.print("最大薪资=" + s.getMax() + ", ");
System.out.print("最小薪资=" + s.getMin() + "\n");
// 将公司员工姓名拼成一个字符串,用逗号分隔
String nameList = list.stream().map(Employee::getName).collect(Collectors.joining(", "));
System.out.println("公司员工名单如下:\n " + nameList);
}
}
数据分组
数据分组就是将流中元素按照指定的条件分开保存,类似 SQL 语言中的“GROUPBY”关键字
[例14.28]将所有员工按照部门分组
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class GroupDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Employee> list = Employee.getEmpList();
Stream<Employee>stream=list.stream();
Map<String,List <Employee>> map = stream.collect(Collectors.groupingBy(Employee::getDept));
Set<String>depts=map.keySet();
for(String dept:depts) {
System.out.println(dept+"员工信息如下:");
List<Employee>temp=map.get(dept);
for(Employee e:temp) {
System.out.println(e);
}
System.out.println();
}
}
}
【例题14.29】将所有员工先按照部门分组,再按照性别分组
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class GroupingDemo2 {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 一级分组规则方法,按照员工部门进行分级
Function<Employee, String> deptFunc = Employee::getDept;
// 二级分组规则方法,按照员工部门进行分级
Function<Employee, String> sexFunc = Employee::getSex;
// 将流中的数据进行二级分组,先对员工部分进行分组,在对员工性别进行分组
Map<String, Map<String, List<Employee>>> map = stream
.collect(Collectors.groupingBy(deptFunc, Collectors.groupingBy(sexFunc)));
// 获取Map的中的一级分组键集合,也就是部门名称集合
Set<String> deptSet = map.keySet();
for (String deptName : deptSet) { // 遍历部门名称集合
// 输出部门名称
System.out.println("【" + deptName + "】 部门的员工列表如下:");
// 获取部门对应的二级分组的Map对象
Map<String, List<Employee>> sexMap = map.get(deptName);
// 获得二级分组的键集合,也就是性别集合
Set<String> sexSet = sexMap.keySet();
for (String sexName : sexSet) { // 遍历部门性别集合
// 获取性别对应的员工集合
List<Employee> emplist = sexMap.get(sexName);
System.out.println(" 【" + sexName + "】 员工:"); // 输出性别种类
for (Employee emp : emplist) {// 遍历员工集合
System.out.println(" " + emp); // 输出对应员工信息
}
}
}
}
}