携程四面:说说Lambda表达式的演化过程!
前言
能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口)。 这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制(重点),在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。
读者福利:Java核心学习笔记+2021最新大厂面试真题共享!
一、演化过程
A.基础类的准备
package com.os.model;
import java.util.Objects;
public class Employee {
private int id;
private String name;
private int age;
private double salary;
//省略生成的getteer和setter方法,构造方法、toString方法、hashCode、equals方法
}
复制代码
B.筛选数据
我们根据不同的筛选条件需要设置不同的方法,增加了很多的代码量。
package com.os.test;
import com.os.model.Employee;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Demo01 {
private static List<Employee> emps = Arrays.asList(
new Employee(101, "悟空", 18, 9999.99),
new Employee(102, "八戒", 59, 6666.66),
new Employee(103, "唐僧", 28, 3333.33),
new Employee(104, "沙僧", 8, 7777.77),
new Employee(105, "白龙马", 38, 5555.55)
);
public static void main(String[] args) {
System.out.println("===>1.筛选年龄");
List<Employee> list = filterEmployeeAge(emps);
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("===>2.筛选工资");
list = filterEmployeeSalary(emps);
for (Employee employee : list) {
System.out.println(employee);
}
}
/**
* 需求:获取公司中年龄小于 35 的员工信息
* @param employeeList
* @return
*/
public static List<Employee> filterEmployeeAge(List<Employee> employeeList){
List<Employee> list = new ArrayList<>();
for (Employee emp : employeeList) {
if(emp.getAge() <= 35){
list.add(emp);
}
}
return list;
}
/**
* 需求:获取公司中工资大于 5000 的员工信息
* @param employeeList
* @return
*/
public static List<Employee> filterEmployeeSalary(List<Employee> employeeList){
List<Employee> list = new ArrayList<>();
for (Employee emp : employeeList) {
if(emp.getSalary() >= 5000){
list.add(emp);
}
}
return list;
}
}
复制代码
C.代码进化:策略设计模式
(1)定义筛选条件的泛型接口
package com.os.service;
/**
* 针对于数据的筛选条件的接口
*/
public interface ObjectDataPredicate<T> {
boolean test(T t);
}
复制代码
(2)实现类去实现不同的筛选方式
按照年龄进行筛选实现类
package com.os.service;
import com.os.model.Employee;
public class FilterEmployeeForAge implements ObjectDataPredicate<Employee> {
@Override
public boolean test(Employee employee) {
return employee.getAge() <= 35;
}
}
复制代码
按照工资进行筛选实现类
package com.os.service;
import com.os.model.Employee;
public class FilterEmployeeForSalary implements ObjectDataPredicate<Employee> {
@Override
public boolean test(Employee employee) {
return employee.getSalary() >= 5000;
}
}
复制代码
(3)策略模式的实现代码
public static List<Employee> filterEmployee(List<Employee> emps, ObjectDataPredicate<Employee> objectDataPredicate){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(objectDataPredicate.test(employee)){
list.add(employee);
}
}
return list;
}
复制代码
完整代码如下
package com.os.test;
import com.os.model.Employee;
import com.os.service.FilterEmployeeForAge;
import com.os.service.FilterEmployeeForSalary;
import com.os.service.ObjectDataPredicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Demo02 {
private static List<Employee> emps = Arrays.asList(
new Employee(101, "悟空", 18, 9999.99),
new Employee(102, "八戒", 59, 6666.66),
new Employee(103, "唐僧", 28, 3333.33),
new Employee(104, "沙僧", 8, 7777.77),
new Employee(105, "白龙马", 38, 5555.55)
);
public static void main(String[] args) {
List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge());//接口回调
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("------------------------------------------");
List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary());//接口回调
for (Employee employee : list2) {
System.out.println(employee);
}
}
public static List<Employee> filterEmployee(List<Employee> emps, ObjectDataPredicate<Employee> objectDataPredicate){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(objectDataPredicate.test(employee)){
list.add(employee);
}
}
return list;
}
}
复制代码
这种代码的实现类太多了
D.代码进化:匿名内部类
package com.os.test;
import com.os.model.Employee;
import com.os.service.FilterEmployeeForAge;
import com.os.service.FilterEmployeeForSalary;
import com.os.service.ObjectDataPredicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Demo03 {
private static List<Employee> emps = Arrays.asList(
new Employee(101, "悟空", 18, 9999.99),
new Employee(102, "八戒", 59, 6666.66),
new Employee(103, "唐僧", 28, 3333.33),
new Employee(104, "沙僧", 8, 7777.77),
new Employee(105, "白龙马", 38, 5555.55)
);
public static void main(String[] args) {
List<Employee> list = filterEmployee(emps, new ObjectDataPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getId() <= 103;
}
});//接口回调
for (Employee employee : list) {
System.out.println(employee);
}
}
public static List<Employee> filterEmployee(List<Employee> emps, ObjectDataPredicate<Employee> objectDataPredicate){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(objectDataPredicate.test(employee)){
list.add(employee);
}
}
return list;
}
}
复制代码
通过内部类,我们能发现整个代码中核心的部分就是一句话
employee.getId() <= 103
E.代码进化:Lambda 表达式
package com.os.test;
import com.os.model.Employee;
import com.os.service.ObjectDataPredicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Demo04 {
private static List<Employee> emps = Arrays.asList(
new Employee(101, "悟空", 18, 9999.99),
new Employee(102, "八戒", 59, 6666.66),
new Employee(103, "唐僧", 28, 3333.33),
new Employee(104, "沙僧", 8, 7777.77),
new Employee(105, "白龙马", 38, 5555.55)
);
public static void main(String[] args) {
/*
List<Employee> list = filterEmployee(emps, new ObjectDataPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getId() <= 103;
}
});//接口回调
*/
List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("------------------------------------------");
List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
for (Employee employee : list2) {
System.out.println(employee);
}
}
public static List<Employee> filterEmployee(List<Employee> emps, ObjectDataPredicate<Employee> objectDataPredicate){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(objectDataPredicate.test(employee)){
list.add(employee);
}
}
return list;
}
}
复制代码
Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
二、Lambda基础语法
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为->
,该操作符被称为Lambda 操作符或剪头操作符。它将Lambda 分为两个部分:
- 左侧:指定了Lambda 表达式需要的所有参数(对应接口中形参)
- 右侧:指定了Lambda 体,即Lambda 表达式要执行的功能。(方法体,可以推断返回值类型)
A.格式1:无参数,无返回值
package com.os.print.service;
@FunctionalInterface//定义接口函数
public interface Printer01 {
void print();
}
复制代码
package com.os.print.service;
public class Demo01 {
public static void main(String[] args) {
//之前我们可以使用你们实现类
Printer01 out = new Printer01() {
@Override
public void print() {
System.out.println("匿名实现类");
System.out.println("====>"+Math.random());
}
};
out.print();
//使用Lambda表达式
out = ()-> System.out.println("方法体只有一行,可以省略大括号");
out.print();
out = ()->{
System.out.println("方法体有很多,需要使用大括号搞定");
System.out.println("====>"+Math.random());
};
out.print();
}
}
复制代码
B.格式2:有一个参数,无返回值
package com.os.print.service;
@FunctionalInterface//定义接口函数
public interface Printer02<T> {
void print(T t);
}
复制代码
public class Demo02 {
public static void main(String[] args) {
//通过泛型推断参数e的类型
Printer02<Employee> out01 = (e)-> System.out.println(e);
out01.print(new Employee(999,"悟空",19,25000));
Printer02<Integer> out2 = (e)-> System.out.println(e);
out2.print(999);
Printer02<String> out3 = (e)-> System.out.println(e);
out3.print("西游记");
}
}
复制代码
C.格式3:若只有一个参数,小括号可以省略不写
package com.os.print.service;
import com.os.model.Employee;
public class Demo02 {
public static void main(String[] args) {
//通过泛型推断参数e的类型
Printer02<Employee> out01 = e-> System.out.println(e);
out01.print(new Employee(999,"悟空",19,25000));
Printer02<Integer> out2 = e-> System.out.println(e);
out2.print(999);
Printer02<String> out3 = e-> System.out.println(e);
out3.print("西游记");
}
}
复制代码
D.格式4:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
使用系统有的函数接口测试如下:
package com.os.print.service;
import com.os.model.Employee;
import java.util.Comparator;
public class Demo03 {
public static void main(String[] args) {
/*
public interface Comparator<T> {
}
* */
Comparator<Integer> comparator = (x,y)->{
System.out.println("接口函数方法");
return Integer.compare(x,y);
};
}
}
复制代码
自定义函数接口方法:
package com.os.print.service;
@FunctionalInterface//定义接口函数
public interface Printer03<T> {
T print(T t1,T t2);
}
复制代码
package com.os.print.service;
import com.os.model.Employee;
import java.util.Comparator;
public class Demo03 {
public static void main(String[] args) {
Printer03<String> out01 = (s1,s2)->{
String str = s1.concat(s2);
return str.toUpperCase();
};
System.out.println(out01.print("abc","efg"));
}
}
复制代码
自定义函数接口方法两个参数:
package com.os.print.service;
@FunctionalInterface//定义接口函数
public interface Printer04<T,R> {
R print(T t1, R t2);
}
复制代码
package com.os.print.service;
import com.os.model.Employee;
public class Demo04 {
public static void main(String[] args) {
Printer04<String, Employee> out = (name,e)->{
e.setName(name);
return e;
};
Employee employee = out.print("西游记",new Employee());
System.out.println(employee);
}
}
复制代码
E.格式5:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
package com.os.print.service;
import com.os.model.Employee;
import java.util.Comparator;
public class Demo04 {
public static void main(String[] args) {
Comparator<Integer> comparator = (x, y)->Integer.compare(x,y);
System.out.println(comparator.compare(1,2));
Printer04<String, Employee> out = (name,e)->e;
Employee employee = out.print("西游记",new Employee());
System.out.println(employee);
}
}
复制代码
F.格式5:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y); //一般不会使用这种写法
复制代码
上述Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”
lambda语法的总结如下:
- 上联:左右遇一括号省
- 下联:左侧推断类型省
- 横批:能省则省
三、函数式接口
-
只包含一个抽象方法的接口,称为函数式接口。
-
你可以通过Lambda 表达式来创建该接口的对象。
- (若Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
-
在任意函数式接口上设置
@FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
上述的示例中,我们已经定义过函数式接口,但是我们不可能每次都要自己定义函数式接口,太麻烦!所以,Java内置了函数式接口在java.util.function
包下
A.Predicate 断言型接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
复制代码
package com.os.print.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Demo05 {
public static void main(String[] args) {
List<String> list = Arrays.asList("Hello", "pangsir", "Lambda", "www", "ok");
List<String> strList = filterStr(list, (s) -> s.length() > 3);
for (String str : strList) {
System.out.println(str);
}
}
//需求:将满足条件的字符串,放入集合中
public static 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;
}
}
复制代码
B.Function<T,R> 函数型接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
复制代码
package com.os.print.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
public class Demo06 {
public static void main(String[] args) {
String newStr = strHandler("\t\t\t 西游记齐天大圣孙悟空 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("西游记齐天大圣孙悟空", (str) -> str.substring(2, 5));
System.out.println(subStr);
}
//需求:用于处理字符串
public static String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
}
复制代码
C.Supplier 供给型接口
@FunctionalInterface
public interface Supplier<T> {
T get();
}
复制代码
package com.os.print.service;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Demo07 {
public static void main(String[] args) {
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:产生指定个数的整数,并放入集合中
public static 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;
}
}
复制代码
D.Consumer 消费型接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
复制代码
package com.os.print.service;
import java.util.function.Consumer;
public class Demo08 {
public static void main(String[] args) {
happy(10000, (m) -> System.out.println("购物消费:" + m + "元"));
}
public static void happy(double money, Consumer<Double> con){
con.accept(money);
}
}
复制代码
java.util.function包下有很多有用的函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer |
T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier |
无 | T | 返回类型为T的对象,包含方法:T get(); |
Function<T, R>函数型接口 | T | R | 对类型为T的对象应用操作。结果R类型的对象。方法:R apply(T t); |
Predicate |
T | boolean | 确定类型为T的对象是否满足某约束,boolean 值。含方法boolean test(T t); |
BiFunction<T,U,R> | T,U | R | 对类型为T,U参数应用操作,返回R类型的结果。包含方法为:R apply(T t,U u); |
UnaryOperator |
T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为T apply(Tt); |
BinaryOperator |
T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为T apply(Tt1,Tt2); |
BiConsumer<T,U> | T,U | void | 对类型为T,U参数应用操作。包含方法为void accept(T t,U u) |
ToIntFunction |
T | intlongdouble | 分别计算int、long、double、值的函数 |
IntFunction |
intlongdouble | R | 参数分别为int、long、double类型的函数 |
四、方法引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
方法引用:使用操作符“::” 将方法名和对象或类的名字分隔开来。
- 对象::实例方法
- 类::静态方法
- 类::实例方法
注意:
-
① 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致! 复制代码
-
② 若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName 复制代码
A.对象的引用 :: 实例方法名
package com.os.print.service;
import com.os.model.Employee;
import java.util.function.Supplier;
public class Demo09 {
public static void main(String[] args) {
Employee emp = new Employee(101, "张三", 18, 9999.99);
Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get());
System.out.println("----------------------------------");
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
}
复制代码
B.类 :: 静态方法名
package com.os.print.service;
import java.util.function.BiFunction;
import java.util.function.Supplier;
public class Demo10 {
public static void main(String[] args) {
BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
System.out.println(fun.apply(1.5, 22.2));
System.out.println("--------------------------------------------------");
BiFunction<Double, Double, Double> fun2 = Math::max;
System.out.println(fun2.apply(1.2, 1.5));
}
}
复制代码
C.类 :: 实例方法名
package com.os.print.service;
import com.os.model.Employee;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
public class Demo11 {
public static void main(String[] args) {
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcde", "abcde"));
System.out.println("-----------------------------------------");
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));
System.out.println("-----------------------------------------");
Function<Employee, String> fun = (e) -> e.getName();
System.out.println(fun.apply(new Employee()));
System.out.println("-----------------------------------------");
Function<Employee, String> fun2 = Employee::getName;
System.out.println(fun2.apply(new Employee()));
}
}
复制代码
若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
D.构造方法引用 ClassName::new
格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
package com.os.print.service;
import com.os.model.Employee;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
public class Demo12 {
public static void main(String[] args) {
Function<String, Employee> fun = Employee::new;
System.out.println(fun.apply("悟空"));
BiFunction<String, Integer, Employee> fun2 = Employee::new;
System.out.println(fun2.apply("八戒",18));
}
}
复制代码
E.数组引用
package com.os.print.service;
import com.os.model.Employee;
import java.util.function.BiFunction;
import java.util.function.Function;
public class Demo13 {
public static void main(String[] args) {
Function<Integer, String[]> fun = (e) -> new String[e];
String[] strs = fun.apply(10);
System.out.println(strs.length);
System.out.println("--------------------------");
Function<Integer, Employee[]> fun2 = Employee[] :: new;
Employee[] emps = fun2.apply(20);
System.out.println(emps.length);
}
}
关注公众号:麒麟改bug 共享2021金三银四Java面试题总结集锦!