美文网首页
函数式接口

函数式接口

作者: shz_Minato | 来源:发表于2019-01-12 19:17 被阅读5次

函数式接口

一、函数式接口的定义

  函数式接口:函数式接口中有且仅有一个抽象方法,这个抽象方法的意义在于表达某种行为。

  以下情况是满足函数式接口定义的:
  1. 接口中可以有多个默认实现,但是默认实现需要用default关键字显示标注。

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

  2. 接口可以包含过个静态方法。

@FunctionalInterface
public interface MyInterface {
    String getName();

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

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

@FunctionalInterface
public interface MyInterface {
    String getName();

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

二、函数式接口的表示

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

  函数式接口与Lambda表达式有着天然的联系,Lambda表达式的基本结构是

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

  1. 如果参数可以通过类型推导出来,那么参数类型可以不写。(可以先不写试试)

  2. 如果参数只有一个,可以将括号省略。

  3. body如果只有一行代码,可以省略花括号。注意的是一行代码的表示有表达式和语句之分,需要配合return、分号、花括号的情况。
  4. 为了便于理解和说明,这里将Lambda表达式的按着其参数和返回值类型进行描述。

  (int a,int b)->a+b 的类型为(int,int)->int

  (int a,int b)->println(a+b) 的类型为(int,int)->Unit(不返回值)

三、JDK8中的常见的函数式接口

  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>
  void accept(T t);
  接受一个参数,不返回结果

  Function<T,R>
  R apply(T t);
  接受一个类型的参数,返回一个结果
  可以通过默认的compose和andThen方法复合函数

  BiFunction<T, U, R>
  R apply(T t, U u);
  接受两个类型的参数,返回一个结果

  Predicate<T>
  boolean test(T t);
  接受一个参数,返回一个Boolean值

  Supplier<T>
  T get()
  不接受参数返回一个结果

五、小结

函数式的参数代表的是一种行为,开发者传递的是某种操作

相关文章

  • java基础-day23-函数式接口和Stream流

    函数式接口和Stream 1. 函数式接口 1.1 函数式接口概述 1.2 常用函数式接口 1.3 比较器函数式接...

  • 2020-07-04【函数式接口】

    函数式接口概述 函数式接口作为方法的参数 函数式接口作为方法的返回值 常见的函数式接口 Supplier接口 Co...

  • 12.函数式接口

    主要内容 自定义函数式接口 函数式编程 常用函数式接口 第一章 函数式接口 1.1 概念 函数式接口在Java中是...

  • 函数式接口

    函数式接口 一、函数式接口的定义   函数式接口:函数式接口中有且仅有一个抽象方法,这个抽象方法的意义在于表达某种...

  • 测验:函数式接口

    下面哪些接口是函数式接口? 答案:只有Adder是函数式接口。SmartAdder不是函数式接口,因为它定义了两个...

  • Java8系列:神奇的函数式接口

    01 函数式接口是什么? 有且只有一个抽象方法的接口被称为函数式接口,函数式接口适用于函数式编程的场景,Lambd...

  • 函数式接口和Lambda表达式深入理解

    0x00 函数式接口 前面讲了一下函数式接口,不过可能只是讲了个大概,大致讲了一下什么是函数式接口 函数式接口就是...

  • java8函数式接口

    一、函数式接口 1、函数式接口在java中是指:有且仅有一个抽象方法的接口,函数式接口,即适用于函数式编程场景的接...

  • Java 8 知多少

    一、函数式接口 函数式接口的定义: 函数式接口(Functional Interface)就是一个有且仅有一个抽象...

  • Java lambda表达式

    1. Java函数式接口 Java实现函数式编程的方式是函数式接口(functional interface),函...

网友评论

      本文标题:函数式接口

      本文链接:https://www.haomeiwen.com/subject/sxicdqtx.html