深入了解lambda表达式、函数式接口和方法引用

1. lambda表达式

Lambda表达式:特殊的匿名内部类,语法更简洁。
Lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递

基本语法:

<函数式接口><变量名>=(参数1,参数2...) ->{
	// 方法体
}

注意:

函数式接口:接口中只有一个抽象方法

(参数1,参数2):抽象方法的参数

->:分隔符

{}:表示抽象方法的实现

首先我们先从三个例子来大致了解一下什么是lombda表达式

1.1 普通lambda表达式

**例子:**用线程打印一句话,以下用普通的两种方法完成

public class Test01 {

    public static void main(String[] args) {
        
        // 该构造方法需要传递一个线程任务对象。Runnable类型
        MyThread task01 = new MyThread();
        Thread thread01 = new Thread(task01);
        thread01.start();
        
        // 匿名内部类
        Runnable task02 = new Runnable() {
            @Override
            public void run() {
                System.out.println("这是匿名内部类方式的任务对象");
            }
        };
        Thread thread02 = new Thread(task02);
        thread02.start();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println("自定义任务接口类");
    }
}

运行结果:

在这里插入图片描述

分析代码:

  1. Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核32。
  2. 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类。
  3. 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类。
  4. 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错。
  5. 而实际上,似乎只有方法体才是关键所在。

这时可以使用Lambda表达式:

		// 使用lambda表达式
        Runnable task03 = () -> {
            System.out.println("这是使用Lambda表达式完成的");
        };
        Thread thread03 = new Thread(task03);
        thread03.start();

运行结果:

在这里插入图片描述

是不是比之前的两种方法简介很多

注意:

使用lambda表达式的前提:必须是函数式接口

1.2 无参无返回值的lambda表达式

举例:

自定义一个接口,包含一个方法,实现该接口并重写其中的方法,打印一句话

首先我们先自定义一个接口:

	// 自定义一个接口
    interface Swim{
        public void swimming();
    }

写一个调用接口中方法的方法:

	// 调用接口中的方法的方法
    private static void fun(Swim s){
        s.swimming();
    }

匿名内部类方式:

public static void main(String[] args) {
        // 匿名内部类
        Swim swim = new Swim() {
            @Override
            public void swimming() {
                System.out.println("这是使用匿名内部类的方式");
            }
        };
        
        fun(swim);
    }

使用lambda表达式:

	// lambda表达式
        Swim swim02 = () -> {
            System.out.println("使用lambda表达式的方式1");
        };
        fun(swim02);

是不是看起来简短了一些,我们还可以再次缩减代码

接下来我们利用lamebda表达式允许把函数作为一个方法的参数(函数作为方法参数传递)的特性再次缩减代码

		// lambda表达式,再次缩减
        fun(() -> {System.out.println("使用lambda表达式的方式2");});
        // 由于{}内只有一条语句,{}可以省略
        fun(() -> System.out.println("使用lambda表达式的方式3"));

运行结果:

在这里插入图片描述

我们发现,使用lambda表达式直接把原来的有段代码缩减成了一句代码,使用起来更加方便

1.3 有参有返回值的lambda表达式

举例:

创建一个对象类,添加几条数据,对类的进行排序

传统的方法:

public class Test03 {
    public static void main(String[] args) {
        // 创建人类集合
        List<Person> personList = new ArrayList<>();
        // 向集合中添加内容
        personList.add(new Person("萧炎",18));
        personList.add(new Person("美杜莎",20));
        personList.add(new Person("古熏儿",16));
        
        // 根据年龄进行排序
        // 传统做法:使用Comparator排序规则接口
        Comparator<Person> comparator = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                // 按照返回的数的正负进行换位,正的在前
                return o2.getAge() - o1.getAge();
            }
        };
        Collections.sort(personList,comparator);
        // 遍历打印
        for (Person person : personList) {
            System.out.println(person);
        }
    }
}

// 创建一个人类对象,使用lombok的注解自动生成get、set和toString方法
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
    private String name;
    private Integer age;
}

使用lambda表达式:

只需要把上面的传统做法后的内容替换为下面的代码即可

		// 使用lambda表达式
        Comparator<Person> comparator1 = (o1,o2) -> o2.getAge() - o1.getAge();
		Collections.sort(personList,comparator1);
		// 遍历打印
        personList.forEach(item -> System.out.println(item));

运行结果:

在这里插入图片描述

通过以上三个例子,是不是对lambda表达式有一定的了解了,我们可以总结一下它的特点

1.4 lombda表达式的特点

Lambda引入了新的操作符:->(箭头操作符),->将表达式分成两部分

左侧:(参数1,参数2…)表示参数列表

右侧:{}内部是方法体

注意事项:

  1. 形参列表的数据类型会自动推断。
  2. 如果形参列表为空,只需保留()。
  3. 如果形参只有1个,()可以省略,只需要参数的名称即可。
  4. 如果执行语句只有一句,且无返回值,{}可以省略,若有返回值,则若想省去,则必须同时省略return,且执行语句也保证只有一句。
  5. Lambda不会生成一个单独的内部类文件。

