重学Java 8新特性 | 第5讲——函数式接口

Posted 李阿昀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学Java 8新特性 | 第5讲——函数式接口相关的知识,希望对你有一定的参考价值。

我们都知道Lambda表达式需要接口的支持,并且接口中的抽象方法还不能多,只能有一个,不然就没法区分它实现的到底是哪个抽象方法了。一般,我们会称该接口为函数式接口,在上一讲中我就已经提到过了,大家还记得吧!这一讲我们就来具体唠唠它。

什么是函数式接口?

Lambda表达式可以很简洁的代替匿名内部类的代码编写,而匿名内部类往往是实现某一接口的一个抽象方法。所以,在使用Lambda表达式时,我们最应该关注的应该是接口的抽象方法,并且这个接口还必须只有一个抽象方法。我们称这种只有一个抽象方法的接口为函数式接口,而Lambda表达式恰好需要一个函数式接口的支持。

知道了函数式接口是什么之后,我们就可以通过Lambda表达式来创建这种接口的对象了,很显然,如果Lambda表达式抛出一个受检异常,那么该异常就需要在目标接口的抽象方法上声明出来。

当然,我们还可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查该接口是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

之前,我们就创建过一个接口,叫MyPredicate,大家还记得吧!它里面就只有一个抽象方法,因此它就是一个函数式接口。既然是一个函数式接口,那么我们就可以使用@FunctionalInterface注解来修饰了。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 21:31
 */
@FunctionalInterface // 使用该注解修饰之后,说明这个接口必须是一个函数式接口,而且里面只能有一个抽象方法
public interface MyPredicate<T> 

    public boolean test(T t);


下面我用一个例子来说明一下函数式接口的用法,即简单使用一下Lambda表达式来对一个数进行运算,不管是啥运算都行。

由于我们是想使用Lambda表达式来完成,所以首先我们得创建一个函数式接口,例如MyFun。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-09 20:56
 */
@FunctionalInterface // 使用该注解修饰之后,说明这个接口必须是一个函数式接口,而且里面只能有一个抽象方法
public interface MyFun 

    public Integer getValue(Integer num);


然后,编写一个对一个数进行运算的方法。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;
import java.util.function.Consumer;

/*
 *
 * @author liayun
 * @create 2021-12-09 1:02
 */
public class TestLambda2 

    public Integer operation(Integer num, MyFun mf) 
        return mf.getValue(num);
    


最后,调用以上operation方法做个测试。

从上可知,为了将Lambda表达式作为参数进行传递,接收Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型。

实战演练

了解了函数式接口之后,接下来我们来做三道练习题,以巩固目前为止所学的知识。

练习题一

调用Collertions.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同再按姓名比),并使用Lambda表达式作为参数传递。

这道练习题太简单了,唰一下就能做出来,这里我也不买关子了,直接给出代码吧!

package com.meimeixia.exer;

import com.meimeixia.java8.Employee;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author liayun
 * @create 2021-12-09 21:42
 */
public class TestLambda 

    List<Employee> employees = Arrays.asList(
            new Employee(100, "张三", 18, 9999.99),
            new Employee(101, "李四", 38, 5555.99),
            new Employee(102, "王五", 50, 6666.66),
            new Employee(103, "赵六", 16, 3333.33),
            new Employee(104, "田七", 8, 7777.77)
    );

    @Test
    public void test1() 
        Collections.sort(employees, (e1, e2) -> 
            if (e1.getAge() == e2.getAge()) 
                return e1.getName().compareTo(e2.getName());
             else 
                return Integer.compare(e1.getAge(), e2.getAge());
            
        );

        for (Employee emp : employees) 
            System.out.println(emp);
        
    


其中,Employee实体类的代码如下所示。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 20:52
 */
public class Employee 

    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() 

    

    public Employee(int id, String name, int age, double salary) 
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    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 double getSalary() 
        return salary;
    

    public void setSalary(double salary) 
        this.salary = salary;
    

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


此时,运行TestLambda类中的test1测试方法,你便可以在控制台中看到如下打印信息了。

是不已经按年龄从小到大进行排序了啊!

如果我们想按年龄逆序排序,那么又该怎么办呢?很简单,将test1测试方法修改成下面这个样子就行了。

@Test
public void test1() 
    Collections.sort(employees, (e1, e2) -> 
        if (e1.getAge() == e2.getAge()) 
            return e1.getName().compareTo(e2.getName());
         else 
            // return Integer.compare(e1.getAge(), e2.getAge());
            // 如果我们想按年龄逆序排序,那么又该怎么办呢?想按年龄逆序排序,加一个-号就行了。
            return -Integer.compare(e1.getAge(), e2.getAge());
        
    );

    for (Employee emp : employees) 
        System.out.println(emp);
    

再次运行以上test1测试方法,这时,你便可以在控制台中看到如下打印信息了。

练习题二

