本文参考了网上Java8的视频。
一. Lambda表达式
1. 为什么要用Lambda表达式?
Lambda是一个匿名函数,我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样传递)
举几个例子,看看Lambda使用之后的变化:
案例1: 为TreeSet实现自己的比较方法。
/**
* 原来的匿名内部类
核心的方法其实就是Integer.compare()
*/
public void test1(){
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> ts = new TreeSet<>(comparator);
}
/**
* Lambda表达式加工后
*/
public void test2(){
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> ts = new TreeSet<>(comparator);
}
案例二: 获取当前员工中年龄大于20岁的员工列表
原先做法,每种条件都重写一遍遍历,按规则过滤的代码,造成了大量的代码冗余。
/**
* 获取当前员工中年龄大于20岁的员工信息
*/
public void test3_1(){
//获取所有的员工信息列表
List<Employee> employeeList = new ArrayList<>();
//年龄大于20岁的员工信息列表
List<Employee> newEmployeeList = new ArrayList<>();
employeeList.forEach(employee -> {
if(employee.getAge() > 20){
newEmployeeList.add(employee);
}
});
}
/**
* 获取当前员工中薪资待遇大于5000的员工信息
*/
public void test3_2(){
//获取所有的员工信息列表
List<Employee> employeeList = new ArrayList<>();
//年龄大于20岁的员工信息列表
List<Employee> newEmployeeList = new ArrayList<>();
employeeList.forEach(employee -> {
if(employee.getSalary() > 5000){
newEmployeeList.add(employee);
}
});
}
优化1: 策略设计模式。
将遍历的代码块提取出来,我们只需要根据不同的业务需求,书写过滤规则的代码即可。
interface Predicate<Employee>{
//过滤规则,符合条件才为true
boolean test(Employee employee);
}
/**
* 按照年龄过滤
*/
class CompareByAgePredicate implements Predicate<Employee>{
@Override
public boolean test(Employee employee) {
return employee.getAge() > 20;
}
}
/**
* 按照薪资待遇过滤
*/
class CompareBySalaryPredicate implements Predicate<Employee>{
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 5000;
}
}
/**
* 循环遍历用户列表 (公共代码)
*/
public List<Employee> test4(List<Employee> employeeList, Predicate<Employee> predicate){
//年龄大于20岁的员工信息列表
List<Employee> newEmployeeList = new ArrayList<>();
employeeList.forEach(employee -> {
if(predicate.test(employee)){
newEmployeeList.add(employee);
}
});
return newEmployeeList;
}
/**
* 最终执行代码
*/
public void test5(){
//获取所有的员工信息列表
List<Employee> employeeList = new ArrayList<>();
//获取年龄大于20岁的员工信息列表
List<Employee> newEmployeeList1 = test4(employeeList, new CompareByAgePredicate());
//获取薪资待遇大于5000的员工信息列表
List<Employee> newEmployeeList2 = test4(employeeList, new CompareBySalaryPredicate());
}
优化2: 使用匿名内部类
由于优化1中,需要针对每一种约束条件都建立一个独立的class,过于麻烦,因此直接使用匿名内部类实现。
public void test6(){
//获取所有的员工信息列表
List<Employee> employeeList = new ArrayList<>();
//获取年龄大于20岁的员工信息列表
List<Employee> newEmployeeList1 = test4(employeeList, new Predicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getAge()>20;
}
});
//获取薪水大于5000的员工信息列表
List<Employee> newEmployeeList2 = test4(employeeList, new Predicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 5000;
}
});
}
优化3: 使用Lambda表达式
public void test7(){
//获取所有的员工信息列表
List<Employee> employeeList = new ArrayList<>();
//获取年龄大于20岁的员工信息列表
List<Employee> newEmployeeList1 = test4(employeeList, (employee) -> employee.getAge()>20);
newEmployeeList1.forEach(System.out::println);
//获取薪水大于5000的员工信息列表
List<Employee> newEmployeeList2 = test4(employeeList, (employee -> employee.getSalary()>5000));
newEmployeeList2.forEach(System.out::println);
}
优化4: 使用stream API
public void test8(){
//获取所有的员工信息列表
List<Employee> employeeList = new ArrayList<>();
employeeList.stream()
.filter((e) -> e.getSalary() >=5000)
.limit(2)
.forEach(System.out::println);
}
二. Lambda表达式基础语法
1. 操作符
JDK8中引入了一个新的操作符"->",该操作符被称为箭头操作符或Lambda操作符。箭头操作符将Lambda分成了两个部分:
左侧: Lambda表达式的参数列表
右侧: Lambda表达式中需要实现的功能, 即Lambda体
2. 语法格式
1. 无参数,无返回值: () -> System.out.println("Hello!");
2. 有一个参数,无返回值: (x) -> System.out.println(x);
3. 有两个以上的参数,有返回值,并且Lambda体中有多条语句:
Comparator<Integer> com = (x, y) -> {
System.out.println("语句1");
System.out.println("语句2");
return Integer.compare(x, y);
};
4. 若有两个以上参数,有返回值,但Lambda体中只有一条语句:
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
5. Lambda参数列表中的数据类型可以省略不写,这是因为JVM编译器可以通过上下文推断出数据类型,即"类型推断"
(Integer x, Integer y) -> Integer.compare(x,y)
3. 函数式接口
若接口中只有一个抽象方法,那么该接口被称为"函数式接口"。可以使用@FuntionInterface修饰,用于检查该接口是否是函数式接口。
@FunctionalInterface
interface FunInterface{
//多写或者不写抽象方法都会报错!
void test();
}
三. Lambda表达式练习
1. 对一个数进行运算
{
/**
* 需求:对一个数进行运算
*/
public void test6(){
//乘法
int result1 = getOperation(1000, (x) -> x * 50);
System.out.println(result1);
//加法
int result2 = getOperation(1000, (y) -> y+200);
}
public int getOperation(int number, OperationFun fun){
return fun.getValue(number);
}
}
@FunctionalInterface
interface OperationFun{
int getValue(int number);
}
2. 调用Collections.sort()方法,通过定制排序比较两个Employee(先按照年龄比,年龄相同再按照姓名比),使用Lambda作为参数传递。
@org.junit.Test
public void test1(){
List<Employee> employeeList = new ArrayList<Employee>(){{
add(Employee.builder().name("Jack").age(25).build());
add(Employee.builder().name("Blue").age(20).build());
}};
Collections.sort(employeeList, (e1, e2)-> {
if(e1.getAge() == e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else{
return Integer.compare(e1.getAge(), e2.getAge());
}
});
employeeList.forEach(System.out::println);
}
3. ①声明函数式接口,接口中声明抽象方法,public String getValue(String str) ②声明类TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值。
③再将一个字符串的第2个和第4个索引位置进行截取子串。
@FunctionalInterface
public interface CalculateInterface {
String getValue(String str);
}
public void test2(){
convertStr("\t\t\t 声明抽象方法 ", str -> str.toUpperCase());
convertStr("\t\t\t 声明抽象方法 ", str -> str.substring(2, 5));
}
private String convertStr(String str, CalculateInterface inter){
return inter.getValue(str);
}
4. ①声明一个带两个泛型的函数式接口,泛型类型为<T,R> T为参数,R为返回值。 ②接口中声明对应的抽象方法。
③在TestLambda类中声明方法,使用接口作为参数,计算两个long型参数的和。 ④再计算两个long型 参数的乘积。
@FunctionalInterface
public interface Test3Interface<T, R> {
R test(T t1, T t2);
}
@org.junit.Test
public void test3(){
//计算和
Long result1 = calculate(1L,2L, (x, y) -> x+y);
System.out.println(result1);
//计算乘积
Long result2 = calculate(1L, 2L, (x, y) -> x * y);
System.out.println(result2);
}
private Long calculate(Long t1 , Long t2, Test3Interface<Long, Long> inter){
return inter.test(t1, t2);
}
四. Java8 内置的四大核心函数式接口
1. Consumer<T> 消费型接口
void accept(T t);
@org.junit.Test
public void test(){
shopping(5000, x -> System.out.println(x));
}
private void shopping(double money, Consumer<Double> consumer){
consumer.accept(money);
}
2. Supplier<T> 供给型接口
T get();
/**
* 供给型接口
*/
@org.junit.Test
public void test1(){
List<Integer> integerList = getNumberList(10, () -> (int)(Math.random() * 100));
integerList.forEach(System.out::println);
}
/**
* 获取指定个数的指定类型数据集合
*/
private List<Integer> getNumberList(Integer nums, Supplier<Integer> supplier){
List<Integer> integerList = Lists.newArrayList();
for(int i=0; i< nums; i++){
int target = supplier.get();
integerList.add(target);
}
return integerList;
}
3. Function<T, R>函数式接口
R apply(T t);
/**
* 函数型接口
*/
@org.junit.Test
public void test2(){
//去除字符串中所有的空格
String str1 = handlerStr("我爱你 祖国!", s -> s.replaceAll(" ", ""));
System.out.println(str1);
//获取第2至第4个字符
String str2 = handlerStr("我爱你 祖国!", s-> s.substring(2,5));
System.out.println(str2);
}
/**
* 字符串处理
*/
private String handlerStr(String str, Function<String, String> func){
return func.apply(str);
}
4. Predicate<T>: 断言型接口
boolean test(T t);
/**
* Predicate<T> 断言型接口
*/
@org.junit.Test
public void test3(){
List<String> targetList = Arrays.asList("Hello", "Java", "World");
List<String> strings = filterStr(targetList, str -> str.length() == 5);
strings.forEach(System.out::println);
}
//满足条件的字符串放入集合中
public List<String> filterStr(List<String> stringList, Predicate<String> predicate){
List<String> list = new ArrayList<>();
stringList.forEach(str -> {
if(predicate.test(str)){
list.add(str);
}
});
return list;
}
5. 其它子接口
五. 方法引用和构造器引用
1. 方法引用
若Lambda体中的方法已经被实现了,那么就可以使用方法引用。
方法引用主要有以下三种表现形式:
1. 对象::实例方法名
/**
* 对象::实例方法名
*/
public void test1(){
//之前的写法
PrintStream ps1 = System.out;
Consumer<String> consumer1 = x -> ps1.println(x);
//使用方法引用
PrintStream ps2 = System.out;
Consumer<String> consumer2 = ps2::println;
//更进一步
Consumer<String> consumer3 = System.out::println;
/*
但使用方法引用有一个前提: 待实现的接口的参数列表和返回值必须与方法引用的参数类表和返回值保持一致
比如此处:
Consumer中的抽象方法 void accept(T t);
PrintStream中已经实现的方法引用: void println(String x);
*/
}
@org.junit.Test
public void test2(){
Employee employee = new Employee("Jack", 25, 22000);
//之前的写法
Supplier<String> supplier1 = () -> employee.getName();
System.out.println(supplier1.get());
//使用方法引用
Supplier<Integer> supplier2 = employee::getAge; //由于参数列表一致,因此连( )括号都不用写了
System.out.println(supplier2.get());
}
2. 类::静态方法名
/**
* 类::静态方法
*/
public void test3(){
//比较两个数的大小
//之前的写法
Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);
//使用方法引用
Comparator<Integer> comparator2 = Integer::compare;
}
3. 类::实例方法名
/**
* 类::实例方法
*/
public void test4(){
//比较两个字符串是否相同
//之前的写法
BiPredicate<String, String> biPredicate1 = (x, y) -> x.equals(y); //注意:String equals( )方法是一个实例方法
//使用方法引用
BiPredicate<String, String> biPredicate2 = String::equals;
/*
使用类::实例方法 这种方法引用有其前提条件:
Lambda参数列表的第一个参数必须是实例方法的调用者,第二个参数必须是实例方法的参数。
*/
}
2. 构造器引用
/**
* 构造器引用
*/
public void test5(){
//创建对象
//原来的方法
Supplier<Employee> supplier1 = () -> new Employee();
//使用方法引用
Supplier<Employee> supplier2 = Employee::new;
/*
注意,使用构造器引用时,最终调用哪个构造函数,与函数式接口的方法参数列表相关。
*/
Function<Integer, Employee> function = Employee::new;
}
3. 数组引用
/**
* 数组引用
*/
@org.junit.Test
public void test6(){
//获取指定长度的空数组
//原来的方法
Function<Integer, String[]> func1 = (x) -> new String[x];
System.out.println(func1.apply(10).length);
//使用方法引用
Function<Integer, String[]> func2 = String[]::new;
System.out.println(func2.apply(20).length);
}