Java8的函数式接口可能是这样的!

897 阅读8分钟

Java8中新增了函数式接口的概念,那么和之前的接口相比,函数式接口有什么不同呢?西红柿下面来一一告诉你。

函数式接口是what嘞

函数式接口:函数式接口中有且仅有一个抽象方法,这个抽象方法的意义在于表达某种行为。
通过定义貌似看不出什么,👇我们看几个具体的案例。

案例一

接口中可以有多个默认实现,但是默认实现需要用default关键字显示标注。

@FunctionalInterface
//此注解用于显式表示一个接口是否满足函数式接口的定义,如果不满足定义会标红
public interface MyInterface {
    String getName();
    
    //这是默认实现的方法,并不违反函数接口的规则。
    default int getAge(){
        return 1;
    }
}

在Java8之前,接口中的方法都必须是抽象,Java团队巧妙的利用default,为了解决版本的兼容问题。

案例二

接口可以包含静态方法。

@FunctionalInterface
public interface MyInterface {
    String getName();

    //这是静态的方法,并不违反函数接口的规则。
    static int getAge() {
        return 1;
    }
}

案例三

接口可以包含和Object的public一样方法签名的方法,但是此方法需要是抽象的。

@FunctionalInterface
public interface MyInterface {
    String getName();

    //该方法的方法签名与Object的相同
    //并且是抽象的,因为实现此接口的实现类,必然会提供方法的实现。
    int hashCode();
}

上面三个案例就是函数式接口的声明,那么如何去使用函数式接口呢?

函数式接口是咋用嘞

函数式接口的实例有三种方式创建:Lambda表达式、方法引用和构造方法引用
因为函数式接口只有一个抽象方法,所以Lambda表达式实际代表的行为就是接口中的抽象方法,也从侧面说明了Lambda表达式不能脱离函数式接口的语境。
我们👇就看一下 函数式接口与Lambda表达式之间的联系。

Lambda表达式的基本结构是这样的👇:

(Type1 arg1,Type2,arg2,...) -> {body}

为了简化上面结构,Java为我们定义了一些规则:

1) 如果参数可以通过类型推导出来,那么参数类型可以不写。(可以先不写试试)
  
2) 如果参数只有一个,可以将括号省略。
   
3) body如果只有一行代码,可以省略花括号。注意的是一行代码的表示有表达式和语句之分,需要配合return、分号、花括号的情况。

4) 为了便于理解和说明,这里将Lambda表达式的按着其参数和返回值类型进行描述。<br>
(int a,int b)->a+b 的类型为(int,int)->int<br>
(int a,int b)->println(a+b) 的类型为(int,int)->Unit

通过Lambda表达式就可以使用函数式接口,贴心的JDK为我们提供了一些常用的接口,下面来看看有哪些

JDK中的函数式接口

Consumer

接受一个参数,不返回结果

/**
 * 这个函数式接口代表一种操作行为:无返回结果的单一参数操作
 * 接口中的抽象方法,就是操作
 * 仍包含一个默认方法
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * 对给定的参数进行操作
     */
    void accept(T t);

    /**
     * 返回一个组合的Consumer。对t参数执行完给定的行为后,再去执行after行为。
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

我们以集合遍历为例

public class Main {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);

        //forEach函数参数类型是Consumer,对集合的每个元素执行accept行为(①)
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                //此处的行为就是对元素进行打印,
                System.out.println(integer);
            }
        });

        //Lambda表达式 就是描述的接口的抽象方法
        //本例中,接口的抽象方法 (接受一个参数,执行一个无返回结果的操作)
        //也就是(T t)->Unit,式子中的item可以命名为任意名字
        list.forEach(item -> System.out.println());
    }

}

①的源码如下:

//调用的是Iterable接口的默认方法forEach
default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        //遍历集合的每一个元素
        for (T t : this) {
            //调用接口的accept行为方法,参数是集合的元素
            action.accept(t);
        }
    }

以上的源码就说明了 Consumer代表一种抽象的行为,集合的元素去参与这个行为,开发者需定义出具体行为。

Function

接受一个参数,返回一个结果

/**
 * 接受一个t参数返回一个R类型的结果,T与R可以相同也可以不同
 */
@FunctionalInterface
public interface Function<T, R> {

    /**
     * t参数去执行给定的行为
     */
    R apply(T t);

