Lambda表达式和函数式编程

2021/11/7 14:10:40

本文主要是介绍Lambda表达式和函数式编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Lambda 简介

​ Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。λ希腊字母表中排序第十一位的字母,英语名称为Lambda。

Lambda 表达式的优点

  • Lambda是java8引入的新特性
  • 使用它设计的代码会更加简洁
  • 去掉了没有意义的代码,只留下了核心的逻辑,可读性更好
  • 其实质属于函数式编程的概念
  • 避免匿名内部类定义过多
  • 理解 Functional Interface(函数式接口)是学习lambda表达式的基础

函数式接口

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

  • @FunctionalInterface 修饰函数式接口的,要求接口中的抽象方法只有一个。显示标注了此接口是一个函数式接口

  • jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用

代码示例

    @FunctionalInterface
    public interface Fun{
        void run();
    }

    @Test
    public void test(){
        Fun fun =  ()->System.out.println("Running from Lambda");
        fun.run();;
    }

内置四种核心函数式接口

JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。

Java8 java.util.functional 包中四种核心函数接口

  • Consumer(消费型接口)接收一个参数,无返回值

  • Supplier(提供型接口) 无参,有返回值

  • Function(函数接口)接收一个参数,有返回值

  • Predicat(断言型接口)接收一个参数,返回boolean

下面是内置接口简单实现,并不是全部的内容,仅作参考

  1. Consumer(消费型接口)接收一个参数,无返回值
/**
 * Represents an operation that accepts a single input argument and returns no
 * result. Unlike most other functional interfaces, {@code Consumer} is expected
 * to operate via side-effects.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #accept(Object)}.
 *
 * @param <T> the type of the input to the operation
 *
 * @since 1.8
 */
@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); };
    }
}
  1. Supplier(提供型接口) 无参,有返回值
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
  1. Function(函数接口)接收一个参数,有返回值
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}
  1. Predicat(断言型接口)接收一个参数,返回boolean
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

Lambda 基础语法

语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。

下面是针对内置的四种和两种自定义接口进行实例演示

  1. Consumer 接口调用示例
    /**
     * 1. Consumer(消费型接口)接收一个参数,无返回值
     */
    @Test
    public void consumerTest(){
        Consumer<Integer> consumer = (n) -> {
            System.out.println("NoReturnMultiParam param:" + n );
        };
        consumer.accept(6);
    }


  1. Supplier 接口调用示例
    /**
     * 2. Supplier(提供型接口) 无参,有返回值
     */
    @Test
    public void supplierTest(){
        Supplier<String> supplier = ()->{
            return "hello world!";
        };
        System.out.println(supplier.get());
    }

    
  1. Function 接口调用示例及引用方式使用
		/**
     * 3. Function(函数接口)接收一个参数,有返回值
     */
    @Test
    public void functionTest(){
        Function<String, Integer> function = (s) ->{
            return s.length();
        };
        System.out.println(function.apply("hello world!"));
    }

    /**
     * 使用类型引用的方式简单方法的调用
     */
    @Test
    public void functionTest2(){
        Function<String, Integer> function = String::length;
        System.out.println(function.apply("hello world!"));
    }

    

  1. Predicate 接口调用示例
/**
     * 4. Predicate(断言型接口)接收一个参数,返回boolean
     */
    @Test
    public void predicateTest(){
        Predicate<String> predicate = s ->{
            return s.length() > 3;
        };
        predicate.test("hello world!");
    }

   
  1. 自定义接口:多参考有返回值调用示例
 		/**
     * 5. 自定义接口:多个参数有返回值
     */
    @FunctionalInterface
    public interface ReturnMultiParam {
        int method(int a, int b);
    }

    @Test
    public void ReturnMultiParamTest(){
        ReturnMultiParam returnMultiParam = (int a, int b) -> {
            System.out.println("ReturnMultiParam param:" + "{" + a + "," + b +"}");
            return 1;
        };

        int res3 = returnMultiParam.method(6, 8);
        System.out.println("return:" + res3);
    }
    
    
  1. 自定义接口:无参无返回值调用示例
		/**
     * 6. 自定义接口:无参无返回值
     */
    @FunctionalInterface
    public interface NoReturnNoParam {
        void method();
    }

    @Test
    public void NoReturnNoParamTest(){
        NoReturnNoParam noReturnNoParam = () -> {
            System.out.println("NoReturnNoParam");
        };
        noReturnNoParam.method();
    }

lambda 表达式引用方法

有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。

语法 方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象

