我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
1 简介
Lambda是一个匿名函数,可以将Lambda表达式理解为一段可以传递的代码(将代码像数据一样传递),让代码更加简洁灵活。
2 匿名内部类与Lambda表达式
2.1 初探
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);
TreeSet<Integer> treeSet=new TreeSet<>((x,y)->Integer.compare(x,y));
2.2 从匿名内部类到Lambda表达式
假设我们有一个员工类Employee,我们要用一些条件对公司的员工进行筛选,为了提高代码的复用性,我们可以设计一个专门的接口:
interface MyPredicate<T> {
public boolean condition(T t);
}
在接口中的方法condition()就是我们要进行筛选的条件,针对不同的筛选条件,我们只需要多次实现这个接口即可:
class FilterEmployByAge implements MyPredicate<Employee> {
@Override
public boolean condition(Employee employee) {
return employee.getAge() > 35;
}
}
在业务处理过程中,我们使用filterEmployees1()方法调用接口MyPredicate:
public List<Employee> filterEmployees1(List<Employee> list, MyPredicate<Employee> myPredicate) {
List<Employee> result = new ArrayList<>();
for (Employee employee : list) {
if (myPredicate.condition(employee)) {
result.add(employee);
}
}
return result;
}
//filterEmployees1 方法调用的时候选择myPredicate接口的实现类
List<Employee> result1 = test.filterEmployees1(employees, new FilterEmployByAge());
其实真正需要的是接口中的condition()方法。换句话说filterEmployees1()希望接受一段方法代码作为参数,但没有办法直接传递这个方法代码本身,只能传递一个接口。
优化方案1: 针对这种情况,我们可以使用策略设计模式+匿名内部类的方式来简化代码。即,在选择筛选条件的时候,使用匿名内部类的方式实现MyPredicate接口(代替了接口实现类):
//无需再专门实现MyPredicate接口,但是filterEmployees1()方法还是需要写
List<Employee> result2 = test.filterEmployees1(employees, new MyPredicate<Employee>() {
@Override
public boolean condition(Employee employee) {
return employee.getAge() > 35;
}
});
优化方案2: 从上述优化后的代码可以看到,除了必要的筛选条件,还是有很多无用代码,可以使用Lambda表达式进一步进行优化:
//无需再专门实现MyPredicate接口,但是filterEmployees1()方法还是需要写
List<Employee> result2 = test.filterEmployees1(employees, (e)->e.getAge()>35);
//使用Lambda表达式对遍历List的优化
result2.forEach(System.out::println);
优化方案3: 其实过滤一个List这种应用场景,可以直接使用java8新特性的另一种Stream API处理方法:
3 Lambda表达式详解
3.1 基础语法
-> 箭头操作符: Java8引入了一个新的操作符“->”,该操作符称为Lambda操作符,其将Lambda表达式拆为两部分: 左侧:Lambda表达式的参数列表; 右侧:Lambda体,即Lambda表达式需要执行的功能。
常见语法格式:
格式1 对应抽象方法无参数列表,无返回值
//匿名函数实现Runable对象
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
//使用Lambda实现Runable对象
Runnable r1 = () -> System.out.println("hello");
//两种调用
r.run();
r1.run();
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
格式2有一个参数,无返回值(参数的小括号可不写)
util提供的Comsumer接口中accept方法就是只有参数,且没有返回值。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
//利用Lambda表达式对Consumer接口中accept方法的实现
Consumer<String> consumer=(x)-> System.out.println(x);
//等同于
Consumer<String> consumer1=System.out::println;
consumer.accept("test print");
格式3 多个参数,Lambda体多条语句,有返回值
Comparator<Integer> comparator=(x,y)->{
System.out.println("test");
return Integer.compare(x,y);
};
格式4 Lambda体中只有一条语句,且有返回值(return可省略)
Comparator<Integer> comparator=(x,y)->Integer.compare(x,y);
//等同于
Comparator<Integer> comparator1= Integer::compare;
note Lambda表达式需要函数式接口支持(即接口中只有一个抽象方法); Lambda表达式的参数列表数据类型可以不写,jvm可通过上下文推断出数据的类型; 对于只调用一个方法的Lambda语句,可以省略参数,在调用方法前添加::(如格式2和格式4demo所示),即使用方法引用的方式进行调用。
3.2 Java内置的函数式接口
1)常用的四大核心函数式接口
消费性接口
//消费money
public class LambdaTest {
public static void main(String args[]) {
happy(12.5,System.out::println);
}
static void happy(double money, Consumer<Double> consumer) {
consumer.accept(money);
}
}
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
供给型接口
//生成随机数组
public class LambdaTest {
public static void main(String args[]) {
List<Integer> res=getNumList(10, ()->(int)(Math.random()*10));
}
public static List<Integer> getNumList(Integer length, Supplier<Integer> supplier){
List<Integer> list=new ArrayList<>();
for(int i=0;i<length;i++){
Integer num=supplier.get();
list.add(num);
}
return list;
}
}
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
函数型接口
//操作字符串
public class LambdaTest {
public static void main(String args[]) {
System.out.println(strHandle("AABBCC",x->x.toLowerCase()));
}
public static String strHandle(String str, Function<String,String> function){
return function.apply(str);
}
}
段言型接口
//过滤字符串列表
public class LambdaTest {
public static void main(String args[]) {
List<String> resStrList = getStrList(strList, str -> str.length() > 5);
}
public static List<String> getStrList(List<String> strList, Predicate<String> predicate) {
List<String> res = new ArrayList<>();
strList.forEach(str -> {
if (predicate.test(str)) {
res.add(str);
}
});
return res;
}
}
note
- 常用的
::符号就是由消费者接口实现的; - 常用的stream中的filter方法是由断言型接口实现的;
2)其他一些不太常用的子接口
3.3 一些其他Demo
1)调用Collections.sort()方法,通过定制排序比较Employee对象(先比较年龄,年龄相同比较名字)
// 自定义排序规则
Collections.sort(employees, (o1, o2) -> {
if (o1.getAge() == o2.getAge()) {
return o1.getName().compareTo(o2.getName());
} else {
return Integer.compare(o1.getAge(), o2.getAge());
}
} );
2)利用策略模式,声明函数式接口,接口中声明抽象方法getValue,在另一个类中编写一个方法使用接口作为参数,将一个字符串变为大写
public class LambdaTest {
public static void main(String args[]) {
// 在用的时候再指定策略,这里也可以使用匿名内部类的方式来实现
System.out.println(toUpper("aabbcc",x->x.toUpperCase()));
}
// 调用策略的方法
public static String toUpper(String str,Operator operator){
return operator.getValue(str);
}
}
//策略接口
interface Operator{
public String getValue(String str);
}
4 方法引用
4.1 概述
1)使用场景
若Lambda方法体中只调用了一个方法,且已经被实现,那么可以使用方法引用的方式进行调用。
2)注意事项
- 使用方法引用,则要保证Lambda体中调用的方法的参数与返回值与函数式接口定义的一致;
- 只有当Lambda参数列表第一参数是实例方法的调用者,第二个参数是实例方法的参数时,才能使用格式3形式的方法引用。
4.2 基础语法格式
1)对象::实例方法名
Consumer<String> consumer1=(x)-> System.out.println(x);
//等价于
Consumer<String> consumer1= System.out::println;
Employee employee = new Employee();
//匿名内部类持有外部类中的引用
Supplier<String> supplier = () -> employee.getName();
//等价于
Supplier<String> supplier = employee::getName;
String name = supplier.get();
2)类::静态方法名
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
//等价于
Comparator<Integer> comparator1 = Integer::compare;
3)类::实例方法名
BiPredicate<String,String> biPredicate=(x,y)->x.equals(y);
// 等价于
BiPredicate<String,String> biPredicate= String::equals;
5 构造器引用
5.1 概述
若在Lambda方法体中只构造一个对象,那么可以使用构造器引用的方式进行构造,其中调用的构造器根据Lambda体的参数所决定。(例如无参的Lambda会调用无参构造函数)
5.2 基础语法格式
类::new
Supplier<Employee> supplier1 = () -> new Employee();
//等价于
Supplier<Employee> supplier1 = Employee::new;
6 数组引用
6.1 概述
若在Lambda方法体中只构建一个数组,那么可以使用构造器引用的方式进行构造。
6.2 基础语法格式
Type::new
Function<Integer, Integer[]> function = (x) -> new Integer[x];
//等价于
Function<Integer, Integer[]> function = Integer[]::new;