所有分类
  • 所有分类
  • 未分类

Java-函数式接口(java.util.function包)

简介

本文介绍Java的函数式接口(java.util.function包)。

常用接口

接口定义方法作用说明
Function< T, R >R apply(T t);参数:T对象。返回值:R对象。功能型接口
Consumer< T >void accept(T t);参数:T对象。返回值:无消费型接口
Supplier< T >T get();参数:无。返回值:T对象供给型接口
Predicate< T >boolean test(T t);参数:T对象。返回值:boolean断言型接口

不常用接口

接口定义描述
UnaryOperator< T >参数:T对象。返回值:T对象
BiConsumer<T, U>参数:T对象和U对象。返回值:无
BiPredicate<T, U>参数:T对象和U对象。返回值:boolean
BiFunction<T, U, R>参数:T对象和U对象。返回值:R对象
BinaryOperator< T >参数:两个T对象。参会之:T对象

JDK1.8函数接口变化

JDK1.8新增加的函数接口:

  • java.util.function
  • java.util.stream

JDK1.8之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

Function< T, R >  功能型

简介

概述

作用

实现一个”一元函数“,即传入一个值经过函数的计算返回另一个值。

使用场景

  1. V HashMap.computeIfAbsent(K , Function<K, V>) // 简化代码,如果指定的键尚未与值关联或与null关联,使用函数返回值替换。
  2. <R> Stream<R> map(Function<? super T, ? extends R> mapper); // 转换流

设计思想

一元函数的思想,将转换逻辑提取出来,解耦合

定义

package java.util.function;
import java.util.Objects;

@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));
    }   

    // 返回一个执行了apply()方法之后只会返回输入参数的函数对象。
    static <T> Function<T, T> identity() {
        return t -> t;
    } 
}

实例

apply示例1:直接返回

Function<String, String> function = a -> "Hello," + a;
System.out.println(function.apply("Jack")); // Hello,Jack

apply示例2:执行代码再返回

Function<String, String> function = a -> {
    System.out.print("Welcome!");
    return "Hello," + a;
}
System.out.println(function.apply("Jack")); // Welcome!Hello,Jack

compose

Function<String, String> function = a -> {a + " Jack!"};
Function<String, String> function1 = a -> {a + " Bob!"};
String greet = function.compose(function1).apply("Hello");
System.out.println(greet); // Hello Bob! Jack!

andThen

Function<String, String> function = a -> {a + " Jack!"};
Function<String, String> function1 = a -> {a + " Bob!"};
String greet = function.andThen(function1).apply("Hello");
System.out.println(greet); // Hello Jack! Bob!

所有接口

接口定义描述
Function< T, R >接收T对象,返回R对象。
DoubleFunction<R>接收一个double类型的参数并返回结果的函数
DoubleToIntFunction接收一个double类型的参数并返回int结果的函数
DoubleToLongFunction接收一个double类型的参数并返回long结果的函数
IntFunction<R>接收一个int类型的参数并返回结果的函数
IntToDoubleFunction接收一个int类型的参数并返回double结果的函数
IntToLongFunction接收一个int类型的参数并返回long结果的函数
LongFunction<R>接收一个long类型的参数并返回结果的函数
LongToDoubleFunction接收一个long类型的参数并返回double结果的函数
LongToIntFunction接收一个long类型的参数并返回int结果的函数
ToDoubleBiFunction<T,U>接收两个参数并返回double结果的函数
ToDoubleFunction<T>接收一个参数并返回double结果的函数
ToIntBiFunction<T,U>接收两个参数并返回int结果的函数
ToIntFunction<T>接收一个参数并返回int结果的函数
ToLongBiFunction<T,U>接收两个参数并返回long结果的函数
ToLongFunction<T>接收一个参数并返回long结果的函数

Consumer< T > 消费型

简介

概述

作用

消费某个对象

使用场景

Iterable接口的forEach方法需要传入Consumer,大部分集合类都实现了该接口,用于返回Iterator对象进行迭代。

设计思想

  1. 开发者调用ArrayList.forEach时,一般希望自定义遍历的消费逻辑,比如:输出日志或者运算处理等。
  2. 处理逻辑留给使用者,使用灵活多变。
  3. 多变的逻辑能够封装成一个类(实现Consumer接口),将逻辑提取出来。

