21世纪了,听说你还不会用Lambda表达式?

112 阅读7分钟

我报名参加金石计划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;