方法引用的方式:

  1. 对象名::方法名
  2. 类名::静态方法名
  3. 类名::引用实例方法
  4. 类名::构造器
  5. 数组引用
  • 对象名::方法名的示例。并且进行原始lambda 和 使用引用方法调用示例比对。
    /**
     * 针对整数数组求合
     * @param intList 整数数组
     * @return 返回数组之和
     */
    public Integer showSum(List<Integer> intList){
        var ref = new Object() {
            int sum = 0;
        };
        intList.forEach(i -> {
            ref.sum += ref.sum +i;
        });
        return ref.sum;
    }

    @Test
    public void showSumTest(){
        List<Integer> list = new ArrayList<>(Arrays.asList(10, 20, 30));
        // 1. 使用 lambda 原始方式调用方法
        Function<List<Integer>, Integer> function1 = l -> {
                int sum = 0;
            for (int i:l) {
                sum += sum + i;
            };
            return sum;
        };
        int result = function1.apply(list);
        System.out.println(result);

        // 2. 使用 lambda 引用方式调用方法
        Function<List<Integer>, Integer> function2 = this::showSum;
        result = function2.apply(list);
        System.out.println(result);
    }
  • 类名::静态方法名示例
    @Test
    public void test1(){
        // 1. 使用 lambda 原始方式调用方法
        Comparator<Integer> comparator = (x, y) -> Integer.compare(x,y);
        System.out.println(comparator.compare(1, 2));
      
        // 2. 使用 lambda 引用方式调用方法
        comparator = Integer::compare;
        System.out.println(comparator.compare(1, 2));
    }
  • 类名::引用实例方法示例
    @Test
    public void test2(){
        // 1. 使用 lambda 原始方式调用方法
        BiPredicate<String, String> biPredicate = (x, y) -> x.equals(y);
        System.out.println(biPredicate.test("test", "hello"));

        // 2. 使用引用方式调用方法
        biPredicate = String::equals;
        System.out.println(biPredicate.test("test", "hello"));
    }
  • 类名::构造器示例

**需要注意,**调用的构造器参数列表要和函数式接口中抽象方法的参数列表保持一致。

    public class Student {
        int id;
        String name;
        int age;
        public Student(int id, String name, int age){
            this.id = id;
            this.name = name;
            this.age = age;
        };

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

    @FunctionalInterface
    interface IStudent<T1, T2, R>{
        R test(T1 a1, T2 s, T1 a2);
    }

    @Test
    public void test3(){
        // 1. 使用 lambda 原始方式调用方法
        IStudent<Integer, String, Student> iStudent = (x,y,z) ->new Student(x,y,z);
        System.out.println(iStudent.test(1, "zhang3", 18));

        // 2. 使用构造引用方式调用方法
        iStudent = Student::new;
        System.out.println(iStudent.test(2, "li4", 18));
    }
  • 数组引用示例
    @Test
    public void test4(){
        // 1. 使用 lambda 原始方式调用方法
        Function<Integer, String[]> function = s->new String[10];
      
        // 2. 使用数组引用方式调用
        function = String[]::new;
        System.out.println(Arrays.stream(function.apply(20)).count());
    }

lambda 表达式创建线程

我们以往都是通过创建 Thread 对象,然后通过匿名内部类重写 run() 方法,一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。

Thread t = new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        System.out.println(2 + ":" + i);
      }
    });
  	t.start();

Lambda 集合的操作

  • 遍历集合

我们可以调用集合的 public void forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。以下是 Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。

		@Test
    public void test5(){
        ArrayList<Integer> list = new ArrayList<>();

        Collections.addAll(list, 1,2,3,4,5);

        //lambda表达式 方法引用
        list.forEach(System.out::println);

        list.forEach(element -> {
            if (element % 2 == 0) {
                System.out.println(element);
            }
        });
    }
  • 删除集合中的某个元素

我们通过public boolean removeIf(Predicate<? super E> filter)方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。

    public class Item{
        int id;
        String name;
        double price;

        public Item(int id, String name, double price) {
            this.id = id;
            this.name = name;
            this.price = price;
        }

        public int getId() {
            return id;
        }

        @Override
        public String toString() {
            return "Item{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    }

    @Test
    public void test6(){
        ArrayList<Item> items = new ArrayList<>();
        items.add(new Item(11, "小牙刷", 12.05 ));
        items.add(new Item(5, "日本马桶盖", 999.05 ));
        items.add(new Item(7, "格力空调", 888.88 ));
        items.add(new Item(17, "肥皂", 2.00 ));
        items.add(new Item(9, "冰箱", 4200.00 ));

        items.removeIf(ele -> ele.getId() == 7);

        //通过 foreach 遍历,查看是否已经删除
        items.forEach(System.out::println);
    }
  • 集合内元素的排序

在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。

    @Test
    public void test7(){
        ArrayList<Item> list = new ArrayList<>();
        list.add(new Item(13, "背心", 7.80));
        list.add(new Item(11, "半袖", 37.80));
        list.add(new Item(14, "风衣", 139.80));
        list.add(new Item(12, "秋裤", 55.33));

        list.sort((o1, o2) -> o1.getId() - o2.getId());

        System.out.println(list);
    }

Lambda 表达式中的闭包问题

这个问题我们在匿名内部类中也会存在,如果我们把注释放开会报错,告诉我 num 值是 final 不能被改变。这里我们虽然没有标识 num 类型为 final,但是在编译期间虚拟机会帮我们加上 final 修饰关键字。
在这里插入图片描述

通过Lambda 函数式编辑完成一个装饰器

    public void show(Consumer<Integer[]> consumer){
        long startTime = System.currentTimeMillis();
        Integer[] array = {1,2,3};
        consumer.accept(array);
        long endTime = System.currentTimeMillis();
        System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
    }

    public void quickSort(Integer[] array) {
        System.out.println("Running...,快速排序最快");
    }

    public void headSort(Integer[] array){
        System.out.println("Running...,空间复杂度最低");
    }

    public void mergeSort(Integer[] array){
        System.out.println("Running...,归并稳定性最好");
    }

    @Test
    public void test9(){
        show(this::quickSort);
        show(this::headSort);
        show(this::mergeSort);
    }


这篇关于Lambda表达式和函数式编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程