Java 8 之Function<T, R>和BiFunction<T,U,R>接口

6,747 阅读6分钟

简介

为了更好的将函数作为参数,Java遂引入了Function接口

Function<T, R>的使用

看其源码

image.png

要素察觉

  • 该接口会接收一个参数,且会产生结果
  • 在使用这个接口前需要明确定义参数类型和返回的结果类型(Java的泛型就是这么回事)
  • 里面有一个apply方法将会对参数进行操作,并返回结果
  • 因为是函数式接口(@FunctionalInterface),可以通过lambda表达式实现该接口

测试

public class FunctionTest {
    public static void main(String[] args) {
        FunctionTest test = new FunctionTest();
               //lambda表达式实现了apply,也就实现了Function接口,不要被<Integer, Integer>影响了
        System.out.println(test.operate(5, integer -> integer * integer));//第二个参数是方法
    }                             //输入参数的类型//返回类型
    public int operate(int i, Function<Integer, Integer> function){
           return function.apply(i);//apply的返回值是Integer,参数是i
    }
}

Function<T, R>进阶玩法,使用Function的默认方法composeandThen

再观察一波Function<T, R>接口源码

image.png

要素察觉

  • 两个方法都使用了两次apply,只是this.apply执行的先后不同
  • 说明两个Function可以组合起来用 => 两个apply可以组合使用 => 可以用两个Lambda表达式实现apply

测试


public class FunctionTest {
    public static void main(String[] args) {
        FunctionTest test = new FunctionTest();
        System.out.println(test.operate1(2,integer -> integer*3,integer -> integer*integer));
        System.out.println("-----------------------");
        System.out.println(test.operate2(2,integer -> integer*3,integer -> integer*integer));
    }
    public int operate1(int i,Function<Integer, Integer> function1 ,Function<Integer, Integer> function2){
        return function1.compose(function2).apply(i);//当前对象(this)为function1
    }
    public int operate2(int i,Function<Integer, Integer> function1 ,Function<Integer, Integer> function2){
        return function1.andThen(function2).apply(i);//当前对象(this)为function1
    }
}

解释

  • compose image.png

执行f1之前(before)先执行f2

  • andThen image.png

执行f1之后(after)再执行f2

小结

  • 总之,要分清当前function,也就是当前对象,判断执行顺序。对象有方法和成员变量。
  • 本例中,function1都是当前对象,调用它的方法,以及将function2作为参数传给它。
  • function是对象,只是有函数的功能而已。面向对象不能丢

BiFunction<T,U,R>的使用

看其源码

image.png

要素察觉

  • BiFunction接收两个参数返回一个结果
  • 它有一个抽象方法apply,接收两个参数
  • 它有一个andThen的默认方法
  • Function比起来多了一个参数,少了一个compose方法
    • 因为Function只需要一个参数,BiFunction刚好可以返回一个参数,可以先BiFuctionFunction,所以有andThen方法
    • BiFunction是当前对象,如果它后执行,其他Function先执行,且它们都只会返回一个结果,就会导致BiFunction只有一个参数,满足不了BiFunction的需要两个参数的需求,所以没有compose方法

测试

public class FunctionTest {
    public static void main(String[] args) {
        FunctionTest test = new FunctionTest();
        System.out.println(test.operate3(2,3, (a,b) -> a-b));
        System.out.println("-----------------------");
        System.out.println(test.operate4(3,4,(a,b) -> a*b, result -> result*10));
    }                                           //指定参数类型和返回类型
    public int operate3(int a, int b, BiFunction<Integer,Integer,Integer> function){
        return function.apply(a,b);
    }
    public int operate4(int a, int b, BiFunction<Integer,Integer,Integer> function1,Function<Integer, Integer> function2){
        return function1.andThen(function2).apply(a,b);//andThen的参数是Function
       
    }
}

image.png

解释

image.png

image.png

其实BifuntctionFunction差不多,BiFunction接收两个参数,Function接收一个参数

最后

这两个接口有啥用?

  • 当需要对数据进行操作时且你想用lambda表达式时,就可以用它,而不需要自己去写一个满足函数式接口的接口。
  • 而且对一个或两个数据的修改和操作是不同的,比如两个数的加减乘除,就不需要定义四个方法,只要Bifunction接口,用不同的Lambda表达式实现就好了
  • 某个操作只用一次用Lamdba表达式即可,就不需要单独封装成方法了
System.out.println(test.operate3(2,3, (a,b) -> a+b));// 只用一次,就不需要封装成说明add方法之类的
System.out.println(test.operate3(2,3, (a,b) -> a-b));
System.out.println(test.operate3(2,3, (a,b) -> a*b));
System.out.println(test.operate3(2,3, (a,b) -> a/b));// 只用一次,就不需要封装成说明div方法之类的
public int operate3(int a, int b, BiFunction<Integer,Integer,Integer> function){
    return function.apply(a,b);
 }
  • 如果你一个方法都不想封装,可以这么写
public class FunctionTest {
    public static void main(String[] args) {
        Function<Integer, Integer> function;//省去一堆基本数据类型的变量定义了
        function = a ->a*3;
        System.out.println(function.apply(2));
    }                                
}

