第十四章 Iambda表达式和流处理
14.1:Iambda表达式简介
Iambda表达式可以用非常少的代码来实现抽象方法。 Iambda表达式不能独立执行,因此必须是西安函数式接口,并返回一个函数式接口的对象。 Iambda表达式的语法特殊的 语法格式如下
()->结果表达式
参数 ->结果表达式
(参数1,参数2, ....参数n)->结果表达式
// () -> {代码块}
// 这个方法 按照 这样的代码来实现
//简单总结:操作左侧的式方法残念书,操作符右侧是方法体
Iambda表达式实现函数式接口
Iambda表达式可以实现函数式接口,
1.函数式接口
函数式接口的式京包含的一个抽象方法的接口,接口中的方法简单明了地说明了接口地,如用途线程解耦Runnable,动作时间监听接口,ActionListener等,开发者可以创建自定义地函数式接口 如下
interface Mylnterface{
void method();
}
//定义了一个没有方法体的抽象方法
2. Iambda表达式实现无参数抽象方法
很多函数接口的抽放方法是无参数的,如线程接口Runnable接口只有一个run()方法这样的无参抽象方法在 Iambda表达式中使用()表示代码如
本实例,直接在 Iambda表达式中创建了SayHiInterface接口对象,并制定了一个字符串作为接口方法的返回值,最后在输出语句中,pi对象就是Iambda表达式创建出的对象,当pi调用接口方法时就输出了 Iambda表达式指定的字符串
3.Iambda表达式实现也有参的抽象方法
代码如下
package 第十四章;
interface AddInterface{
int add(int a , int b );
}
public class ParamterDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//使用匿名内部类方法补全方法体(有参数)
AddInterface add1 = new AddInterface () {
public int add(int a , int b) {
return a+b ;
}
};
System.out.println("内部类:"+add1.add(3, 5));
//使用lambda表达式补全方法体 (有参数)
AddInterface add2 = (a,b) ->{ return a+b ;
};
System.out.println("lambda表达式:"+add2.add(9, 5));
}
}
在这实例中,函数式接口的抽象方法有两个参数,Iambda表达式的圆括号内也写了两个参数对应的抽象方法,注意 Iambda表达式中的参数不需要与抽象方法的参数名称相同,但是顺序必须相同
4.Iambda表达式使用代码块
当函数式接口的抽象方法需要实现复杂的逻辑而不是返回一个简单的表达式的话 ,就需要在Iambda表达式中使用代码块 Iambda表达式会自动判断返回类型释放符合抽象方法的定义
代码如下
package 第十四章;
interface CheckGrade{
String check(int grade);
}
public class GradeDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
CheckGrade g = (n)->{
if(n>90 && n<=100) {//使用if判断
return "成绩优异";//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));
}
}
Iambda表达式调用外部变量
Iambda表达式除了可以调用定义好的参数,还可以调用表达式以外的变量,但是,这些外部变量有些可以被更改 有些则不行
1.Iambda表达式无法更改局部变量
局部变量在lambda表达式中默认被定义为final(静态)也就是说Iambda表啊但是只能调用局部变量,却不能改变其值 代码如下
package 第十四章;
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表达式可以改变其值
例题如下
package 第十四章;
interface Variableinterface1{//创建接口
void method();//测试方法
}
public class VariableDemo2 {//测试类
int value =100;//创建类的成员变量
public void action () {//创建类的成员方法
Variableinterface1 v =() ->{//实现接口
value = -12;//更改成员变量,没提示任何错误
};
System.out.print("运行接口方法钱value"+value);//运行接口方法钱先输出成员变量值
v.method();//运行接口方法
System.out.print("运行接口方法后value"+value);//运行接口方法后在输出成员变量值
}
public static void main(String[] args) {
// TODO Auto-generated method stub
VariableDemo2 demo=new VariableDemo2();//创建测试类对象
demo.action();//调用测试类方法
}
}
lambda表达式与异常处理
很多接口的抽象方法为了保证程序的安全性,会在定义时抛出异常,但是lambda表达式中并没有抛出异常的语法,这是因为lambda表达式会默认抛出抽象原有的异常每当此方法被调用时则需要进行异常处理
14.2方法的引用
lambda表达式还添加了一类新语法,用来一弄方法也就是方法可以作为一个对象被调用,根据不同的方法类型,方法的引用包括了引用静态方法,引用成员方法和引用构造方法等等。
引用语法如下
类名::静态方法名 //“::”是引用的意思
主要的使用方法如下
package 第十四章;
interface AddInterface1{
int add(int a ,int b );
}
public class paramterDemo1 {
static int add (int a , int b){//需要添加static变量(让这个方法变成静态方法 然后达到只能坐在本类使用)
return a+b ;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//1 paramterDemo1 pd = new paramterDemo1();//去掉static的方法
AddInterface1 add1 = paramterDemo1::add;//使用paramterDemo1中的add方法来补全方法体然而达成效果
System.out.println("方法的引用:"+add1.add(3, 5));
}
}
代码图如下
运行结果如下
方法的引用:8
引用成员方法
引用成员方法如下
对象名::成员方法名
跟引用静态方法不同 这个操作符左边一定要是对象名 右边不是类名。这种语法可以达到抽象方法按照抽象方法按照类成员方法逻辑来实现的目的
实际代码如下
package 第十四章;
import java.text.SimpleDateFormat;
import java.util.Date;
interface instanceMethodlnterface{//接口
String method (Date date );//没有方法体 抽象方法
}
public class lnstanceMethidDemo {//有方法体
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
lnstanceMethidDemo demo =new lnstanceMethidDemo();//new对象让以下代码找到方法 然而调用 达到补全上方没有方法体的代码
instanceMethodlnterface im =demo::format;//im调用demo对象方法体中的方法 然而达到补全该方法中的方法体
Date date =new Date ();
System.out.println("默认格式:"+date );
System.out.println("接口输出的格式:"+im.method(date));
}
}
代码图如下
运行结果如下
默认格式:Sat Oct 14 02:14:04 CST 2023
接口输出的格式:2023-10-14
引用带有泛型的方法
泛型是java开发经常使用到的功能 “::”操作符支持引用泛型的方法。除了方法外,“::”操作符也支持引用带有泛型的类
引用构造方法
lambda表达式有3种引用构造方法的语句,分别是引用无参构造方法,引用有参构造方法和引用数组构造方法
1.引用无参构造方法
引用无参构造方法的语句如下
类名::new
因为构造方法与与类名相同,如果操作符左右都写类名的话,会让操作符以为是在引用与类名相同的静态方法,这昂会导致程序出现Bug,所以引用构造方法的语法使用了new关键字,操作符右侧new关键字,表示引用构造方法
这个语法有一点要注意:new关键字之后没有圆括号,也没有参数的定义。如果类中既有无参构造方法,又有有参构造方法。使用引用构造方法语句后,究竟哪一个构造方法被引用了呢,引用哪个构造方法是有函数决定的,“::”操作符会返回与抽象方法的参数结构相同的构造方法,如果找不到参数接口相同的构造方法,则会繁盛编译错误
以如下代码所示
package 第十四章;
interface Constructorslnterface2{//创建集合
ConstructorsDemo2 action();//调用有参构造方法
}
public class ConstructorsDemo2 {//测试类
public ConstructorsDemo2() {//无参构造方法
System.out.println("调用无参构造方法");//
}
public ConstructorsDemo2 (int i){//有参构造方法
System.out.println("调用有参构造方法");
}
public static void main (String []args) {//主方法//
Constructorslnterface2 a = ConstructorsDemo2::new ;//调用ConstructorsDemo2类的构造方法
ConstructorsDemo2 b =a.action();//通过有参构造方法创建对象
}
}
代码图如下
运行结果如下
调用无参构造方法
引用有参构造方法
引用有参构造方法的语句与无参构造方法中类似 区别为一个有参数一个没有参数
代码如下
package 第十四章;
interface Constructorslnterface2{//创建集合
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) {//主方法//
Constructorslnterface2 a = ConstructorsDemo2::new ;//调用ConstructorsDemo2类的构造方法
ConstructorsDemo2 b =a.action(123);//通过有参构造方法创建对象
}
}
代码图如下
运行结果输出
调用有参构造方法,,参数为:123
引用数组构造方法
java开发可能出现这样一种特殊场景:把数组类型当作泛型,如果犯法返回值为泛型,在这种特殊场景下,方法就应该是返回一个数组类型的结果,如果要求抽象方法引用构造方法,又要返回数组类型结果,这种场景下抽象方法的参数有了另外一个含义:数组个数 抽象方法的参数可以决定返回的数组长度,但是数组中的元素并不是有值的,还需要再次赋值。引用数组构造方法的语法也会有所不同 语法如下
类名[]::new
代码如下:
package 第十四章;
interface ArraysConslnterface
//抽象方法返回对象数组,方法参数决定了数组的个数
T action(int n);
}
public class ArraysConsDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArraysConslnterface
ArraysConsDemo array[] = a.action(3);
array[0] = new ArraysConsDemo();
array[1] = new ArraysConsDemo();
array[2] = new ArraysConsDemo();
}
}
代码图如下:
Fuction接口
在此之前的所有实例中,想要使用iambda表达式都需要先创建或调用已有的函数式接口,但java.util.function包已经提供了很多预定义函数式接口,就是没有实现任何功能,仅用来封装lambda表达式的对象,该包中最长用的接口时Function
T:被操作的类型,可以理解为方法参数类型
R:操作结果类型,可以理解为方法的返回类型
表Function接口方法
方法 功能说明 方法返回值
apply(T t) 抽象方法,按照被子类实现的逻辑,执行函数,参数为被操作泛型对象 R
andThen(Functionafter) 限制性apply(t)方法将执行结果作为本方法参数按照after函数逻辑继续执行 (T t)->apply(apply(t))
compose (Functionbefore) 先按照before函数逻辑操作接口被操作对象t,再将执行结果作为apply()方法的参数 (V v)->apply(before,appl有(v))
static identity() 此方法时静态方法,返回一个Function对象,此对象的apply()方法只会返回参数值 t->t
代码如下
package 第十四章;
import java.util.function.Function;
public class FunctionlnDemo {
Function
StringBuilder str=new StringBuilder();
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};
System.out.print(demo.function.apply(ip));
}
}
14.3 流处理
流处理有点类似与数据库语句 可以执行非常复杂的过滤 映射 和查找 手机功能,并且代码量很少 但是唯一的确定时代码可读性不高,如果开发者基础不好可能看不懂 流API所表达的含义
创建员工表封装成一个集合 里面有 名字 年龄 薪资 性别 部门 代码如下:
package 第十四章;
import java.util.ArrayList;
import java.util.List;
public class Employee {
private String name ;
private int age ;
private double salsary;
private String sex ;
private String dept;
//构造方法
public Employee(String name, int age, double salsary, String sex, String dept) {
super();
this.name = name;
this.age = age;
this.salsary = salsary;
this.sex = sex;
this.dept = dept;
}
@Override
//重写toString方法 方便打印员工信息
public String toString() {
return "Employee [name=" + name + ", age=" + age + ", salsary=" + salsary + ", sex=" + sex + ", dept=" + dept
+ "]";
}
//以下是员工的属性的getter方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getSalsary() {
return salsary;
}
public String getSex() {
return sex;
}
public String getDept() {
return dept;
}
static List
List
list.add(new Employee("喜羊羊",18,9000,"男","运营部"));//向列表中添加数据
list.add(new Employee("美羊羊",15,3000,"女","设计部"));
list.add(new Employee("废羊羊",22,9000,"男","开发部"));
list.add(new Employee("灰太狼",33,14000,"男","销售部"));
list.add(new Employee("红太狼",28,8000,"女","人事部"));
list.add(new Employee("懒洋洋",17,10300,"女","运营部"));
return list;//返回list集合
}
}
14.3.2 Optional类
Optional类像是一个容器,可以保存任何对象,并且针对NullPointerException空指针异常做了优化,保证Optional类保存的值不会null。因此Optional类是针对“对象可能是针对null也可能不是null”
的场景为开发者提供了优质的解决方案,减少了繁琐的异常处理
Optional类是final修饰的,所以不能有子类 Optional类是带有反省的类,所以该类可以保存任何对象的值
从Optional类的声明代码中就可以看出这种特性,JDK中的部分代码如下
public final class Optional
private final Tvalue;
...//省略其中代码
}
Optional类中有一个叫做value的成员属性 这个属性就是用来保存具体值的 value 是用反省T修饰的,并且还用了final修饰,这表示一个Optional对象只能保存一个值
Optional类提供了很多的封装,校验,和获取值的方法 如下
方法 返回类型
功能描述
empty() Optional
filter() Optional
如果Optional实例的value是有值,并且该值与给定的条件匹配,则返回这个值的Optional实例,否则返回一个表示空值的Optional石磊
get() T 如果Optional实例的value有值,则返回值否则抛出NoSuchElementException > 返回一个value值等于参数值的非null的Optional实例 T 如果Optional实例的value是优质的,则返回value的值,否则返回参数值 Collectors类为收集器类,该类实现了java.util.Collectors接口,可以将Stream流对象进行各种各样的封装,归集,分组等操作,同时,Collectors类还是提供了很多使用的数据加工方法,如数据统计计算等。Collectors类常用方法如下 方法 功能描述 将流中的元素封装成Map集合 数据查找并不是在六种获取的数据(这属于数据过滤)而是判断流中释放符号条件数据,查找的结果时一个boolean值或者一个Optional类对象 1.allMatch()方法 allMatch()方法Stream接口提供的方法,该方法会判断流中的元素是否全部符合某一条件,返回结果是boolean值。如果所有都符合条件则返回true ,否则返回false。 代码如下: package 第十四章; 代码图如下 运行结果 Employee [name=灰太狼, age=33, salsary=14000.0, sex=男, dept=销售部] 数据分组就是将六种元素按照指定的条件分组保存,类似SQL语言中的 CROUPBY关键字分组之后的数据会按照不同的标签别保存成一个集合,然后按照 键值关系封装在Map对象中 代码如下 package 第十四章; 代码图如下: 运行结果如下: 销售部的员工列表如下Employee [name=灰太狼, age=33, salsary=14000.0, sex=男, dept=销售部]
of
ofNullabel
Optional
orElse(T other)
Collectors类
averagingDouble(ToDoubleFunctionmapper) 计算流元素平均值
averagingInt(ToIntFunctionmapper) 计算流元素平均值
averagingLong(ToLongFunction super T> mapper) 计算流元素平均值
countiong() 统计元素个数
maxBy(Comparatorcomparator) 返回符合条件的最大元素
minBy(Comparator super T >comparator) 返回符合条件的最小元素
summarizingInt(ToIntFunctionmapper) 返回流的元素的和
joining() 按照顺序将元素连接成一个String类型的数据
joining(CharSequence delimiter) 按照顺序将元素连接成一个String类型数据,并只当元素之间的分隔符
toList() 江流中的元素封装成List集合
toMap(Function superT,?extends K> ketMapper, Function value Mpper)
toSet() 将流中的元素封装成Set集合
groupingBy(Function super T,extends K> classifier) 根据分类函数对元素进行分组,并将结果封装成一个Mao集合
groupinBy(Function super ,?extends K>classifier,Collector super T A ,D>downstream) 根据分类函数对元素进行分组,并将结果封装成一个Map集合。第一个参数为一级分组条件,第二个参数为二级分组条件
数据查找
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
List
Stream
//筛选奶奶里大于30岁的员工
stream = stream.filter(emp -> emp.getAge()>30);//拿去指定条件 输出满足条件的数据 最终为了stream类型(流) filter返回一个满足条件的流
//stream= stream.limit(2);//限制输出的指定条数
List
for (Employee i:result) {
System.out.print(i);
}
}
}
数据分组
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
Stream
Map
Set
for(String dept:depts){
System.out.print(dept+"的员工列表如下");
List
for(Employee e :temp) {
System.out.println(e);
}
System.out.println();
}
}
}
人事部的员工列表如下Employee [name=红太狼, age=28, salsary=8000.0, sex=女, dept=人事部]
开发部的员工列表如下Employee [name=废羊羊, age=22, salsary=9000.0, sex=男, dept=开发部]
设计部的员工列表如下Employee [name=美羊羊, age=15, salsary=3000.0, sex=女, dept=设计部]
运营部的员工列表如下Employee [name=喜羊羊, age=18, salsary=9000.0, sex=男, dept=运营部]
Employee [name=懒洋洋, age=17, salsary=10300.0, sex=女, dept=运营部