Java函数式编程
术语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表达式实现。