Java8 新特性 Lambda表达式的基本用法

一、Lambda表达式

Lambda表达式是一个匿名函数 ,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。有了Lambda表达式使得Java的函数式编程更加方便,代码更加简洁。

二、Lambda表达式的例子

一个简单的例子

分别写两个测试用例实现相同的功能,一个使用匿名内部类,一个使用Lambda表达式,体会二者的差异。
需求:新建一个list数组,放入一些数,对数组进行排序并打印输出。

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import java.util.function.Consumer;

public class TestLambda {
    
    List<Integer> list;
    
    @Before
    public void setup(){
        list = new ArrayList<>();
        list.add(3);
        list.add(5);
        list.add(99);
        list.add(1);
    }
    
    @After
    public void tearDown(){
        list = null;
    }

    //匿名内部类
    @Test
    public void test1(){

        list.sort( new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

    //Lambda表达式
    @Test
    public void test2(){
        list.sort((o1, o2) -> Integer.compare(o1, o2));
        list.forEach(integer -> System.out.println(integer));
    }
}

可以看到在使用Lambda表达式能够明显减少代码量,并且具有较好的可读性。

一个更加复杂的例子

假设我们有一个Employee类如下:

class Employee{
    private String name;
    private int age;
    private int salary;

    public Employee() {
    }

    public Employee(String name, int age, int salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

现在需要对这个类进行一些统计操作:
1、直接声明函数实现统计功能。(无优化)
需要对所有员工中年龄大于40的筛选出来,我们可能会写一个筛选函数。

    //一个更加复杂的例子
    @Test
    public void test3() {
        List<Employee> employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );
        //需求:获取年龄大于40的员工;
        List<Employee> result1 = filterEmployees(employees);
        for (Employee e : result1) {
            System.out.println(e);
        }
    }

    //获取年龄大于40的员工;
    public List<Employee> filterEmployees(List<Employee> list) {
        List<Employee> employees = new ArrayList<>();
        for (Employee employee : list) {
            if (employee.getAge() >= 40) {
                employees.add(employee);
            }
        }
        return employees;
    }

如果这样的需求很多,例如要求筛选员工工资大于10000的,我们又需要添加一个筛选函数。

    //需求:获取当前公司中员工工资大于10000的员工信息
    public List<Employee> filterEmployees2(List<Employee> list) {
        List<Employee> employees = new ArrayList<>();
        for (Employee employee : list) {
            if (employee.getSalary() >= 10000) {
                employees.add(employee);
            }
        }
        return employees;
    }

可见,每增加一个需求,就会新增一个筛选函数。仔细观察会发现,上面两个筛选函数中只有if判断语句中不一样,因此可以将筛选函数提取到接口中,使代码更加清晰。

2、提取函数公共部分,即使用策略设计模式(针对接口编程而不是实现编程)。
因为筛选函数中存在大量冗余代码,将这些冗余代码提出,并用接口方式实现。
定义泛型接口如下,当中只有test一个泛型方法:

interface MyPredicate<T>{
    boolean test(T t);
}

当我们需要增加一个筛选功能时,只需要新建一个类实现该接口,即实现test函数来添加筛选条件,例如筛选40岁以上员工的代码如下:

class FilterEmployeeByAge implements MyPredicate<Employee> {
    @Override
    public boolean test(Employee employee) {
        return employee.getAge()>=40;
    }
}

于是,我们可以针对所有的筛选定义一个统一的函数,任何的筛选条件都通过调用该方法进行筛选。

    public List<Employee> fileterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate) {
        List<Employee> employees = new ArrayList<>();
        for (Employee employee : list) {
            if (myPredicate.test(employee)) {
                employees.add(employee);
            }
        }
        return employees;
    }

具体使用方法如下:

    @Test
    public void test4(){
        List<Employee> employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

        List<Employee> employeeList = fileterEmployee(employees, new FilterEmployeeByAge());
        for (Employee employee : employeeList) {
            System.out.println(employee);
        }
    }

这样一来,所有的筛选都只需要调用filterEmployee这一个方法,只需要传入过滤接口的不同实现类即可。

3、匿名内部类。可以看到,使用策略设计模式,虽然使得函数的公共部分被提取出来,但针对每一种不同的筛选条件,仍然需要新建一个实现了特定接口的类。如果这个类仅使用一次,没有被复用的机会,显然类的定义就显得多余。于是,可以使用匿名内部类进行替换,我们不需要定义类来实现接口而直接使用new关键字新建一个满足该接口(实现了该接口的方法)的对象。
例如使用匿名内部类的方法筛选工资小于10000的员工。

    @Test
    public void test5(){
        List<Employee> employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

        List<Employee> employeeList = fileterEmployee(employees, new MyPredicate<Employee>() {
            @Override
            public boolean test(Employee employee) {
                return employee.getSalary()<10000;
            }
        });
        for (Employee employee : employeeList) {
            System.out.println(employee);
        }
    }

4、Lambda表达式。如果需要实现的接口仅有一个抽象方法,那么我们实现该接口时目的是明确的,就是为了实现该接口中定义的仅有的一个抽象方法的功能。所以匿名内部类也可以省去,直接使用Lambda表达式传递“代码”,告诉这个仅有的抽象方法应该如何实现。上方的匿名内部类也可以替换为如下Lambda表达式。函数式接口的声明可以在接口上方加上@FunctionalInterface注解

    @Test
    public void test6(){
        List<Employee> employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

        List<Employee> employeeList = fileterEmployee(employees, employee -> employee.getSalary()<10000);
        for (Employee employee : employeeList) {
            System.out.println(employee);
        }
    }

5、Stream API。在Lambda表达式进行几乎极简优化的情况下,Java8中还有一项新特性能够更好的对集合进行处理,即Stream API。通过调用Stream API提供的filter方法,甚至不需要定义上述的过滤方法和接口,直接结合Lambda表达式完成数据的筛选操作。

    @Test
    public void test7() {
        List<Employee> employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

//        List<Employee> employeeList = employees.stream().filter((e)->e.getSalary()<10000).collect(Collectors.toList());
//        for (Employee employee : employeeList) {
//            System.out.println(employee);
//        }
        employees.stream().filter((e)->e.getSalary()<10000).forEach(employee -> System.out.println(employee));
    }

上方注释可以进行筛选并存储到集合,或者直接使用forEach结合lambda表达式直接完成输出,用起来更加方便。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359