美文网首页
函数式接口

函数式接口

作者: 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()
      不接受参数返回一个结果

    五、小结

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

    相关文章

      网友评论

          本文标题:函数式接口

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