Java函数式编程

时间:2020-01-09 14:11:10  来源:igfitidea点击:

术语Java函数编程是指Java中的函数编程。从历史上看,用Java编写函数式编程并非易事,甚至在Java中甚至不可能实现函数式编程的某些方面。在Java 8中,Oracle致力于使函数式编程更容易,并且在一定程度上取得了成功。在本Java函数式编程教程中,我将介绍函数式编程的基础知识,以及Java中可能包含的哪些部分。

函数式编程基础

函数式编程包含以下关键概念:

  • 用作第一类对象
  • 纯函数
  • 高阶函数

纯函数式编程也有一组规则要遵循:

  • 无状态
  • 没有副作用
  • 不变变量
  • 优先于循环递归

这些概念和规则将在本教程的其余部分中进行解释。

即使我们始终不遵循所有这些规则,我们仍然可以从应用程序中的函数编程思想中受益。正如我们将看到的,函数式编程并不是解决每个问题的正确工具。特别是"无副作用"的想法使它很难例如写入数据库(这是副作用)。我们需要了解函数式编程最擅长解决哪些问题,而哪些不是。

用作第一类对象

在函数式编程范例中,函数是语言中的第一类对象。这意味着我们可以创建函数的"实例",就像变量引用该函数实例一样,就像对String,Map或者任何其他对象的引用一样。函数也可以作为参数传递给其他函数。

在Java中,方法不是一流的对象。我们得到的最接近的是Java Lambda表达式。我不会在这里介绍Java Lambda表达式,因为我在Java Lambda表达式教程的文本和视频中都对它们进行了介绍。

纯函数

在以下情况下,函数是纯函数:

  • 该函数的执行没有副作用。
  • 函数的返回值仅取决于传递给函数的输入参数。

这是Java中纯函数(方法)的示例:

public class ObjectWithPureFunction{

    public int sum(int a, int b) {
        return a + b;
    }
}

注意sum()函数的返回值仅取决于输入参数。还要注意,sum()没有副作用,这意味着它不会在函数的任何地方修改任何状态(变量)。

相反,这是一个非纯函数的示例:

public class ObjectWithNonPureFunction{
    private int value = 0;

    public int add(int nextValue) {
        this.value += nextValue;
        return this.value;
    }
}

请注意,方法add()如何使用成员变量来计算其返回值,并且还修改了value成员变量的状态,因此具有副作用。

高阶函数

如果至少满足以下条件之一,则该函数为高阶函数:

  • 该函数将一个或者多个函数作为参数。
  • 该函数返回另一个函数作为结果。

在Java中,最接近高阶函数的函数(方法)将一个或者多个lambda表达式作为参数,然后返回另一个lambda表达式。这是Java中高阶函数的示例:

public class HigherOrderFunctionClass {

    public <T> IFactory<T> createFactory(IProducer<T> producer, IConfigurator<T> configurator) {
        return () -> {
           T instance = producer.produce();
           configurator.configure(instance);
           return instance;
        }
    }
}

注意createFactory()方法如何返回一个lambda表达式作为结果。这是高阶函数的第一个条件。

还要注意,createFactory()方法将两个实例作为参数,它们都是接口的实现(IProducer和IConfigurator)。 Java lambda表达式必须实现函数接口,还记得吗?

想象一下接口看起来像这样:

public interface IFactory<T> {
   T create();
}
public interface IProducer<T> {
   T produce();
}
public interface IConfigurator<T> {
   void configure(T t);
}

如我们所见,所有这些接口都是函数接口。因此它们可以由Java lambda表达式实现,因此createFactory()方法是一个高阶函数。

关于高阶函数的文本中的不同示例还涵盖了高阶函数

没有状态

如本教程开头所提到的,函数式编程范例的规则是没有状态。 "无状态"通常是指函数外部的无状态。一个函数可能具有内部包含临时状态的局部变量,但是该函数不能引用该函数所属的类或者对象的任何成员变量。

这是不使用外部状态的函数的示例:

public class Calculator {
    public int sum(int a, int b) {
       return a + b;
    }
}

相反,这是一个使用外部状态的函数的示例:

public class Calculator {
    private int initVal = 5;
    public int sum(int a) {
       return initVal + a;
    }
}

此函数明显违反了无状态规则。

没有副作用

函数式编程范例中的另一个规则是没有副作用。这意味着函数无法更改函数以外的任何状态。在函数外部更改状态称为副作用。

函数外部的状态既指函数中的类或者对象的成员变量,也指函数内部参数中的成员变量,或者指外部系统(如文件系统或者数据库)中的状态。

不变变量

函数编程范例中的第三条规则是不可变变量。不可变的变量使避免副作用变得更加容易。

优先于循环递归

函数式编程范例中的第四条规则是在循环中优先使用递归。递归使用函数调用来实现循环,因此代码变得更具函数性。

循环的另一种替代方法是Java Streams API。此API在函数上受到启发。

函数介面

Java中的函数接口是只有一种抽象方法的接口。抽象方法是指仅一种未实现的方法。一个接口可以有多种方法,例如默认方法和静态方法,都有实现,但是只要接口只有一个未实现的方法,则该接口被视为函数接口。

这是函数接口的示例:

public interface MyInterface {
    public void run();
}

这是具有默认方法和静态方法的函数接口的另一个示例:

public interface MyInterface2 {
    public void run();

    public default void doIt() {
        System.out.println("doing it");
    }

    public static void doItStatically() {
        System.out.println("doing it statically");
    }
}

注意实现的两种方法。这仍然是一个函数性的接口,因为仅未实现" run()"(抽象)。但是,如果有更多方法没有实现,则该接口将不再是函数性接口,因此无法由Java lambda表达式实现。