Java基础:JDK8四大核心函数式接口详解与应用

star2017 1年前 ⋅ 372 阅读

函数式接口 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口(default 修饰),这又关联了 Java 8 的另一个新特性,允许在接口中定义默认方法(default 修饰)

函数式接口是行为的抽象,是数据转换。其最直接表现是将函数(行为)作为数据传递进方式中。

函数式编程的一大好处,是可以用更精练的代码来表达常用数据处理。函数接口能够轻易地实现模板方法模式,只要将不确定的业务逻辑抽象成函数接口,然后传入不同的 Lambda 表达式即可。

JDK 8 新特性中的 Lambda 表达式是 Java 8 发布的最重要的特性,Lambda 表达式允许把函数作为一个方法的参数传递 (函数作为参数传递进方法中),但是只能用在重写函数式接口的抽象方法上。

与 Lambda 表达式功能一起发布的 函数式接口 是其重要组成部分。Lambda 表达式和方法引用就是针对 函数式接口的使用(即只能用在函数式接口上),因为 Lambda 表达式没有方法名,所以针对的就是接口中的抽象方法,则要求函数式接口中有且只有一个抽象方法,否则就不知道 Lambda 重写的是哪个抽象方法。

Lambda 表达式只能用在重写函数式接口的抽象方法上,因为函数式接口只有一个抽象方法,编译器会认为 Lambda 表达式写的方法就是接口的唯一的抽象方法。

JDK 8 以前的接口中的所有方法必须都是抽象的,不能有非抽象方法,其实现类必须实现其所有方法,而 JDK 在更新过程中可能对原有的接口进行扩展,若在原有接口中加入抽象方法,那实现了该接口的类要全部重写新加入的方法,为了解决更新接口同时又兼容以前版本的程序,就支持在接口中增加 default 方法,变相的让 Java 支持多继承,因为可以实现多个接口。

Java 中的 Lambda 表达式的功能更多应用在流数据处理中(负责迭代数据的集合)。

FunctionalInterface

JDK 8 新增了 @FunctionalInterface 注解,用于标识接口类型声明为函数式接口,从概念上讲,函数式接口只有一个抽象方法。可以通过 java.lang.reflect.Method#isDefault() 反射操作来判断是否为默认方法。

注意:函数式接口的实现可以使用 Lambda 表达式,方法引用或函数引用创建。

@FunctionalInterface 注解只能作用在接口类型上,不能在注解类型,枚举类型 或 类上使用且。作用的目标接口必须满足函数式接口的要求。

@FunctionalInterface 注解用于显式标注一个函数式接口,该注解主要用于编译级错误检查,使用了该注解后,如果自定义接口不满足函数式接口的要求,会报错。并不是所有函数式接口都必须使用该注解进行标识,编译器会把满足函数接口定义的任何接口视为函数接口,而不管接口声明中是否存在 @FunctionalInterface 注解。

4个核心函数式接口

JDK 8 在 java.util.function 包中定义了一些基础的函数式接口,其中有四个可认为是核心的函数式接口:

函数式接口 类型别名 描述 备注
Function<T,R> 函数型 数据转换器,接收一个 T 类型的对象,
返回一个 R 类型的对象,单参数单返回值的行为接口;
提供了 apply, compose, andThen, identity 方法
Supplier<T> 供给型 数据提供器,可以提供 T 类型对象;
无参的构造器,提供了 get 方法
Consumer<T> 消费型 数据消费器, 接收一个 T 类型的对象,无返回值;
通常用于设置 T 对象的值;
单参数无返回值的行为接口;提供了 accept, andThen 方法
Predicate<T> 断定型 条件测试器,接收一个 T 类型的对象,返回布尔值;
通常用于传递条件函数; 单参数布尔值的条件性接口;
提供了 test (条件测试) , and-or- negate(与或非) 方法;
其中, compose, andThen, and, or, negate 用来组合函数接口
而得到更强大的函数接口。

其它的函数接口都是通过这四个扩展而来:

  • 在参数个数上扩展: 比如接收双参数的,有 Bi 前缀, 比如 BiConsumer<T, U>, BiFunction<T, U, R>
  • 在类型上扩展: 比如接收原子类型参数的,有 [Int|Double|Long|Function|Consumer|Supplier|Predicate]。
  • 特殊常用的变形: 比如 BinaryOperator , 是同类型的双参数 BiFunction<T, T, T> ,二元操作符 。UnaryOperator 是 Function<T, T> 一元操作符。