定义

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {
    //提供一个T类型的输入参数,不返回执行结果
    void accept(T t);

    //返回一个组合函数,after将会在该函数执行之后应用
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

实例

accept

StringBuilder sb = new StringBuilder("Hello ");
Consumer<StringBuilder> consumer = (str) -> str.append("Jack!");
consumer.accept(sb);
System.out.println(sb.toString());	// Hello Jack!

andThen

StringBuilder sb = new StringBuilder("Hello ");
Consumer<StringBuilder> consumer = (str) -> str.append("Jack!");
Consumer<StringBuilder> consumer1 = (str) -> str.append(" Bob!");
consumer.andThen(consumer1).accept(sb);
System.out.println(sb.toString());	// Hello Jack! Bob!

所有接口

接口描述
Consumer<T>提供一个T类型的输入参数,不返回执行结果
BiConsumer<T,U>提供两个自定义类型的输入参数,不返回执行结果
DoubleConsumer表示接受单个double值参数,但不返回结果的操作
IntConsumer表示接受单个int值参数,但不返回结果的操作
LongConsumer表示接受单个long值参数,但不返回结果的操作
ObjDoubleConsumer<T>表示接受object值和double值,但是不返回任何操作结果
ObjIntConsumer<T>表示接受object值和int值,但是不返回任何操作结果
ObjLongConsumer<T>表示接受object值和long值,但是不返回任何操作结果

Supplier< T >  供给型

简介

概述

作用

创建一个对象(工厂类)

使用场景

Optional.orElseGet(Supplier<? extends T>):当this对象为null,就通过传入supplier创建一个T返回。

设计思想

封装工厂创建对象的逻辑

定义

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {
    //获取结果值
    T get();
}

实例(常用)

示例1:直接返回值

Supplier<String> supplier = () -> "Hello Jack!";
System.out.println(supplier.get()); // Hello Jack!

示例2:执行代码

Supplier<String> supplier = () -> {
    int i = 3;
    return i + 1;
}
System.out.println(supplier.get()); // 4

示例3:封装功能

假如我们有好几个地方需要手动捕获异常,然后打印出来,正常写法:每段代码都try catch一下。这样会很麻烦!方案是:封装成Supplier,然后调用:

package com.example.a;

import java.util.function.Supplier;

public class Demo {
    public static void main(String[] args) {
        // 原始语句:Integer i = 2 / 0;
        Integer i = executeCatchException(() -> {
            return 2 / 0;
        });
        System.out.println(i);

        Integer a = null;
        // 原始语句:Boolean result = a.equals(2);
        Boolean result = executeCatchException(() -> {
            return a.equals(2);
        });
        System.out.println(a);
    }

    private static <T> T executeCatchException(Supplier<T> supplier) {
        T t = null;
        try {
            t = supplier.get();
        } catch (Exception e) {
            String msg = "报错:" + e.getMessage();
            System.out.println(msg);
        }

        return t;
    }
}

结果

报错:/ by zero
null
报错:null
null

所有接口

接口描述
Supplier<T>不提供输入参数,但是返回结果的函数
BooleanSupplier不提供输入参数,但是返回boolean结果的函数
DoubleSupplier不提供输入参数,但是返回double结果的函数
IntSupplier不提供输入参数,但是返回int结果的函数
LongSupplier不提供输入参数,但是返回long结果的函数

Predicate< T > 断言型接口

简介

概述

作用

  • 判断对象是否符合某个条件

使用场景

  • ​ArrayList的removeIf(Predicate):删除符合条件的元素
  • ​如果条件硬编码在ArrayList中,它将提供无数的实现,但是如果让调用者传入条件,这样ArrayList就可以从复杂和无法猜测的业务中解放出来。

设计思想

  • 提取条件,让条件从处理逻辑脱离出来,解耦合

定义

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    // 根据给定的参数进行判断
    boolean test(T t);

    // 返回一个组合判断,将other以短路与的方式加入到函数的判断中
    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);
    }

    // 返回一个组合判断,将other以短路或的方式加入到函数的判断中
    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);
    }
}

实例

test

Predicate<Integer> predicate = number -> number != 0;
System.out.println(predicate.test(10));    //true

and

Predicate<Integer> predicate = number -> number != 0;
predicate = predicate.and(number -> number >= 10);
System.out.println(predicate.test(10));    //true

or

Predicate<Integer> predicate = number -> number != 0;
predicate = predicate.or(number -> number != 10);
System.out.println(predicate.test(10));    //true

所有接口

接口描述
Predicate<T>对给定的输入参数执行操作,返回一个boolean类型的结果(布尔值函数)
BiPredicate<T,U>对给定的两个输入参数执行操作,返回一个boolean类型的结果(布尔值函数)
DoublePredicate对给定的double参数执行操作,返回一个boolean类型的结果(布尔值函数)
IntPredicate对给定的int输入参数执行操作,返回一个boolean类型的结果(布尔值函数)
LongPredicate对给定的long参数执行操作,返回一个boolean类型的结果(布尔值函数)

原理

apply

首先我们已经知道了Function是一个泛型类,其中定义了两个泛型参数T和R,在Function中,T代表输入参数,R代表返回的结果。也许你很好奇,为什么跟别的java源码不一样,Function 的源码中并没有具体的逻辑呢?

其实这很容易理解,Function 就是一个函数,其作用类似于数学中函数的定义:y = f(x) ,(x,y)跟<T,R>的作用几乎一致。所以Function中没有具体的操作,具体的操作需要我们指定,apply具体返回的结果取决于传入的lambda表达式。

举例:

Function<Integer, Integer> test = i -> i + 1;
System.out.println(test.apply(5));

用lambda表达式定义了一个行为使得i自增1,我们使用参数5执行apply,最后返回6。这跟我们以前看待Java的眼光已经不同了,在函数式编程之前我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法我们可以后续再设置。

再举个例子:

public void test() {
    Function<Integer, Integer> test1 = i -> i + 1;
    Function<Integer, Integer> test2 = i -> i * i;
    /* print:6 */
    System.out.println(calculate(test1, 5));
    /* print:25 */
    System.out.println(calculate(test2, 5));
}

public static Integer calculate(Function<Integer, Integer> test, Integer number) {
    return test.apply(number);
}

通过传入不同的Function,实现了在同一个方法中实现不同的操作。在实际开发中这样可以大大减少很多重复的代码,比如我在实际项目中有个新增用户的功能,但是用户分为VIP和普通用户,且有两种不同的新增逻辑。那么此时我们就可以先写两种不同的逻辑。除此之外,这样还让逻辑与数据分离开来,我们可以实现逻辑的复用。

当然实际开发中的逻辑可能很复杂,比如两个方法F1,F2都需要操作AB,但是F1需要A->B,F2方法需要B->A。这样的我们用刚才的方法也可以实现,源码如下:

public void test() {
    Function<Integer, Integer> A = i -> i + 1;
    Function<Integer, Integer> B = i -> i * i;
    /* F1:36 */
    System.out.println("F1:" + B.apply(A.apply(5)));
    /* F2:26 */
    System.out.println("F2:" + A.apply(B.apply(5)));
}

compose和andThen

如上边例子所示,假如我们F1,F2需要四个逻辑ABCD,那我们还这样写就会变得很麻烦了。compose和andThen可以解决我们的问题。

先看compose的源码:compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后使用当前Function的apply。如下所示

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

andThen跟compose正相反,先执行当前的逻辑,再执行传入的逻辑:

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

这样说可能不够直观,我可以换个说法:compose等价于B.apply(A.apply(5)),而andThen等价于A.apply(B.apply(5))。

Function<Integer, Integer> A = i -> i + 1;
Function<Integer, Integer> B = i -> i * i;
/* F1:36 */
System.out.println("F1:" + B.apply(A.apply(5)));
/* F1:36 */
System.out.println("F1:" + B.compose(A).apply(5));
/* F2:26 */
System.out.println("F2:" + A.apply(B.apply(5)));
/* F2:26 */
System.out.println("F2:" + B.andThen(A).apply(5));

其他网址

《JAVA开发实战经典 第2版》=> 16.10 内建函数式接口 ​

0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录