练习二是这样的:

  1. 声明一个函数式接口,并在接口中声明一个抽象方法,即public String getValue(String str);
  2. 声明一个TestLambda类,在类中编写一个方法,该方法使用接口作为参数,然后将一个字符串转换成大写,并作为方法的返回值返回。
  3. 再将一个字符串的第2个和第4个索引位置进行截取子串。

要想完成该练习题,首先我们得创建一个如下所示的函数式接口。

package com.meimeixia.exer;

/**
 * @author liayun
 * @create 2021-12-10 17:02
 */
@FunctionalInterface
public interface MyFunction 

    public String getValue(String str);


然后,在TestLambda类中编写一个用于处理字符串的方法。

// 需求:用于处理字符串
public String strHandler(String str, MyFunction mf) 
    return mf.getValue(str);

最后,对以上方法进行测试。

@Test
public void test2() 
    String trimStr = strHandler("\\t\\t\\t 我李阿昀牛逼牛逼   ", (str) -> str.trim());
    System.out.println(trimStr);

    String upper = strHandler("abcdef", (str) -> str.toUpperCase());
    System.out.println(upper);

    String newStr = strHandler("我李阿昀牛逼牛逼", (str) -> str.substring(1, 4));
    System.out.println(newStr);

练习题三

最后,再看一道练习题。

  1. 声明一个带两个泛型的函数式接口,泛型类型为<T,R>,其中T为参数,R为返回值。
  2. 接口中声明对应抽象方法。
  3. 在TestLambda类中声明一个方法,使用接口作为参数,计算两个Long型参数的和。
  4. 再计算两个Long型参数的乘积。

要想完成该练习题,首先我们得创建一个如下所示的函数式接口。

package com.meimeixia.exer;

/**
 * @author liayun
 * @create 2021-12-10 17:46
 */
@FunctionalInterface
public interface MyFunction2<T, R> 

    public R getValue(T t1, T t2);


然后,在TestLambda类中编写一个对两个Long型数据进行处理的方法。

// 需求:对两个Long型数据进行处理
public void op(Long l1, Long l2, MyFunction2<Long, Long> mf) 
    System.out.println(mf.getValue(l1, l2));

最后,对以上方法进行测试。

@Test
public void test3() 
    op(100L, 200L, (x, y) -> x + y);

    op(100L, 200L, (x, y) -> x * y);

经过以上三道练习题的磨练,你有没有什么感想呢?

现在你应该体会到了一个非常不好的地方,那就是使用Lambda表达式往往离不开函数式接口的支持,但是如果每次使用Lambda表达式都要自定义一个函数式接口的话,那么Lambda表达式就没有完全起到简化代码的作用了,相反还变得更麻烦了。

实际上没有关系啊,人家Java 8已经给我们内置好了通常使用到的函数式接口,这些接口是不需要我们自己来建的,因为人家Java 8已经给我们提供好了。至于内置了哪些函数式接口,下面我会详细给大家讲解。

Java 8中内置的四大核心函数式接口

Java 8中内置了一些函数式接口来供开发者们调用,这样就不需要开发者们重复定义函数式接口了。Java 8中提供了四大核心函数式接口,它们分别如下。

下面我会一一介绍以上Java 8中提供的四大核心函数式接口。

Consumer<T>:消费型接口

Java 8中消费型接口的定义如下所示。

何谓消费呢?消费就是有去无回。通过观察以下例子,你就应该知道该函数式接口该如何使用了。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.function.Consumer;

/**
 *
 * @author liayun
 * @create 2021-12-10 19:54
 */
public class TestLambda3 
    
    // Consumer<T>:消费型接口,消费就是有去无回
    @Test
    public void test1() 
        happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
    

    public void happy(double money, Consumer<Double> con) 
        con.accept(money);
    


Supplier<T>:供给型接口

Java 8中供给型接口的定义如下所示。

供给型接口主要就是给我们产生一些对象,并返回给我们。例如,产生指定个数的整数,并放入集合中。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 *
 * @author liayun
 * @create 2021-12-10 19:54
 */
public class TestLambda3 
    
    // Supplier<T>:供给型接口,就是给你产生一些对象,并返回给你
    @Test
    public void test2() 
        List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100)); // 随机产生10个整数放入到集合中
        for (Integer num : numList) 
            System.out.println(num);
        
    

    // 需求:产生指定个数的整数,并放入集合中
    public List<Integer> getNumList(int num, Supplier<Integer> sup) 
        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < num; i++) 
            Integer n = sup.get();
            list.add(n);
        

        return list;
    

    // Consumer<T>:消费型接口,消费就是有去无回
    @Test
    public void test1() 
        happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
    

    public void happy(double money, Consumer<Double> con) 
        con.accept(money);
    


以上是关于重学Java 8新特性 | 第5讲——函数式接口的主要内容,如果未能解决你的问题,请参考以下文章

Java8新特性及实战视频教程完整版

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第4讲——Lambda表达式详解

重学Java 8新特性 | 第1讲——我们为什么要学习Java 8新特性?