函数式接口可以接收的值类型有:

  • 类/对象的静态方法引用、实例方法引用。引用符号为双冒号 ::
  • 类的构造器引用,比如 Class::new
  • Lambda 表达式。

Consumer接口

接口定义

对入参做一些操作,没有返回值(相当于内部消费掉传入的参数)。

在 Stream 里,主要用于内部 ForEach,内部迭代,对传入的参数做一系列的业务操作。

/**
 * 表示接受单个输入参数但不返回结果的操作
 * 与大多数其他函数接口不同,Consumer 需要通过额外的作用来操作
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * 单一抽象方法 
     * 对给定参数执行此操作。
     */
    void accept(T t);

    /**
     * 用于复合操作的默认方法
     * 返回一个组合的{@code Consumer},按顺序执行此操作,然后执行{@code after}操作。
     * 如果执行任一操作时引发异常,则将异常中继到组合操作的调用方。
     * 如果执行此操作引发异常,则不会执行{@code after}操作。
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

使用示例

void accept(T t):

Lambda 表达式或方法引用相当于重写内部 accept(T t) 方法。

  1. 示例一:consumerTest1

    @Test
    public void consumerTest1() {
        List<String> list = Arrays.asList("Tom", "Kitty", "Rose", "Alex", "Andy");
    
        // 匿名内部类(重写内部 accept 方法, 默认调用单一的抽象方法)
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        System.out.println("----------------------");
    
        // Lambda 表达式(重写内部 accept 方法, 默认调用单一的抽象方法)
        list.forEach(s -> System.out.println(s));
        System.out.println("----------------------");
    
        // 方法引用(重写内部 accept 方法, 默认调用单一的抽象方法)
        list.forEach(System.out::println);
    }
    

    结果输出:

    Tom
    Kitty
    Rose
    Alex
    Andy
    ----------------------
    Tom
    Kitty
    Rose
    Alex
    Andy
    ----------------------
    Tom
    Kitty
    Rose
    Alex
    Andy
    
  2. 示例二:consumerTest2

    /**
     * 1.消费型  Consumer<T>  void accept(T t)
     */
    @Test
    public void consumerTest2() {
        // 匿名方法
        Consumer<String> c1 = new Consumer<String>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };
    
        // Lambda 表达式
        Consumer<String> c2 = str -> System.out.println(str);
    
        // 方法引用
        Consumer<String> c3 = System.out::println;
        c3.accept("Hello World");
    }
    

    输出结果:

    Hello World 1
    
    Hello World 2
    Hello World 3
    
  3. 示例三:consumerTest3

    public void happy(double money, Consumer<Double> con) {
        consumer.accept(money);
    }   
    
    @Test
    public void consumerTest3() {
        // Lambda
        this.happy(200D, s -> System.out.println(s));
    
        // 方法引用
        this.happy(200D, System.out::println);
    
        // 匿名方法
        this.happy(200D, new Consumer<Double>() {
            @Override
            public void accept(Double money) {
                System.out.println(money);
            }
        });
    }
    

    输出结果:

    200.0
    200.0
    200.0
    

