一、导引
Lambda 表达式(lambda expression)是一个匿名函数。想要理解Lambda表达式一定要先理解匿名内部类。
二、简介
Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
不采用Lambda的老方法:
Runnable runnable1=new Runnable(){
@Override
public void run(){
System.out.println("Running without Lambda");
}
};
使用Lambda:
Runnable runnable2=()->System.out.println("Running from Lambda");
正如你所看到的,使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。
三、四种接口介绍:
Lambda表达式使得在java编程当中,因为高效的编码方式和对接口与功能实现的分离,极大的减少了编程代码量,下面对四种Lambda内置接口进行简易的描述和功能介绍
* Consumer<T> : 消费型接口
* void accept(T t); :有参数,但是没有返回值
*
* Supplier<T> : 供给型接口 :有返回值,但是没有参数
* T get();
*
* Function<T, R> : 函数型接口 :有返回值,也有参数
* R apply(T t);
*
* Predicate<T> : 断言型接口 :返回boolean 类型,有参数
* boolean test(T t);
使用方式:
1.消费型:
//消费型 consumer:特点有一个参数,这个参数进行处理,然后没有返回值
lamdbs.consumerMoney(100, (e)->{System.out.println("我买东西消费了"+e+"元钱");});
//消费型Lamdba使用:
public void consumerMoney(double money,Consumer<Double> cons) {
cons.accept(money);
}
2.供给型:
//供给型,没有参数, 但是能得到一个返回值:如给定一个数,产生一个随机的数组,供给型,没有参数, 但是能得到一个返回值:如给定一个数,产生一个随机的数组
List<Integer> numsBySuppliers = lamdbs.getNumsBySuppliers(10, ()->(int)(Math.random()*100));
public List<Integer> getNumsBySuppliers(int num,Supplier<Integer> sup){
List<Integer> numList=new ArrayList<>();
for(int i=0;i<num;i++) {
Integer intNum = sup.get();
numList.add(intNum);
}
return numList;
}
3.函数型:
int strLengthByFunction = lamdbs.getStrLengthByFunction("wo shi yanghongtang", (e)->e.length());
//函数Lamdba使用--》传进去一个值,然后处理完成后返回一个值,比如:传进去一个字符串,返回该字符串的长度
public int getStrLengthByFunction(String str,Function<String, Integer> fun) {
return fun.apply(str);
}
4.断言型
List<String> filterStr = lamdbs.filterStr(Arrays.asList("alsfowage","aegt","wq4y","wtqt"), (e)->(e.length()>3));
//将满足条件的字符串,放入集合当中
public List<String> filterStr(List<String> list,Predicate<String> pre){
List<String> list1=new ArrayList<>();
for (String string : list) {
if(pre.test(string)) {
list1.add(string);
}
}
return list1;
}
四、使用 Lambda 表达式原因
Lambda 是一个匿名函数,可以把 Lambda表达式 理解为是一段可以传递的代码 (将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升
语法格式
package com.company.demo;
/**
* 无参无返回值的lambda表达式,不属于Lambda内置的四种函数
*/
interface MyInterface{
public void method();
}
public class Test01 {
public static void main(String[] args) {
//完整写法
MyInterface myInterface1 = ()-> {System.out.println("无参无返回值,一条语句");};
myInterface1.method();
//括号省略写法
MyInterface myInterface2 = ()-> System.out.println("无参无返回值,一条语句,省略{}");
myInterface2.method();
//多条语句不可省略括号{}
MyInterface myInterface3 = ()->{
System.out.println("无参无返回值");
int n = 6;
System.out.println(n);
};
myInterface3.method();
}
}
语法格式一 : 无参数,无返回值
Runnable r1 = () -> System.out.println("Hello Lambda!");
语法格式二 : 有一个参数,并且无返回值
Consumer<String> Con = (x) -> System.out.println(x)
语法格式三 : 若只有一个参数,小括号可以省略不写
Consumer<String> con = x -> System.out.println(x)
Consumer<String> con = (x) -> System. out .println(x);
con.accept( "啦啦啦,我是卖报的小行家" );
语法格式四 : 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator <Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
语法格式五 : 若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator <Integer> com = (x, y) -> Integer.compare(x, y);
语法格式六 : Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y);
注 : Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”
上联 : 左右遇一括号省
下联 : 左侧推断类型省
横批 : 能省则省
Lambda 表达式需要 “函数式接口” 的支持
函数式接口 : 接口中只有一个抽象方法的接口,称为函数式接口,可以通过 Lambda 表达式来创建该接口的对象 (若 Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明) 可以使用注解 @FunctionalInterface 修饰可以检查是否是函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口
@FunctionalInterface
public interface MyFun {
Integer getValue(Integer num);
}
函数式接口中使用泛型 :
@FunctionalInterface
public interface MyFun< T > {
T getValue( T t);
}
从匿名类到 Lambda 的转换
// 原来的匿名内部类作为参数传递
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer. compare (o1, o2);
}
};
TreeSet<Integer> treeSet = new TreeSet<>(comparator);
// Lambda 表达式作为参数传递
Comparator<Integer> comparator = (o1, o2) -> Integer. compare (o1, o2);
TreeSet<Integer> treeSet = new TreeSet<>(comparator);
作为参数传递 Lambda 表达式 : 为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
//需求 : 雇员对象如下,有一个包含许多员工信息的对象 employees,要求获取当前公司中员工年龄大于 35 的员工信息
@Data
public class Employee {
private String name;
private int age;
public Employee() {
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
// ...
@Override
public String toString() {
return "Employee{" + "name='" + name + ", age=" + age + '}';
}
}
//最简单的方式是采用 foreach 循环遍历,以下是各种优化方式
//优化方式一:策略设计模式
@FunctionalInterface
interface MyPredicate<T> {
boolean test(T t);
}
public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean test(Employee employee) {
return employee.getAge() >= 35;
}
}
public List<Employee> filterEmployees3(List<Employee> employees, MyPredicate<Employee> myPredicate) {
List<Employee> emps = new ArrayList<>();
for (Employee employee : employees) {
if (myPredicate.test(employee)) {
emps.add(employee);
}
}
return emps;
}
//调用
List<Employee> emps=filterEmployees3(employees,new FilterEmployeeByAge());
for(Employee employee:emps){
System.out.println(employee.toString());
}
//优化方式二:匿名内部类
List<Employee> emps=filterEmployees3(employees,new MyPredicate<Employee>(){
@Override
public boolean test(Employee employee){
return employee.getAge()>=35;
}
});
for(Employee employee:emps){
System.out.println(employee.toString());
}
//优化方式三:Lambda
filterEmployees3(employees,employee->employee.getAge()>=35).forEach(System.out::println);
//优化方式四:Stream API
employees.stream().filter((employee)->employee.getAge()>=35).limit(5).forEach(System.out::println);
Java8 内置的四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法 : void accept(T t) |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T的对象,包含方法 : T get() |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象,包含方法 : R apply(T t) |
Predicate<T> 断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法 : boolean test(T t) |
//Consumer<T> 消费型接口 :
public void happy( double money, Consumer<Double> con) {
con.accept(money);
}
happy( 10000 , (m) -> System. out .println( "吃大餐,每次消费:" + m + "元" ));
//Supplier<T> 供给型接口 :
// 需求 : 产生指定个数的整数,并放入集合中
public List<Integer> getNumList( int num, Supplier<Integer> sup) {
List<Integer> list = new ArrayList<>();
for ( int i = 0 ; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
getNumList( 10 , () -> ( int ) (Math. random () * 100 )).forEach(System. out ::println);
//Function<T, R> 函数型接口 :
// 需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun) {
return fun.apply(str);
}
String newStr = strHandler( " \t\t\t Good Good Study,Day Day up. " , (str) -> str.trim());
System. out .println(newStr);
//Predicate<T> 断言型接口 :
// 需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre) {
List<String> strList = new ArrayList<>();
for (String str : list) {
if (pre.test(str)) {
strList.add(str);
}
}
return strList;
}
List<String> list = Arrays. asList ( "Hello" , "atguigu" , "Lambda" , "www" , "ok" );
filterStr(list, (s) -> s.length() > 3 ).forEach(System. out ::println);
其他接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结果,包含方法为 : R apply(T t, U u) |
UnaryOperator<T> (Function子接口) | T | T | 对类型为T的对象进行一元运算, 并返回T类型的结果,包含方法为 : T apply(T t) |
BinaryOperator<T> (BiFunction 子接口) | T,T | T | 对类型为T的对象进行二元运算, 并返回T类型的结果,包含方法为 T apply(T t1, T t2) |
BiConsumer<T, U> | T, U | void | 对类型为T, U 参数应用操作,包含方法为 void accept(T t, U u) |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> | T | int long double | 分 别 计 算 int 、 long 、double、 值的函数 |
IntFunction<R> LongFunction<R> DoubleFunction<R> | int long double | R | 参数分别为int、 long、double 类型的函数 |
方法引用
若 Lambda 体中的功能,已经有方法提供实现,可以使用方法引用 (可以将方法引用理解为Lambda 表达式的另外一种表现形式)。当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!) 方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。
如下三种主要使用情况:
- 对象的引用 :: 实例方法名
- 类名 :: 静态方法名
- 类名 :: 实例方法名
注 : 1> 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致(就是函数签名和返回值一致)
2> 若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式 : ClassName::MethodName
PrintStream ps = System. out ;
Consumer<String> con1 = (str) -> ps .println(str);
Consumer<String> con2 = ps::println;
Consumer<String> con3 = System. out::println;
对象的引用 :: 实例方法名
Employee emp = new Employee( 101 , "张三" , 18 , 9999.99 );
Supplier<String> sup = () -> emp .getName();
System. out .println(sup.get());
Supplier<String> sup2 = emp::getName;
System. out .println(sup2.get());
类名 :: 静态方法名
Comparator<Integer> comparator1 = (x, y) -> Integer. compare (x, y);
Comparator<Integer> comparator2 = Integer:: compare ;
BiFunction<Double, Double, Double> fun = (x, y) -> Math. max (x, y);
System. out .println(fun.apply( 1.5 , 22.2 ));
BiFunction<Double, Double, Double> fun2 = Math:: max ;
System. out .println(fun2.apply( 1.2 , 1.5 ));
类名 :: 实例方法名
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System. out .println(bp.test( "abcde" , "abcde" ));
BiPredicate<String, String> bp2 = String::equals;
System. out.println(bp2.test( "abc", "abc"));
Function<Employee, String> fun = (e) -> e.show();
System. out .println(fun.apply( new Employee()));
Function<Employee, String> fun2 = Employee::show;
System. out.println(fun2.apply( new Employee()));
注 : 当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数 (或无参数) 时 : ClassName::methodName构造器引用
构造器的参数列表,需要与函数式接口中参数列表保持一致 (就是函数签名一致)
1> 类名 :: new
Supplier<Employee> sup = () -> new Employee();
System. out .println(sup.get());
Supplier<Employee> sup2 = Employee:: new ;
System. out .println(sup2.get());
Function<String, Employee> fun = Employee:: new ;
BiFunction<String, Integer, Employee> fun2 = Employee:: new ;
数组引用 类型[] :: new
Function<Integer, String[]> fun = (args) -> new String[args];
String[] strs = fun.apply( 10 );
System. out .println(strs. length );
Function<Integer, Employee[]> fun2 = Employee[]:: new ;
Employee[] emps = fun2.apply( 20 );
System. out .println(emps. length );
强大的Stream API
Stream 的操作三个步骤
Stream 的中间操作
筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p)
|
接收 Lambda , 从流中排除某些元素。
|
distinct()
|
筛选,通过流所生成元素的 hashCode() 和 equals() 去
除重复元素
|
limit(long maxSize)
|
截断流,使其元素不超过给定数量。
|
skip(long n)
|
跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素
不足 n 个,则返回一个空流。与 limit(n) 互补
|
映射
方法 | 描述 |
---|---|
map(Function f)
|
接收一个函数作为参数,该函数会被应用到每个元
素上,并将其映射成一个新的元素。
|
mapToDouble(ToDoubleFunction f)
|
接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 DoubleStream。
|
mapToInt(ToIntFunction f)
|
接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 IntStream。
|
mapToLong(ToLongFunction f)
|
接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 LongStream。
|
flatMap(Function f)
|
接收一个函数作为参数,将流中的每个值都换成另
一个流,然后把所有流连接成一个流
|
排序
方法 | 描述 |
---|---|
sorted()
| 产生一个新流,其中按自然顺序排序 |
sorted(Comparator comp)
|
产生一个新流,其中按比较器顺序排序
|
Stream 的终止操作
查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p)
|
检查是否匹配所有元素
|
anyMatch(Predicate p)
|
检查是否至少匹配一个元素
|
noneMatch(Predicate p)
|
检查是否没有匹配所有元素
|
findFirst()
|
返回第一个元素
|
findAny()
|
返回当前流中的任意元素
|
count()
|
返回流中元素总数
|
max(Comparator c)
|
返回流中最大值
|
min(Comparator c)
|
返回流中最小值
|
forEach(Consumer c)
|
内部迭代
(使用 Collection 接口需要用户去做迭
代,称为
外部迭代
。相反,Stream API 使用内部
迭代——它帮你把迭代做了)
|
归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b)
|
可以将流中元素反复结合起来,得到一个值。 返回 T
|
reduce(BinaryOperator b)
|
可以将流中元素反复结合起来,得到一个值。 返回 Optional<T>
|
收集
方法 | 描述 |
---|---|
collect(Collector c)
|
将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
|
方法 | 返回类型 | 作用 |
---|---|---|
toList
| List<T> | 把流中元素收集到List |
List<Employee> emps= list.stream().collect(Collectors.toList());
| ||
toSet
| Set<T> | 把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet());
| ||
toCollection
| Collection<T> | 把流中元素收集到创建的集合 |
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
| ||
counting
| Long | 计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting());
| ||
summingInt
|
Integer
|
对流中元素的整数属性求和
|
inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
| ||
averagingInt
|
Double
|
计算流中元素
Integer
属性的平均
值
|
doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
| ||
summarizingInt
|
IntSummaryStatistics
|
收集流中
Integer
属性的统计值。
如:平均值
|
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
| ||
joining
|
String
|
连接流中每个字符串
|
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
| ||
maxBy
| Optional<T> | 根据比较器选择最大值 |
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
| ||
minBy
|
Optional<T>
|
根据比较器选择最小值
|
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
| ||
reducing
|
归约产生的类型
|
从一个作为累加器的初始值
开始,利用BinaryOperator与
流中元素逐个结合,从而归
约成单个值
|
inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
| ||
collectingAndThen
|
转换函数返回的类型
|
包裹另一个收集器,对其结
果转换函数
|
inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
| ||
groupingBy
|
Map<K, List<T>>
|
根据某属性值对流分组,属
性为
K
,结果为
V
|
Map<Emp.Status, List<Emp>> map= list.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
| ||
partitioningBy
|
Map<Boolean, List<T>>
|
根据
true
或
false
进行分区
|
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
|
————————————————
课程文件地址:Java8Lambde新特性-Java文档类资源-CSDN下载