另外一个小样例,对集合进行操作

  • People
public class People {
    String name;
    int age;
    //getter和setter,自行添加
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 需求:获取People集合中年龄小于特定的元素
public class FunctionTestPro {
    public static void main(String[] args) {
        FunctionTestPro testPro = new FunctionTestPro();
        People p1 = new People("jetty",18);
        People p2 = new People("kitty",12);
        People p3 = new People("Matty",70);
        List<People> list = Arrays.asList(p1,p2,p3);
        //获取年龄小于70的元素
        List<People> list1 = testPro.getPeople(70,list,(age,peopleList) ->peopleList.stream()//形成流
                .filter(people -> people.getAge()< age ) //获取小于age的元素
                .collect(Collectors.toList()));//将结果累积到List中,并返回
        list1.forEach(System.out::println);//输出
        //是数字码的,请重写People的toString方法
    }                             //条件  需要被操作的集合         // 条件age为Integer 被操作的对象为List  返回值为List  
    public List<People> getPeople(int age, List<People> peopleList ,BiFunction<Integer,List<People>,List<People>> function){
        return function.apply(age,peopleList);//两个参数
    }
}
  • Stream流,将对象以流水的形态通过各种方法过滤,得到所需结果
  • 对于Lambda表达式,如果方法体只有一行,不需要{}也不需要return,它会自动return,如果需要return的话。
  • 如果多行请用{}return
    • (a, b) ->{ a=a*b; return a+b; }

什么时候用Java 提供的Function接口

  • 主要是要有将函数作为参数的思想。
    • 你可以在以下情况下使用Java提供的Function接口:

    • 数据映射:当你需要对集合中的每个元素进行某种映射操作,生成一个新的集合时,可以使用Function接口。

      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      Function<String, Integer> nameLengthMapper = String::length;
      List<Integer> nameLengths = names.stream().map(nameLengthMapper).collect(Collectors.toList());
      // nameLengths 现在包含 [5, 3, 7]
      
    • 连续操作:当你需要在一系列的操作中传递行为时,Function接口可以作为方法参数,使代码更具灵活性。

    public void processWithFunction(String data, Function<String, Integer> processor) {
    int result = processor.apply(data);
    // 处理结果...
    }
    
    processWithFunction("42", Integer::parseInt);
    processWithFunction("Hello", String::length);
    
    • 函数组合:你可以将多个Function接口连接在一起,形成一个复杂的函数组合。
    Function<Integer, Integer> addOne = x -> x + 1;
    Function<Integer, Integer> multiplyByTwo = x -> x * 2;
    Function<Integer, Integer> addOneAndMultiplyByTwo = addOne.andThen(multiplyByTwo);
    int result = addOneAndMultiplyByTwo.apply(5); // 结果为 12 (5 + 1 = 6, 6 * 2 = 12)
    
  • 更好的语义
    • 当涉及多个转换步骤或操作组合时,Function接口可以提供更具说服力的例子。让我们考虑以下情况:假设你有一个字符串列表,其中包含表示人员年龄的字符串,但是你需要计算这些成年的人员年龄的平均值。
    • 我们可以使用Function接口来先将字符串转换为整数,然后计算平均值。尽管这个例子中的三个操作似乎很简单,但考虑到功能的可组合性和代码的可重用性,Function接口仍然非常有用。
    public class Main {
        public static void main(String[] args) {
            List<String> ageStrings = Arrays.asList("25", "30", "40", "22", "28");
    
            // 将字符串转换为整数的方法 
            Function<List<String>, List<Integer>> stringToInteger = ageStringLs -> ageStringLs
                                                     .stream()  
                                                     .map(Integer::parseInt)
                                                     .collect(Collectors.toList());
            
            // 过滤掉未成年人的方法
            Function<List<Integer>, List<Integer>> filterAdults = ages -> ages.stream()  
                                                     .filter(age -> age >= 18)  
                                                     .collect(Collectors.toList());
                                                     
            // 计算平均值的函数
            Function<List<Integer>, Double> averageFunction = list -> list.stream()
                                                     .mapToDouble(Integer::doubleValue)
                                                     .average()
                                                     .orElse(0.0);
    
             // 将三个函数组合在一起,其实就是三个步骤,计算平均年龄,一套下来就获得了所需数据
            Double averageAge = stringToInteger.andThen(filterAdults)
                                                     .andThen(averageFunction)
                                                     .apply(ageStrings);
            
            System.out.println("平均年龄:" + averageAge);
          }
    }
    
    • 这样,就不用写成,像下面那样的代码了,按顺序调用方法,并获取各自的返回值
    public static void main(String[] args) { 
        List<String> ageStrings = Arrays.asList("25", "30", "40", "22", "28"); 
        List<Integer> ages = convertToIntegerList(ageStrings); // 将字符串转换为整数列表 
        List<Integer> adultsAges = filterAdults(ages); // 过滤未成年人  
        double averageAge = calculateAverage(adultsAges); // 计算平均年龄 
        
        System.out.println("平均年龄:" + averageAge); 
    }//下面定义若干个static方法,convertToIntegerList()、filterAdults()、calculateAverage()