andThen(Consumer<? super T> after)

  1. 示例一:consumerAndThen1

    @Test
    public void consumerAndThen1() {
        String[] strArr = {"Tom:boy", "Kitty:girl", "Rose:girl"};
        // 标准Lambda可以使用 lambda表达式替换
        printInfo(strArr, (message) -> {
            System.out.print("Name:" + message.split(":")[0] + "");
        }, (message) -> {
            System.out.println("Gender:" + message.split(":")[1] + "");
        });
        // Lambda 表达式
        printInfo(strArr, (message) -> System.out.print("Name:" + message.split(":")[0] + ""),
                  (message) -> System.out.println("Gender:" + message.split(":")[1] + ""));
    }
    
    // 传入多个 Consumer,指定执行顺序
    public void printInfo(String[] strArr, Consumer<String> con1, Consumer<String> con2) {
        for (int i = 0; i < strArr.length; i++) {
            con1.andThen(con2).accept(strArr[i]);
        }
    }
    

    输出结果:

    Name:TomGender:boy
    Name:KittyGender:girl
    Name:RoseGender:girl
    Name:TomGender:boy
    Name:KittyGender:girl
    Name:RoseGender:girl
    
  2. 示例二:consumerAndThen2

    @Test
    public void consumerAndThen2() {
        List<Integer> list = Arrays.asList(1, 3, 2, 6, 9, 5);
    
        Consumer<Integer> consumer = x -> System.out.print(x);
        // 组合 Consumer
        Consumer<Integer> consumerAndThen = consumer.andThen(i -> {
            System.out.println(", print:" + i);
        });
    
        list.forEach(x -> consumerAndThen.accept(x));
        System.out.println("----------------------");
    
        list.forEach(consumerAndThen::accept);
        System.out.println("----------------------");
    
        // 函数调用,调用默认的抽象方法accept
        list.forEach(consumerAndThen);
        System.out.println("----------------------");
    
        for (Integer integer : list) {
            consumerAndThen.accept(integer);
        }
    }
    

    输出结果:

    1, print:1
    3, print:3
    2, print:2
    6, print:6
    9, print:9
    5, print:5
    ----------------------
    1, print:1
    3, print:3
    2, print:2
    6, print:6
    9, print:9
    5, print:5
    ----------------------
    1, print:1
    3, print:3
    2, print:2
    6, print:6
    9, print:9
    5, print:5
    ----------------------
    1, print:1
    3, print:3
    2, print:2
    6, print:6
    9, print:9
    5, print:5
    

Supplier接口

Consumer 函数式接口相当于消费者,Supplier 接口就相当于生产者了,只有一个 get() 抽象方法返回结果。

接口定义

/**
 * 表示结果的提供者
 * 不要求每次调用 supplier 时都返回一个新的或不同的结果
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

    /**
     * 获取结果
     */
    T get();
}

使用示例

T get()

public class SupplierTest {

    @Test
    public void supplierTest1() {
        Supplier<String> supplier = String::new;
        String string = supplier.get();
    }

    @Test
    public void supplierTest2() {
        Supplier<User> supplier = User::new;
        User user = supplier.get();
    }

    @Test
    public void supplierTest3() {
        Supplier<String> supplier = () -> "Hello World";
        System.out.println(supplier.get());
    }


    public List<Integer> getNumList(int num, Supplier<Integer> supplier) {
        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < num; i++) {
            Integer n = supplier.get();
            list.add(n);
        }
        return list;
    }

    @Test
    public void supplierTest4() {
        List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));

        for (Integer num : numList) {
            System.out.println(num);
        }
    }

}

Function接口

Function 接收参数,然后运算转换为结果返回。

接口定义

/**
 * 表示接受一个参数并生成结果的函数
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

    /**
     * 将此函数应用于给定参数
     */
    R apply(T t);

    /**
     * 返回一个组合函数, 先做入参 before 函数的apply操作, 然后做当前接口的apply操作
     * 如果任何一个函数的求值引发异常, 它将被中继到组合函数的调用者
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 返回一个组合函数, 先做本接口数的apply操作, 然后做入参 after 函数的apply操作
     * 如果任何一个函数的求值引发异常, 它将被中继到组合函数的调用者。
     */
    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;
    }
}

使用示例