2. 函数式接口

如果一个接口只有一个抽象方法,则该接口称之为函数式接口。

函数式接口可以使用Lambda表达式,Lambda表达式会被匹配到这个抽象方法上。

@Functionallnterface 注解检测接口是否符合函数式接口。

内置函数接口的由来:

比如以下求数组和的代码:

public class Test04 {

    public static void main(String[] args) {
        Operator o = arr -> {
            int sum = 0;
            for (int i : arr) {
                sum += i;
            }
            System.out.println("数组的和为:"+sum);
        };
        fun(o);
    }
    
    public static void fun(Operator operator){
        int[] arr = {1,2,3,4,5};
        operator.getSum(arr);
    }
}

@FunctionalInterface
interface Operator {
    // 求数组的和
    void getSum(int[] arr);
}

分析:

​ 我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽 象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,IDK提供了大量常用的函数式接口。

3. 常见的函数式接口

常见的函数式接口有四种,分别是Consumer接口、Supplier接口、Function接口、Predicate接口。这四个接口都在java.util.function包下

函数式接口参数类型返回类型说明
Consumer
消费型接口
Tvoidvoid accept(T); 对类型为T的对象应用操作
Supplier
供给行接口
TT get(); 返回类型为T的对象
Function<T,R>
函数型接口
TRR apply(T t); 对类型为T的对象应用操作,并返回类型为R类型的对象。
Predicate
断言型接口
Tbooleanboolean test(T t); 确定类型为T的对象是否满足条件,并返回boolean类型。

3.1 Consumer接口

​ Consumer接口是Java中的一个函数式接口,用于表示接受单个输入参数并且不返回任何结果的操作。它定义了一个名为accept的抽象方法,该方法接受一个泛型类型的参数,通常用于执行一些操作或者消费给定的输入。Consumer接口通常与Lambda表达式结合使用,可以在不创建额外命名方法的情况下,直接传递操作或行为。

public class Test05 {
    public static void main(String[] args) {
        Consumer<Double> consumer = t -> System.out.println("吃饭消费了:"+t+"元");
        fun(consumer,88);
    }
    
    // 调用某个方法时,该方法需要的参数为接口类型,这时就应该能想到使用lambda
    public static void fun(Consumer<Double> consumer,double money){
        consumer.accept(money);
    }
}

运行结果:

在这里插入图片描述

3.2 Supplier接口

​ Supplier接口是Java中的一个函数式接口,用于表示一个供给型的操作,它不接受任何参数,但返回一个泛型类型的结果。在函数式编程中,Supplier接口通常用于延迟计算或者提供数据的场景,例如当需要动态获取数据、生成随机数或者创建对象时非常有用。

public class Test06 {

    public static void main(String[] args) {
        // 生成一个[0,10)的一个随机数
        fun(() -> new Random().nextInt(10));
    }
    
    public static void fun(Supplier<Integer> supplier){
        System.out.println("内容为"+supplier.get());
    }
}

运行结果:

在这里插入图片描述

3.3 Function接口

​ Function接口是Java中的一个函数式接口,用于表示一个接受一个参数并产生结果的函数。它定义了一个名为apply的抽象方法,该方法接受一个泛型类型的参数,并返回另一个泛型类型的结果。Function接口在函数式编程中非常有用,特别是在需要对输入进行转换、映射或处理的场景中经常被使用。

public class Test07 {
    public static void main(String[] args) {
        // 将字符串转换成大写
        fun(t->t.toUpperCase(),"hello world");
    }
    
    public static void fun(Function<String,String> function,String result){
        System.out.println("结果为:"+function.apply(result));
    }
}

运行结果:

在这里插入图片描述

3.4 Predicate接口

​ Predicate接口是Java中的一个函数式接口,用于表示一个断言(即判断条件),它接受一个参数并返回一个boolean值。Predicate接口通常用于过滤、筛选或者判断输入数据是否满足特定的条件。

public class Test08 {
    public static void main(String[] args) {
        // 判断名称的长度是否大于3
        fun(n->n.length()>3?true:false,"萧炎");
    }
    
    public static void fun(Predicate<String> predicate,String name){
        System.out.println("该名称的长度是否大于3:"+predicate.test(name));
    }
}

运行结果:

在这里插入图片描述

4. 方法的引用

4.1 lombda表达式的冗余

我们来看下面这段代码:

public class Test09 {
    public static void main(String[] args) {
        Consumer<Integer[]> c = arr ->{
            int sum = 0;
            for (Integer b : arr) {
                sum += b;
            }
            System.out.println("数组的和为:"+sum);
        };
        fun(c);
    }
    
    public static void fun(Consumer<Integer[]> consumer){
        Integer[] arr = {1,2,3,4,5};
        consumer.accept(arr);
    }
    
    public static void sum(Integer[] arr){
        int sum = 0;
        for (Integer a : arr) {
            sum += a;
        }
        System.out.println("数组的和为:"+sum);
    }
}

分析:

我们用lombda表达式的求和的方法,已经在本类或其它类中被写过,如其中的sum方法,我们再次书写就造成了代码的冗余

