美文网首页
深入函数式接口与方法引用

深入函数式接口与方法引用

作者: pr0metheus | 来源:发表于2018-03-28 17:27 被阅读0次

    Java Lambda根源起始于函数式接口,对函数式接口的理解直接影响到对Lambda的认知

    如果一个接口中的抽象方法覆盖了Object中的方法,那么该抽象方法不纳入在这个接口所拥有的抽象方法数量里,如果还不理解那么参考如下代码:

    /*
    这是允许的,因为实现这个接口的子类一定是间接或者直接继承
    Object,所以对于toString方法而言并不需要子类自己去实现Object已
    帮我们实现完毕了,而对于test方法子类需要自己去实现。所以仍然认
    为该接口只有一个抽象方法,符合函数式接口的定义。
    */
    @FunctionalInterface
    public interface MyInterface {
        void test();
        String toString();
    }
    
    //编译器报错:多个未覆盖的抽象方法在接口中被发现
    @FunctionalInterface
    public interface MyInterface {
        void test();
        String mytoString();
    }
    

    代码对比,jdk1.8之前的代码如下:

    @FunctionalInterface
    interface MyInterface {
        void test();
        String toString();
    }
    
    public class Test2 {
    
        public void printf(MyInterface myInterface) {
            System.out.println(1);
            myInterface.test();
            System.out.println(2);
        }
    
        public static void main(String[] args) {
            Test2 test2 = new Test2();
            test2.printf(new MyInterface() { //关键看此处
                @Override
                public void test() {
                    System.out.println("test");
                }
            });
        }
    }
    

    jdk1.8的代码如下:

    
    @FunctionalInterface
    interface MyInterface {
        void test();
        String toString();
    }
    
    public class Test2 {
    
        public void printf(MyInterface myInterface) {
            System.out.println(1);
            myInterface.test();
            System.out.println(2);
        }
    
        public static void main(String[] args) {
            Test2 test2 = new Test2();
            test2.printf(() -> { //关键看此处
                System.out.println("test");
            });
        }
    }
    

    知识点:

    • 首先知道一点函数式接口可以用lambda表达式来创建它的实例
    • 对于函数式接口的抽象方法如果它不需要接受参数的话,那么lambda表达式的第一部分要写成()

    分析上面jdk1.8的代码:

    1. test2.printf()需要接受MyInterface函数式接口的实现类
    2. 既然是函数式接口的实现类那么可以用lambda表达式来创建
    3. 怎么创建呢?由函数式接口的特性来决定,因为函数式接口的特点就是只有一个抽象方法,所以我们只需要能表达出该方法的实现就可以了。
    4. () -> {System.out.println("test")} 的解释:

    ()对应与函数式接口中唯一的抽象方法test的参数内容,因为test方法不需要参数所以lambda表达式的参数部分就写成(), 假使test参数内容是int a,那么lambda表达式就要写成(int a) -> {System.out.println("test")};

    1. ->作为分隔符没啥好说的,{System.out.println("test")}就看作是test方法的执行体或内容,因为函数式接口只有一个抽象方法,所以它能自动匹配上认为lambda表达式就是对那个抽象方法的实现,因此方法名字也根本用不上。

    在看如下代码片段:

            MyInterface myInterface = () -> System.out.println("hello world");
            System.out.println(myInterface.getClass());
            System.out.println(myInterface.getClass().getSuperclass());
            System.out.println(myInterface.getClass().getInterfaces()[0]);
    

    上述代码能够执行,并且在控制台打印出:

    class com.shengsiyuan.jdk8.Test2$$Lambda$2/1096979270
    class java.lang.Object
    interface com.shengsiyuan.jdk8.MyInterface

    也就是说 () -> System.out.println("hello world");就是MyInterface的实现类罢了,它的具体实现类的名字是有java给我们定义的,它的父类是Object类,它的实现接口是MyInterface

    好再看如下代码片段:

        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
            list.forEach(new Consumer<Integer>() {
                @Override
                public void accept(Integer integer) {
                    System.out.println(integer);
                }
            });
        }
    

    我们都知道jdk1.8之前是没有forEach方法的,查看源代码发现该方法位于接口Interable中

    public interface Iterable<T> {
    
        Iterator<T> iterator();
    
        default void forEach(Consumer<? super T> action) {
            Objects.requireNonNull(action);
            for (T t : this) {
                action.accept(t);
            }
        }
        ...
    }
    

    至此说明一个问题,jdk1.8开始接口中也可以有具体的方法实现了不过都需要在方法之前加上修饰符default称为default method即默认方法。类似于抽象类中的具体方法可以被子类直接使用,而抽象方法需要子类自己去实现。

    上述代码中也可以改为:

        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
            list.forEach(i -> System.out.println(i));  //因为Consumer也是函数式接口
        }
    

    Lambda表达式的作用:

    • Lambda表达式为java添加了缺失的函数式编程特性,使我们能将函数当作一等公民看待
    • 在将函数作为一等公民的语言中,Lambda表达式的类型是函数,但在java中Lambda表达式是对象,他们必须依附于一类特别的对象类型——函数式接口(Functional Interface)

    外部迭代:通过外部迭代器遍历目标集合
    内部迭代:通过集合本身以及一个函数式接口将元素一个一个取出来,List.forEach(Consumer consumer)就是内部迭代

    list.forEach代码的演变过程:

    list.forEach((Integer i) -> System.out.println(i)); //手工加上类型
    list.forEach(i -> System.out.println(i)); //依靠编译器来自动推算类型
    list.forEach(System.out::println); //函数式接口还可以通过方法引用来创建实例(之后介绍)

    相关文章

      网友评论

          本文标题:深入函数式接口与方法引用

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