R apply(T t)

  1. 示例一

    @Test
    public void functionTest1() {
    
        // 字符串
        Function<Integer, Integer> fun1 = x -> x * 2;
        System.out.println(fun1.apply(4));// 8
    
        Function<Integer, String> fun2 = x -> x * 2 + "aa";
        System.out.println(fun2.apply(4));//8dd
    
        // 字符串对象
        Function<String, String> strFun3 = (str) -> new String(str);
        System.out.println(strFun3.apply("bb"));//bb
    
        Function<String, String> strFun4 = String::new;
        System.out.println(strFun4.apply("cc"));//cc
    
        // 对象
        Function<String, User> objFun5 = (str) -> new User(str);
        System.out.println(objFun5.apply("dd").getName());//dd
    
        Function<String, User> objFun6 = User::new;
        System.out.println(objFun6.apply("ee").getName());//ee
    }
    
    @Test
    public void functionTest2() {
        // 1.函数型  Function<T,R> R apply(T t)
        Function<String, Integer> function2 = s -> s.length();
        Function<String, Integer> function1 = String::length;
        System.out.println(function1.apply("hello world"));
        System.out.println(function2.apply("hello world"));
        //结果:11
    }
    
    // Function<T, R> 函数型接口
    // 需求:用于处理字符串
    public String strHandler(String str, Function<String, String> fun) {
        return fun.apply(str);
    }
    
    @Test
    public void functionTest3() {
        String newStr2 = strHandler("\t\t\t Hello World", (str) -> str.trim());
        String newStr1 = strHandler("\t\t\t Hello World", String::trim);
        System.out.println(newStr1);
        System.out.println(newStr2);
    
        String subStr = strHandler("\t\t\t Hello World", (str) -> str.trim().substring(0, 4));
        System.out.println(subStr);
        //Hello World
        //Hello World
        //Hell
    }
    
  2. 示例二

    public class FunctionTest1 {
    
        public static void main(String[] args) {
            FunctionTest fun = new FunctionTest();
            // 传递行为,而不是传递值
            System.out.println(fun.comput(1, result -> 2 * num));
            System.out.println(fun.comput(2, result -> 5 + num));
            System.out.println(fun.comput(3, Integer::intValue));
            System.out.println(fun.convert(4, result -> num + "helloworld"));
        }
    
        public int comput(int num, Function<Integer, Integer> function) {
            //apply ,传递的是行为
            int result = function.apply(num);
            return result;
        }
    
        public String convert(int num, Function<Integer, String> function) {
            return function.apply(num);
        }
    
        // 对于之前只传递值的写法,几种行为就要定义几种写法。 现在可以使用上面的方式去 传递行为
        public int method1(int num) {
            return num + 1;
        }
    
        public int method2(int num) {
            return num * 5;
        }
    
        public int method3(int num) {
            return num * num;
        }
    }
    

Predicate接口

接口定义

/**
 * 一个参数的布尔值函数。
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * 根据给定参数计算布尔表达式
     */
    boolean test(T t);

    /**
     * 返回一个组合的布尔表达式, 以表示 this 布尔表达式 与 other 布尔表达式的短路逻辑与(&&)
     * 如果 this 为 false, 则不会评估
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * 返回取反的布尔表达式
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * 返回一个组合的布尔表达式, 以表示 this 布尔表达式 与 other 布尔表达式的短路逻辑或(||)
     * 如果 this 为 true, 则不会评估
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * 返回给定的两个参数是否相等的布尔表达式
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

使用示例

boolean test(T t)

public class PredicatePreTest {

    @Test
    public void test1() {
        //断定型  Predicate<T>  boolean test(T t)
        Predicate<Integer> predicate = num -> num >= 100;
        System.out.println(predicate.test(99));
        //false
    }

    @Test
    public void test2() {
        List<String> list = Arrays.asList("Tom", "Kitty", "Rose", "Andy");
        List<String> strList = filterStr(list, (s) -> s.length() > 3);

        for (String str : strList) {
            System.out.println(str);
            //Kitty
            //Rose
            //Andy
        }
    }

    //需求:将满足条件的字符串,放入集合中去
    public List<String> filterStr(List<String> list, Predicate<String> pre) {
        List<String> strList = new ArrayList<>();
        for (String str : list) {
            if (pre.test(str)) {
                strList.add(str);
            }
        }
        return strList;
    }
}

相关参考

  1. Java 8 函数式编程探秘 ( 上 )
  2. Java 8 函数式编程探秘 ( 下 )
  3. JAVA8 Consumer接口
  4. JDK 8 新特性一览
  5. Java 8 新特性
  6. JDK8-十大新特性
  7. Java 8 函数式接口
  8. JDK8函数式接口
  9. Java 8的核心新特性:Lambda(匿名函数)、流、默认方法
  10. JAVA8学习-深入浅出函数式接口FunctionInterface
  11. JDK8新特性之函数式接口
更多内容请访问:IT源点

相关文章推荐
  • 该目录下还没有内容!

全部评论: 0

    我有话说: