【一起学习Java8】函数式编程—Lambda表达式

土味情话:“你在干嘛”“我也想你”

前言

Lambda是一个匿名函数,即没有函数名的函数。 Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)

是不是一脸懵逼,语言上的解释是苍白无力的,并且每个人对于lambda都有自己的理解,所以只有通过实践就会明白到底是怎么一回事

来吧,展示~

前戏

先来看看如果不使用Lambda,匿名函数怎么写

  • 比较器对集合排序—Java8之前写法

    • 准备数据
    class Employee {
        private String name;
        private Integer age;
    
        public Employee(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        // 需重写toString()  	
    }
    
    List<Employee> list = Arrays.asList(
                  new Employee( "张三", 18),
                  new Employee( "李四", 28),
                  new Employee( "王五", 8),
                  new Employee( "赵六", 38),
                  new Employee( "田七", 58)
    );
    
    • 示例代码
    @Test
    public void test() {
      	// 定义比较器,通过Employee对象的age字段进行升序排列
        Comparator<Employee> comparator = new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                // 如果返回值 <1 则升序,  >1 则降序, =0则认为相等
                return o1.age - o2.age;
            }
        };
        // 对list集合数据进行排序       [1]
        list.sort(comparator);
        
        // 打印排序之后的list           [2]
        for (Employee item : list) {
            System.out.println(item);
        }
    }
    

    以上的代码则为Java8之前的示例代码,一共需要10行代码来进行排序

    其中[1][2] 标识的地方均是可以使用Lambda来优化

  • 比较器对集合排序—Lambda

    • 示例代码
    @Test
    public void test1() {
        // 根据age的大小进行升序排列
        list.sort((o1, o2) -> {
            return o1.age - o2.age;
        });
        // 打印输出
        list.forEach(item -> System.out.println(item));
    }
    

    Lambda表达式,一共就只需要4行代码,简洁不简洁,优雅不优雅(还可以更简洁,更优雅)

    • 具体含义

    (o1, o2) -> {return o1.age - o2.age} 这个就是Lambda表达式,相当于上面的Comparator接口中的compare方法。

    • 第一种方法使用匿名内部类,并声明重写的方法,然后处理逻辑
    • Lambda使用箭头函数,直接重写逻辑,不需要声明类名、方法名称、参数类型。JVM会自动进行推断重写的方法名、该参数的类型。

    因为方法名和类型都是自动推断的,所以在定义箭头函数(Lambda)体的时候,定义的变量只能使用该类型所具有的方法,并且定义的变量的类型也要和指定的类型相同

    • 类型推断说明

      • 示例代码中List<Employee>中保存的是Employee对象,所以在(o1, o2) -> o1.age - o2.age中,o1o2的类型为Employee,所以在函数体中调用方法时,只能调用Employee具有的方法,并且返回的类型也必须是int类型,因为Lambda相当于重写的是Comparator的compare方法,该方法的返回值为int,相当于隐式声明类型。

      • 那可以随便返回和传递参数类型吗?

        自然是不可以的,因为在Listsort方法中,第二个参数必须是Comparator接口的实现类,所以参数的类型和返回值类型必须和Comaparator中的compare方法保持一致

        // @FunctionalInterface 标示为函数式接口
        @FunctionalInterface
        public interface Comparator<T> {
            // 定义了方法的返回值,参数类型
        		int compare(T o1, T o2);
        }
        

        所以,并不是没有类型,而是通过约定来将类型的声明省略,但是在运行的时候会自动推断类型,保证程序的正确性。

        @FunctionalInterface 标示为函数式接口,函数式接口指定该接口只能定义一个抽象方法。所以在Lambda中不生命方法名称时,不用担心到底调用的是哪个方法,因为只有一个抽象方法。

        后面的文章会再详细说明函数式接口

  • 创建线程—Java8之前

    @Test
    public void test() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("创建线程");
            }
        }, "Thread-1");
    
        thread.start();
    }
    

    创建Thread对象,并重写里面的Runnablerun方法,调用调用start方法开启线程

  • 创建线程—Lambda

    @Test
    public void test() {
        // 创建Runnable接口的实现类,使用箭头函数表示
        // 可以理解为多态的形式,接口 = 实现类
        Runnable runnable = () -> System.out.println("创建线程");
        // 传递接口实现类对象
        Thread thread = new Thread(runnable);
        thread.run();
    }
    

    接口 = 实现类这种方式就是多态的应用呀,Runnable就是一个函数式接口,所以该接口只有一个public abstract void run();抽象方法,这里的Lambda表达式定义的就是run方法,并且run方法没有参数,也没有返回值

正戏

通过前戏,基本上已经进入状态了,下面可以进入正戏了~~

  • 自定义接口使用Lambda

    interface Multi {
      String twoArg(String head, Double d);
    }
    interface FileOperator {
      void createFile(String path);
    }
    
    @Test
    public void test() {
        // 定义Multi接口,并通过Lambda实现twoArg方法,如果只有一行代码,可以不用定义{} 和 声明 return,默认 return 返回值
        Multi multi = (h, d) -> h + d;
        // 调用接口的方法
        String str = multi.twoArg("Pi!", 3.1415926);
        System.out.println(str);
    }
    
    @Test
    public void test() {
        // 定义lambda表达式,变量p的类型为String
        FileOperator op = (p) -> {
          File file = new File(p);
          System.out.println(file.getName());
        };
        op.createFile("/tmp/file.txt");
    }
    

    这样就可以自定义接口,并通过Lambda来动态实现接口方法。

    不过一般来讲需要使用Lambda来动态实现的接口,在接口上面加上@FunctionalInterface,这个注解用来表示该接口是一个函数式接口。

小总结

  • 匿名内部类的方式,在Java8中可以直接使用Lambda实现,更为简洁直观优雅
  • 自定义Lambda处理逻辑可以用来做函数式接口的实现,实现特定的功能,并且根据不同的实现,自定义不同的Lambda的处理逻辑(多态有点像)
  • 注意Lambda表达式中虽然没有显式指定参数和返回值等类型,不过必须遵守具体接口的参数列表类型和返回值类型

彩蛋

上面说到排序的代码还可以更简洁~

@Test
public void test1() {
    // 根据age的大小进行升序排列
    list.sort((o1, o2) -> o1.age - o2.age);
    // 打印输出
    list.forEach(System.out::println);
}

2行代码,是不是更优雅。

第一行:在方法体中只有一行代码的情况下,可以省略{},并且可以return也可以省略,默认会将计算的结果返回

第二行:forEach中可以使用Lambda的箭头函数,也可以使用方法引用(和Lambda基本一致,在特定的情况下可以替换Lambda)

指尖上的代码

微信公众号「指尖上的代码」,欢迎关注~

你的点赞和关注是写文章最大的动力~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值