一文读懂lambda表达式

前言

lambda表达式现在应用得越来越多,以前我总觉得lambda表达式可读性是不是不太好,但是用了它之后,真香。一直想找个时间写篇文章总结一下,但是平时比较忙,就一直没时间写,从周一就立了一个flag,周末一定要写,但是周六又默默地打开了王者荣耀,唉,还一波连跪,就很离谱。抓住周末的尾巴,可算把flag完成了。

一、什么是lambda表达式

lambda表达式是一个匿名函数,我们可以将其理解为一段可以传递的代码。利用它我们可以写出更加简洁、灵活的代码。

二、lambda表达式初体验

我们通过一个简单的例子来体验一下lambda表达式。
需求:将学生按照年龄的大小进行排序。

import lombok.Data;
@Data
public class Student{
    private String name;
    private Integer age;
    private Integer score;
}

传统的Java代码:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class Test {
    public static void main(String[] args) {
        Comparator<Student> comparator = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        Set<Student> students = new TreeSet<>(comparator);
        for (int i = 5; i > 0; i--) {
            students.add(new Student("test"+i,18+i,90));
        }
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

简单的说一下Comparator比较器,它用以自定义排序规则,其中的compare方法接受两个比较的对象,如果第一个参数大于第二个参数,返回正数,等于返回0,小于返回负数。
通过上面传统的Java代码我们可以看到,对于我们开发人员阅读来说,其实compare方法,仅仅只有一行代码是有用的。但是传统的Java代码却占用了6行,可以想象一下,如果程序中包含大量的类似情景,就会使得我们一个程序异常庞大。而对于Java规范来说,一个方法的方法体是不宜过长的。现在,我们使用lambda表达式对上述代码进行优化。

在这之前,我们需要知道Java的函数式接口:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。详细介绍见末尾。

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class Test {
    public static void main(String[] args) {
    	//使用lambda表达式
        Comparator<Student> comparator = (x,y) -> x.getAge()-y.getAge();
        Set<Student> students = new TreeSet<>(comparator);
        for (int i = 5; i > 0; i--) {
            students.add(new Student("test"+i,18+i,90));
        }
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

我们可以看到,使用lambda表达式之后,由原来的6行代码,变成了一行。

我们再通过一个需求,一步步的优化代码,体验lambda给我们带来的好处。
需求:找出年龄大于15岁的学生。

public class Test {
    private static List<Student> students = new ArrayList<>();

    /**
     * 初始化学生列表
     */
    static {
        students.add(new Student("张三",20,85));
        students.add(new Student("李四",14,95));
        students.add(new Student("王五",22,100));
        students.add(new Student("李华",17,77));
    }

    /**
     *
     * @param objStudent 需要查找的学生列表
     * @return 返回的学生列表
     */
    //找出年龄大于15岁的学生
    public static List<Student> filterStudent(List<Student> objStudent){
        List<Student> result = new ArrayList<>();
        for (Student student : objStudent) {
            if(student.getAge() > 15){
                result.add(student);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        List<Student> students = filterStudent(Test.students);
        System.out.println(students);
    }

}

现在需求变了,我想找出分数大于80的学生。
可能很多初学者会这样实现,将filterStudent()再次拷贝一份,将if(student.getAge() > 15)变为if(student.getScore() > 80),那将来增加无数的需求,都需要增加一个方法吗?
其实我们分析一下,需求变了,对于filterStudent方法的大致逻辑是不变的。变的仅仅只是判断条件,那么我们是否可以将判断条件抽取出来,在需要使用这个方法的时候,将判断的逻辑通过参数传递?实现如下:

public interface Filter {
    /**
     * 过滤方法,由使用者去实现
     * @param student
     * @return
     */
    boolean filter(Student student);
}

改造之后的filterStudent方法如下:

/**
     * 
     * @param objStudent 需要查找的学生列表
     * @param filter 过滤规则
     * @return
     */
    public static List<Student> filterStudent(List<Student> objStudent, Filter filter){
        List<Student> result = new ArrayList<>();
        for (Student student : objStudent) {
            if(filter.filter(student)){
                result.add(student);
            }
        }
        return result;
    }

现在,我们如何去使用它呢?

public static void main(String[] args) {
        //找出年龄大于15岁的学生
        Filter filterAge = (student) -> student.getAge() > 15;
        List<Student> resultAge = filterStudent(Test.students,filterAge);
        System.out.println(resultAge);

        //找出分数大于80分的学生
        Filter filterScore = (student) -> student.getScore() > 80;
        List<Student> resultScore = filterStudent(Test.students,filterScore);
        System.out.println(resultScore);
    }

好了,通过优化以后,我们已经可以应对各种各样的筛选需求。

可能有经验的编程人员会发现上述的代码有一些小瑕疵,就是Filter接口的filter方法并不通用,为啥呢?filter方法中我们传入的对象写死为Student类型,这个实在不好,所以,我们使用泛型将其改进一下,完整代码如下:

public interface Filter<T> {
    /**
     * 过滤方法,由使用者去实现
     * @param 
     * @return
     */
    boolean filter(T t);
}

public class Test {
    private static List<Student> students = new ArrayList<>();

    /**
     * 初始化学生列表
     */
    static {
        students.add(new Student("张三",20,85));
        students.add(new Student("李四",14,95));
        students.add(new Student("王五",22,100));
        students.add(new Student("李华",17,77));
    }

    /**
     *
     * @param objStudent 需要查找的学生列表
     * @param filter 过滤规则
     * @return
     */
    public static List<Student> filterStudent(List<Student> objStudent, Filter<Student> filter){
        List<Student> result = new ArrayList<>();
        for (Student student : objStudent) {
            if(filter.filter(student)){
                result.add(student);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        //找出年龄大于15岁的学生
        Filter<Student> filterAge = (student) -> student.getAge() > 15;
        List<Student> resultAge = filterStudent(Test.students,filterAge);
        System.out.println(resultAge);

        //找出分数大于80分的学生
        Filter<Student> filterScore = (student) -> student.getScore() > 80;
        List<Student> resultScore = filterStudent(Test.students,filterScore);
        System.out.println(resultScore);
    }

}

这种设计方法有着广泛的运用,大家在编程中适当的多多运用这种方法,来提升代码的复用性。

三、lambda表达式结构

lambda表达式可分为三部分:lambda参数列表、lambda操作符、lambda体。() -> {}
根据参数列表不同,方法体的行数不同,lambda可以有不同的变种。
语法一:无参数、无返回值

public interface NoParamNoReturn {
    void print();
}
public static void main(String[] args) {
		//如果lambda体中只有一行代码,{}可以省略,
		//NoParamNoReturn no = ()-> {System.out.println("Hello Lambda");};
        NoParamNoReturn no = ()-> System.out.println("Hello Lambda");
        no.print();
    }

语法格式二:有一个参数,无返回值

public interface ILambda {
    void print(Student student);
}

public static void main(String[] args) {
		//如果只有一个参数()可以省略不写,(x)-> System.out.println(x.getName());
        ILambda no = x -> System.out.println(x.getName());
        no.print(new Student("张三",20,20));
    }

语法格式三:有一个参数,多条语句,有返回值.

public interface ILambda {
    boolean print(Student student);
}
public static void main(String[] args) {
        ILambda no = x -> {
            System.out.println(x.getName());
            return true;
        };
        no.print(new Student("张三",20,20));
    }

语法格式四:有一个参数,一条语句,有返回值.

public static void main(String[] args) {
		// return 可以省略不写,ILambda no = x -> {return x.getAge()>15;};
        ILambda no = x -> x.getAge() > 15;
        no.print(new Student("张三",20,20));
    }

这些变种无需强行记忆,多用,多看,自然能够孰能生巧。初学者只需按照标准语法写即可。

四、函数式接口

在上文中,我们提到了函数式接口,也使用到了函数式接口,我们回顾一下函数式接口的定义:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。

在Java中为我们提供了四大函数式接口:

1、消费型函数接口

Consumer<T>
	void accept(T t); 

该接口接受一个T类型的参数,无返回值。
需求:统计学生C语言成绩和Java语言成绩总分。

@Data
@Data
public class Student1 {
    private String name;
    private Integer age;
    private Integer javaScore;
    private Integer cScore;
    private Integer totalSocre;
}
public static void main(String[] args) {
        Consumer<Student1> consumer = student -> student.setTotalSocre(student.getJavaScore()+student.getCScore());
        Student1 student = new Student1();
        student.setJavaScore(80);
        student.setCScore(90);
        consumer.accept(student);
        System.out.println(student);
    }

2、供给型接口

Supplier<T>
	T get();

该接口无参数,返回一个T类型的对象
需求:产生随机数

public static void main(String[] args) {
        Supplier<Integer> sup = () -> (int)(Math.random() *100);
        System.out.println(sup.get());
    }

3、函数型接口

Function<T, R>
	R apply(T t);

接受一个T类型的参数,返回一个R类型。
需求,获取该学生的总分数

public static void main(String[] args) {
        Function<Student1,Integer> function = student -> student.getCScore() + student.getJavaScore();
        Student1 student = new Student1();
        student.setJavaScore(80);
        student.setCScore(90);
        Integer totalScore = function.apply(student);
        System.out.println(totalScore);
    }

4、断言型接口

Predicate<T> 
	boolean test(T t);

给定一个T,返回一个Boolean
需求:找出年龄大于15岁的学生。
其实这个需求,我们已经实现了,只是将Filter接口换为Predicate接口即可。

public class Test {
    private static List<Student> students = new ArrayList<>();

    /**
     * 初始化学生列表
     */
    static {
        students.add(new Student("张三",20,85));
        students.add(new Student("李四",14,95));
        students.add(new Student("王五",22,100));
        students.add(new Student("李华",17,77));
    }

    /**
     *
     * @param objStudent 需要查找的学生列表
     * @param predicate 过滤规则
     * @return
     */
    public static List<Student> filterStudent(List<Student> objStudent, Predicate<Student> predicate){
        List<Student> result = new ArrayList<>();
        for (Student student : objStudent) {
            if(predicate.test(student)){
                result.add(student);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        Predicate<Student> predicate = x -> x.getAge() > 15;
        List<Student> students = filterStudent(Test.students, predicate);
        System.out.println(students);
    }

}

好了,本文到此处就结束了,相信大家对lambda表达式已经有了很清楚的认识,感谢阅读。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值