美文网首页
Java中的函数式编程

Java中的函数式编程

作者: 掩流年 | 来源:发表于2020-01-21 23:04 被阅读0次

自Java8以来,改动最大的就是添加了lambda表达式新特性。它最大的作用就是保证我们代码的简洁性,提高了可读性。但是在函数式中,也充斥着一些语法糖,我们只有理解这些所谓魔法背后的原理,才能更好的运用这些新特性,快速完成工作,节约我们的生命。

简述函数式

函数式接口,即只有一个声明方法的接口。我们知道的是在Java8之前,如果我们要实现一个筛选功能的接口,可以使用一个传递匿名内部类的方式,如下例子所示:
假设我们定义了一个接口去实现筛选的功能

@FunctionalInterface
interface MyFunction {
    boolean test(Apples apple);
}

在实现的类中使用了一个匿名内部类

    public List<Apples> filterApple(List<Apples> applesList) {
        List<Apples> targetApples = new ArrayList<>();
        findApple(applesList, new MyFunction() {
            @Override
            public boolean test(Apples apple) {
                return apple.getWeight() > 100;
            }
        });
        return targetApples;
    }

    private List<Apples> findApple(List<Apples> applesList, MyFunction myFunction) {
        List<Apples> appleList = new ArrayList<>();
        for (Apples apple : applesList) {
            if (myFunction.test(apple)) {
                appleList.add(apple);
            }
        }
        return appleList;
    }

可以看到我们在使用自定义接口的时候,在之上添加了一个注解,即@FunctionalInterface函数式接口,它没有任何实际意义。它仅仅是表明了这个接口为函数式接口。
函数式接口的明确定义是,只有一个抽象方法的接口。它还有一些其他特性:

  • 可以包含多个static和default方法。
  • 可以从Object中继承的抽象方法。
  • @FunctionalInterface作为可选可不选。
    针对以上特性来说,我们可以简化之前的代码:
  public List<Apples> filterApple(List<Apples> applesList) {
        List<Apples> targetApples = new ArrayList<>();
        findApple(applesList, apple -> apple.getWeight() > 100);
        return targetApples;
    }

可以看到,原来臃肿的代码,瞬间变得清爽了许多。这里解释下lambda这个神奇的魔法。在findApple()这个方法中,传入的第二个参数使用了lambda表达式,因为MyFunction中只有一个抽象方法,所以它很自然的匹配到了其中的 test方法,apple参数即方法的入参,符合方法签名,返回参数为boolean型,也符合函数签名。所以这时候其中的魔法也就不言而喻了。
另外,如果我们打开lambda的字节码,可以看到它背后原理

private static synthetic lambda$filterApple$0(Lcom/wzn/lambda/Apples;)Z

可以看到,函数式执行的方式,实际上是自己生成了一个方法。这个方法是static的,然后还有一个关键词是synthetic,它表示JVM合成的方式。如果有兴趣,可以打开类Modifier ,来查看其中的控制访问符和关键字。
另外还有函数式接口的调用使用的是INVOKEDYNAMIC指令调用,它的含义是动态调用,说明了lambda的调用方式是运行时调用。

但是,这时候又有了新的问题,为什么很多时候我们没有自己写过函数式接口,但是我们却经常在使用函数式代码呢?
这时候,我们应该能想到的是,肯定有人在背后为我们做了这件事情,以至于,我们可以直接使用它,而不必在重复的去定义它。

四大函数式接口

Consumer Function Predicate Supplier
void accept(T t); R apply(T t); boolean test(T t); T get();

依据之上表格中的函数接口,在Java中组合出来了很多的函数式接口供我们使用。
可以看到,在Consumer中,接收了一个泛型参数,不返回值。使用匿名内部类我们可以写这样的代码

  new Consumer<Apples>(){
            @Override
            public void accept(Apples o) {
                System.out.println("123");
            }
        };

使用lambda表达式我们可以这样写代码

Consumer<Apples> consumer = (apples) -> System.out.print(apples);

其他的四大函数式接口的表达方式都可以有类似的使用方式。

函数式接口的一些特性

如果写之下的代码看起来是一个正常的流程,但实际上它是有误的。

//错误的代码
      int i =0;
        Consumer<Apples> consumer = (apples) -> i++;

在lambda表达式中的变量实际上是变量的拷贝。
在idea中,如果鼠标悬浮你会看到如下的提示,它表示表达式应当被声明为final。


idea的提示

可以根据idea的提示修改代码为

        AtomicInteger i = new AtomicInteger();
        Consumer<Apples> consumer = (apples) -> i.getAndIncrement();

这和多线程的可见性有相同的特性。

方法引用

有时候我们会发现,当lambda表达式中{}的内容过长,会对可读性有很大的影响,比如:

    applesList.forEach((apple -> {
            System.out.println(apple.getColor());
            System.out.println(apple.getWeight());
        }));

当在大括号中有更多的业务逻辑其实在用Lambda是对我们程序可读性的一种伤害,这时候就得使用提供公有方法的重构手法,把中间的逻辑提取出来。

 applesList.forEach((apple -> {
            printFields(apple);
        }));

之上的代码同样等价于

applesList.forEach((Apples::printFields))

这样是不是更加简洁清晰了,我们都可以看到,在foreach中的方法实现使用了Consumer参数

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

Consumer接口的方法是传递一个参数,不返回值。还有个问题是

   private void printFields() {
        System.out.println(this.getColor());
        System.out.println(this.getWeight());
    }

但这个方法并没有传入参数?实际上,在Java中所有的实例方法都隐藏了一个this参数。
上面的代码即

   private void printFields(Apples this) {
        System.out.println(this.getColor());
        System.out.println(this.getWeight());
    }

方法引用的核心是方法可以自动被转化为函数式接口。有了它,我们的代码将更加的简洁。

相关文章

  • Java8 函数式接口 学习笔记

    一, 函数式编程 java中的函数式编程体现就是Lambda和方法引用: Lambda Lambda除了简洁之外,...

  • 2018-09-21

    java学习笔记(二) 前一篇简单的介绍了Java8函数式编程,这篇还将继续函数式编程之旅。 流 在Java程序中...

  • Java与Kotlin的区别

    Java:面向对象编程,顶层是类ClassKotlin:函数式编程,顶层是函数,顶层属性,类存在于函数中在java...

  • Java8 函数式编程

    Java 8 函数式编程 本次主题主要介绍什么是函数式编程,其主要特点, 以及它在 Java8 中是怎么体现的.函...

  • 一起来学Java8(一)——函数式编程

    在这篇文章中,我们将了解到在Java8下如何进行函数式编程。 函数式编程 所谓的函数式编程就是把函数名字当做值进行...

  • java之Lambda函数式编程最佳应用举例,链式语法「真干货来

    --java之Lambda函数式编程---- 背景 java 8 Lambda函数式编程,像阿里、腾讯这样的大互联...

  • Java中的 函数式 编程范式(翻译+汇总)

    Java中的 函数式 编程范式(翻译+汇总) 函数式编程范式 略讲 每种编程语言均有自己的语法和结构规范定义。 这...

  • Java lambda表达式

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

  • Java 函数式编程

    从历史上看,用 Java 进行函数式编程并不容易,甚至一些函数式编程在 Java 中是不可能实现的。在 Java...

  • Scala编程:函数式编程

    函数式编程 引言 Scala中的函数是Java中完全没有的概念。因为Java是完全面向对象的编程语言,没有任何面向...

网友评论

      本文标题:Java中的函数式编程

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