    /**
     * 返回一个符合函数,在本实例行为执行前,先去执行before的行为,将before的结果作为本实例行为的输入
     * 因此,before的结果类型一定要是T类型的子类或本身。
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        //先执行before,再执行本实例的行为
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 返回一个符合函数,在本实例行为执行后,紧接着去执行after的行为,将本实例的结果作为after行为的输入
     * 因此,after的输入一定是本实例结果的父类型或本身
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        //先执行本实例,再执行after
        return (T t) -> after.apply(apply(t));
    }

    /**
     * 返回一个参数无操作的行为
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

以字符串取小写为例

public class FunctionTest {
    public static void main(String[] args) {
        FunctionTest functionTest = new FunctionTest();
        
        //匿名函数 (①)
        functionTest.operation("Hello world", new Function<String, String>()         {
            @Override
            public String apply(String str) {
                return str.toLowerCase();
            }
        });

        //表达式
        functionTest.operation("Hello world", item -> item.toLowerCase());

        //Lambda表达式声明函数式接口的实例
        Function<String, String> function = item -> item.toLowerCase();
        functionTest.operation("Hello world",function);

        //语句
        functionTest.operation("Hello world", item -> {
            return item.toLowerCase();
        });
        
        //以上几种方式 都是对Function的实践。定义一种行为:对字符串进行小写并返回
        //输入类型和输出类型都是字符串。
        //开发者自己定义操作的行为,调用的方法都是operation。
    }

    
    public String operation(String str, Function<String, String> function) {
        return function.apply(str);
    }
}

以①为例分析其执行流程
首先,调用operation方法,参数是"Hello World"、匿名类。
其次,调用参数function的apply方法,实际调用的是传入的匿名类的apply方法。
最后,apply方法的参数是传入的"Hello World",执行取小写操作。
综上:开发者需要的是在方法的调用处,定义出需执行的行为,传递的是行为。定义行为的方式有匿名类,Lambda表达式,方法引用等,Lambda表达式更加简洁。

BiFunction

接受两个参数,返回一个结果

/**
 * 是Function思想的特殊化,接受两个参数,返回一个R类型结果
 */
@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * 对t和u参数执行给定的行为
     */
    R apply(T t, U u);

    /**
     * 返回一个复合的函数,在执行完本实例行为后,将本实例的结果作为after行为的输入,执行after行为。
     */
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        //先执行本实例行为,在执行after行为
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

以数字的四则运算操作为例

public class FunctionTest2 {
    public static void main(String[] args) {
        FunctionTest2 test = new FunctionTest2();
        //Lambda表达式创建出BiFunction实例
        //开发者需自己定义出 执行的行为,并传递给compute函数
        //compute函数执行具体的行为
        test.compute(2, 3, (a, b) -> a + b);
        test.compute(2, 3, (a, b) -> a - b);
        test.compute(2, 3, (a, b) -> a * b);
        test.compute(2, 3, (a, b) -> a / b);

        //先执行两个数字相乘,将结果执行平方(①)
        test.compute1(2, 3, (a, b) -> a * b, a -> a * a);
    }


    public int compute(int a, int b, BiFunction<Integer, Integer, Integer> function) {
        return function.apply(a, b);
    }

    public int compute1(int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
        return biFunction.andThen(function).apply(a, b);
    }
}

①的执行流程如下: 首先,将Lambda表达式构建的接口实例传入compute1方法。
然后,调用BiFunction(数字的乘积)的andTen方法,参数是 function(数字的平方)
之后,调用BiFunction(数字的乘积)的apply方法,计算出实际的结果
接着,调用function(数字的平方)的apply方法,参数是上一步的结果
最后,返回结果

Predicate

判断参数是否满足 断言

/**
 * 判断参数是否满足 断言(也就是布尔值)
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * 对给定的参数t执行测试行为
     */
    boolean test(T t);

    /**
     * 与操作
     */
    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);
    }

    /**
     * 或操作
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     *测试两个实例是否相同(equals相等)
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

以字符串判断equals为例

public class PredicateTest {
    public static void main(String[] args) {

        PredicateTest predicateTest = new PredicateTest();
        //第一处
        predicateTest.predicateString("Hello", item -> item.equals("test"));
        


        //第二处
        Predicate<String> predicate = Predicate.isEqual("test");
        predicate.test("Hello");
        
        //以上两个例子并无不同,Predicate的静态isEqual方法只是帮我们写了断言条件
        //第一处的流程分析
        //①调用predicateString方法,参数是“Hello”和Lambda表达式生成的接口实例
        //②调用接口实例的test方法,就是item -> item.equals("test")
        //  item就是“Hello”
        //③返回断言结果false

        //第二处的流程分析
        //①调用静态方法isEqual("test"),生成接口实例
        //②调用test方法,参数是“Hello”
        //③test的实现是isEqual,返回结果false
    }

    public boolean predicateString(String str, Predicate<String> predicate) {
        return predicate.test(str);
    }
}

Supplier

返回一个结果即可,无参数

/**
 *返回一个结果
 */
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     */
    T get();
}

总结以上接口

函数式接口作用
Consumer接受一个T类型的参数,不返回结果
Function<T,R>接受一个T的参数,返回一个R类型的结果
BiFunction<T, U, R>接受T和R类型的两个参数,返回一个R结果
Predicate接受一个T的参数,返回一个Boolean值
Supplier不接受参数,返回一个T类型的结果

以上就是西红柿总结的函数式接口的简略内容。