如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接”引用”过去就好了

4.2 什么是方法引用

方法引用是Lambda表达式的一种简写形式。 如果Lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。

我们可以对上面的代码进行修改,修改如下:

public class Test09 {
    public static void main(String[] args) {
        // 方法的引用
        Consumer<Integer[]> c = Test09::sum;
        fun(c);
    }
    
    public static void fun(Consumer<Integer[]> consumer){
        Integer[] arr = {1,2,3,4,5};
        consumer.accept(arr);
    }
    
    public static void sum(Integer[] arr){
        int sum = 0;
        for (Integer a : arr) {
            sum += a;
        }
        System.out.println("数组的和为:"+sum);
    }
}

运行结果:

在这里插入图片描述

我们发现用这次我们并没有重复写求和的代码,而是直接引用了sum求和的代码。

其中的::写法就算方法引用。

4.3 方法引用的分类

方法引用分为四种类型,分别是:静态方法引用、实例方法引用、对象方法引用、构建方法引用。

类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args)->类名.staticMethod(args)
实例方法引用inst:instMethod(args)->inst:instMethod(args)
对象方法引用类名::instMethod(inst,args)->inst.instMethod(args)
构建方法引用类名::new(args)->new 类名(args)
4.3.1 静态方法引用

在1.3有参有返回值的lambda表达式的例子中,我们通过年龄进行了排序

public class Test03 {
    public static void main(String[] args) {
        // 创建人类集合
        List<Person> personList = new ArrayList<>();
        // 向集合中添加内容
        personList.add(new Person("萧炎",18));
        personList.add(new Person("美杜莎",20));
        personList.add(new Person("古熏儿",16));
        
        // 根据年龄进行排序
        // 使用lambda表达式
        Comparator<Person> comparator1 = (o1,o2) -> o2.getAge() - o1.getAge();
		Collections.sort(personList,comparator1);
		// 遍历打印
        personList.forEach(item -> System.out.println(item));
    }
}

// 创建一个人类对象,使用lombok的注解自动生成get、set和toString方法
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
    private String name;
    private Integer age;
}

假如其中的Person类中有一个排序的方法,我们就可以直接引用这个方法,如下

public class Test03 {
    public static void main(String[] args) {
        // 创建人类集合
        Person[] personList = {new Person("萧炎",18),new Person("美杜莎",20),new Person("古熏儿",16)};
        
        // 方法引用
        // 我们之前是这样进行排序的
        // Comparator<Person> comparator1 = (o1,o2) -> o2.getAge() - o1.getAge();
        
        // 使用方法引用可以修改为
        // Comparator<Person> c = (o1,o2) -> Person.compareTo(o1,o2);
        
        // 我们可以再次简写为
        Comparator<Person> c = Person::compareTo;
        Arrays.sort(personList,c);
        System.out.println(Arrays.toString(personList));
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
    private String name;
    private Integer age;
    
    public static int compareTo(Person p1,Person p2){
        return p1.getAge()-p2.getAge();
    }
}

运行结果:

在这里插入图片描述

这样一来,我们的代码又缩减了许多。

4.3.2 实例方法引用

​ 实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用。

举例:

还在上面的内容上举例,写一个打印名称的方法,用引用调用它

public class Test03 {
    public static void main(String[] args) {
        Person p = new Person("韩立",25);
        // 打印名称
        // Supplier<String> supplier = () -> p.getName();
        // 可以修改为
        Supplier<String> supplier = p::getName;
        fun(supplier);
    }
    
    public static void fun(Supplier<String> supplier){
        System.out.println(supplier.get());
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
    private String name;
    private Integer age;
    
    public static int compareTo(Person p1,Person p2){
        return p2.getAge()-p1.getAge();
    }
}

运行结果:

在这里插入图片描述

4.3.3 对象方法引用
public class Test10 {
    public static void main(String[] args) {
        // 计算字符串的长度
        // Function<String,Integer> function = (str) ->str.length();
        // 可以改写为
        Function<String,Integer> function = String::length;
        System.out.println(function.apply("hello"));
        
        // 比较两个字符串的内容是否一致,T第一个参数类型,U第二个参数类型,R返回结果类型
        // BiFunction<String,String,Boolean> bi = (t,u) -> t.equals(u);
        // 可以改写为
        BiFunction<String,String,Boolean> bi = String::equals;
        // R apply(T t,U u);
        System.out.println(bi.apply("hello", "world"));
    }
}

运行结果:

在这里插入图片描述

4.3.4 构建方法引用
public class Test11 {
    public static void main(String[] args) {
        // 创建一个对象
        // 无参构造
        // Supplier<Man> supplier = ()->new Man();
        // 可以改写为
        Supplier<Man> supplier = Man::new;
        System.out.println(supplier.get());
        
        // 有参构造
        // Function<String,Man> function = (n) -> new Man(n);
        // 该可以改写为
        Function<String,Man> function = Man::new;
        System.out.println(function.apply("萧炎"));
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Man{
    private String name;
}

运行结果:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值