Java 函数接口Function详解与示例【函数接口Function】

Java 8引入了一种新的函数式编程风格,Function接口是Java函数式编程中最重要的四个函数式接口之一。

Function 函数式接口实现的功能:接受一个输入参数,然后产生一个输出结果。
Function接口在java.util.function包中定义,它的源码如下:

package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
    /**抽象方法*/
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

Function 接口是非常有用的,它是Java函数式编程中最重要的四个函数式接口中使用最为广泛的。

用途一:函数转换
Function可以用于将一个类型的值转换为另一个类型的值。它可以用于各种转换操作,如类型转换、数据映射map等。

用途二:数据处理
Function 可用于对输入数据进行处理并生成输出结果。它可以用于执行各种操作,如过滤filter、计算、提取、格式化等。

Function接口定义的方法:

  • 抽象方法: R apply(T t),该方法接受一个参数 t(类型为 T),并返回一个结果(类型为 R)。也就是说,接受一个类型 T 的输入数据 ,经过转换后返回一个类型 R 的数据。
  • 默认方法: Function<T, V> andThen(Function<? super R, ? extends V> after) 返回一个组合函数,把该函数结果应用到after函数中。
  • 使用andThen方法将多个函数应用于同一个输入:

    Function<String, String> function1 = s -> s + " world";
    Function<String, String> function2 = s -> s + "!";
     
    Function<String, String> combined = function1.andThen(function2);
     
    String result = combined.apply("Hello"); // 结果为 "Hello world!"
    
  • 默认方法:Function<V, R> compose(Function<? super V, ? extends T> before) 返回一个组合函数,首先将输入参数应用到before函数,再将before函数结果应用到该函数中。
    使用方法compose()将多个函数应用于同一个输入,但是顺序与方法andThen()的作用刚好相反:
  • Function<String, String> function1 = s -> s + " world";
    Function<String, String> function2 = s -> s + "!";
    /***注意:这里故意调换了两个函数的调用顺序***/
    Function<String, String> combined = function2.compose(function1);
    String result = combined.apply("Hello"); // 结果为 "Hello world!"
    
  • 静态方法: Function<T, T> identity() 恒等式函数identity(),返回输入参数本身。
    Function函数接口的方法identity()是一个直接返回入口参数的方法(函数,Lambda表达式对象),等价于形式如:t -> t 形式的Lambda表达式。一看感觉没啥用处,其实它在某些场景大有用处,例如:作为默认函数,可以作为函数组合链中的起点或默认函数。
    保持一致性,在某些情况下,我们可能需要一个函数,但不需要对输入进行任何操作。使用方法 identity() 可以确保函数的签名(输入和输出类型)与其函数一致。
  • 当我们使用Stream时,要将流中元素转换成其他容器或Map时,就会使用到Function.identity()。例如:

    Stream<String> stream = Stream.of("This", "is", "a", "test");
    Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
    

    Function.identity()应用示例

    下面的代码中,Task是一个对象,它包含Title等属性,Task::getTitle可获取task实例的标题作为Map的key,我们把task对象本身作为Map的value。task -> task是一个用来返回输入参数自身的Lambda表达式。
    我们来对比一下,使用Lambda表达式和使用Function.identity()方法的用法:

    	/***使用Lambda表达式实现的版本***/
    	private static Map<String, Task> taskMap(List<Task> tasks) {
      		return tasks.stream().collect(toMap(Task::getTitle, task -> task));
    	}
    

    可以使用Function接口中的静态方法identity()来让上面的代码代码变得更简洁明了、传递开发者意图时更加直接。使用Function.identity()方法的写法:

    	/***使用Function.identity()方法实现的版本***/
    	import static java.util.function.Function.identity;
    	private static Map<String, Task> taskMap(List<Task> tasks) {
      		return tasks.stream().collect(toMap(Task::getTitle, identity()));
    	}
    

    Function.identity() 和 Lambda表达式( t->t ) 的区别的应用示例

    下面三种写法实现相同功能,这是Function.identity() 和 Lambda表达式( t->t )可以互相替代的情形:

    		/***写法一***/
        	Arrays.asList("How", "much", "is","the","apple")
            	.stream()
            	.collect(Collectors.toMap( str -> str, str->str.length()));
            	
         	/***写法二,Lambda表达式( t->t )版本***/
        	Arrays.asList("How", "much", "is","the","apple")
            	.stream()
            	.map(str -> str)  //Lambda表达式( t->t )
            	.collect(Collectors.toMap(str -> str,str->str.length()));    //String::length
            		
            /***写法三,Function.identity()版本***/
        	Arrays.asList("How", "much", "is","the","apple")
            	.stream()
            	.map(Function.identity())  //Function.identity()版本 
            	.collect(Collectors.toMap( Function.identity(), String::length));
    

    下面看一种 Function.identity() 和 Lambda表达式( t->t )不能互相替代的情形:
    这儿使用 Lambda表达式( t->t )可行:

        int[] iArr = Arrays.asList(5, 9, 12,3).stream().mapToInt(t->t).toArray();
    

    但是,如下使用 Function.identity() 是不行的:

        int[] iArr = Arrays.asList(5, 9, 12,3).stream().mapToInt( Function.identity() ).toArray();
    

    为什么不行呢?其本质是 Function.identity(), Function<T, T> identity() 恒等式函数identity(),返回输入参数本身。输入是Integer对象,返回的还是Integer对象。
    而映射器mapToInt()需要返为基本数据类型的IntStream。
    而上面的Lambda表达式( t->t )在映射器 mapToInt(t->t) 中能把Integer对象自动拆箱变成int,从而产生IntStream。
    Function.identity() 不适用于 mapToInt()、mapToLong()、mapToDouble() 等需要进行拆箱操作的场景。

    Function接口的应用实例
    使用Function接口作为方法的参数:

    void process(Function<String, String> function, String input) {
        String result = function.apply(input);
        System.out.println(result);
    }
     
    process(s -> s + "!", "Hello"); // 输出 "Hello!"
    

    使用Java 8的Stream API进行映射:

    List<String> strings = Arrays.asList("a", "b", "c");
     
    List<String> results = strings.stream()
        .map(s -> s + "!")
        .collect(Collectors.toList());
     
    // 结果为 ["a!", "b!", "c!"]
    

    Function函数接口有一大家族,如下所示:

    参考文献:
    【Java函数篇】Java8中函数接口Function使用详解
    Java 8 Function 函数接口
    Function.identity()的使用详解
    Java8中Function函数式接口详解及使用
    Java8中Function接口的使用方法详解
    Java 8 新特性—函数式接口

    作者:Java编程乐园

    物联沃分享整理
    物联沃-IOTWORD物联网 » Java 函数接口Function详解与示例【函数接口Function】

    发表回复