Java中的函数式编程与示例
到目前为止, Java支持命令式编程风格和面向对象编程风格。添加JavaJava开始支持其Java 8 版本的函数式编程风格。在本文中,我们将讨论Java 8 中的函数式编程。
什么是函数式编程?
它是一种声明式的编程风格,而不是命令式的。与传统的编码风格相比,这种编程风格的基本目标是使代码更简洁、更简单、更可预测和更容易测试。函数式编程处理某些关键概念,例如纯函数、不可变状态、无赋值编程等。
函数式编程与纯函数式编程:
纯函数式编程语言本质上不允许任何可变性,而函数式风格的语言提供高阶函数,但通常允许可变性,但冒着我们未能做正确事情的风险,这给我们带来了负担,而不是保护我们。所以,一般来说,如果一种语言提供了高阶函数,它就是函数式语言,如果一种语言除了高阶函数之外还达到了限制可变性的程度,那么它就变成了纯粹的函数式语言。 Java是一种函数式语言,而像Haskell这样的语言是一种纯粹的函数式编程语言。
让我们了解函数式编程中的一些概念:
- 高阶函数:在函数式编程中,函数被视为一等公民。也就是说,到目前为止,在传统的编码风格中,我们可以用对象做下面的事情。
- 我们可以将对象传递给函数。
- 我们可以在函数中创建对象。
- 我们可以从函数返回对象。
- 我们可以将一个函数传递给一个函数。
- 我们可以在函数。
- 我们可以从一个函数。
- 纯函数:如果一个函数总是为相同的参数值返回相同的结果,并且它没有修改参数(或全局变量)或输出某些东西等副作用,则该函数称为纯函数。
- Lambda 表达式: Lambda 表达式是一种匿名方法,具有最低限度的可变性,它只有一个参数列表和一个主体。返回类型总是根据上下文推断出来的。另外,请注意,Lambda 表达式与函数式接口并行工作。 lambda 表达式的语法是:
(parameter) -> body
- 在其简单形式中,lambda 可以表示为以逗号分隔的参数列表、 –>符号和主体。
如何在Java中实现函数式编程?
java
// Java program to demonstrate
// anonymous method
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
// Defining an anonymous method
Runnable r = new Runnable() {
public void run()
{
System.out.println(
"Running in Runnable thread");
}
};
r.run();
System.out.println(
"Running in main thread");
}
}
java
// Java 8 program to demonstrate
// a lambda expression
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
Runnable r
= ()
-> System.out.println(
"Running in Runnable thread");
r.run();
System.out.println(
"Running in main thread");
}
}
java
// Java program to demonstrate an
// external iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// External iterator, for Each loop
for (Integer n : numbers) {
System.out.print(n + " ");
}
}
}
java
// Java program to demonstrate an
// external iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// External iterator
for (int i = 0; i < numbers.size(); i++) {
System.out.print(numbers.get(i) + " ");
}
}
}
java
// Java 8 program to demonstrate
// an internal iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// Internal iterator
numbers.forEach(number
-> System.out.print(
number + " "));
}
}
java
// Java program to find the sum
// using imperative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int result = 0;
for (Integer n : numbers) {
if (n % 2 == 0) {
result += n * 2;
}
}
System.out.println(result);
}
}
java
// Java program to find the sum
// using declarative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * 2)
.sum());
}
}
java
// Java program to demonstrate an
// declarative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int factor = 2;
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * factor)
.sum());
}
}
java
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int factor = 2;
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * factor)
.sum());
factor = 3;
}
}
Running in Runnable thread
Running in main thread
如果我们看一下run()方法,我们用 Runnable 包装了它。我们在Java 7 之前以这种方式初始化这个方法。同样的程序可以在Java 8 中重写为:
Java
// Java 8 program to demonstrate
// a lambda expression
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
Runnable r
= ()
-> System.out.println(
"Running in Runnable thread");
r.run();
System.out.println(
"Running in main thread");
}
}
Running in Runnable thread
Running in main thread
现在,上面的代码已经被转换为Lambda 表达式,而不是匿名方法。在这里,我们评估了一个没有任何名称的函数,该函数是一个 lambda 表达式。所以,在这种情况下,我们可以看到一个函数已经被评估并分配给一个可运行的接口,这里这个函数被视为一等公民。
将一些函数从Java 7 重构为Java 8:
到目前为止,我们已经多次使用循环和迭代器,直到Java 7,如下所示:
Java
// Java program to demonstrate an
// external iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// External iterator, for Each loop
for (Integer n : numbers) {
System.out.print(n + " ");
}
}
}
11 22 33 44 55 66 77 88 99 100
上面是Java中 forEach 循环的一个示例,它是一种外部迭代器,下面是另一个示例和另一种形式的外部迭代器。
Java
// Java program to demonstrate an
// external iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// External iterator
for (int i = 0; i < numbers.size(); i++) {
System.out.print(numbers.get(i) + " ");
}
}
}
11 22 33 44 55 66 77 88 99 100
我们可以将上面的外部迭代器示例转换为Java 8 中引入的内部迭代器,如下所示:
Java
// Java 8 program to demonstrate
// an internal iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// Internal iterator
numbers.forEach(number
-> System.out.print(
number + " "));
}
}
11 22 33 44 55 66 77 88 99 100
在这里,功能界面起着主要作用。无论何时需要单个抽象方法接口,我们都可以非常轻松地传递 lambda 表达式。上面的代码可以更简化和改进如下:
numbers.forEach(System.out::println);
命令式与声明式编程:
编程的函数式风格是声明式编程。在命令式编码风格中,我们定义了要做什么以及如何去做。然而,在声明式编码风格中,我们只指定要做什么。让我们通过一个例子来理解这一点。给定一个数字列表,让我们使用命令式和声明式编码风格从列表中找出双数的总和。
Java
// Java program to find the sum
// using imperative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int result = 0;
for (Integer n : numbers) {
if (n % 2 == 0) {
result += n * 2;
}
}
System.out.println(result);
}
}
640
上面代码的第一个问题是我们一次又一次地改变变量结果。因此,可变性是命令式编码风格中最大的问题之一。命令式风格的第二个问题是,我们不仅要告诉我们要做什么,还要告诉如何进行处理。现在让我们以声明式的方式重新编写上面的代码。
Java
// Java program to find the sum
// using declarative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * 2)
.sum());
}
}
640
从上面的代码中,我们没有改变任何变量。相反,我们正在将数据从一个函数转换为另一个函数。这是命令式和声明式之间的另一个区别。不仅如此,在上面的声明式代码中,每个函数都是纯函数,纯函数没有副作用。
在上面的例子中,我们用因子 2 将数字加倍,这称为Closure 。请记住,lambda 是无状态的,闭包具有不可变状态。这意味着在任何情况下,闭包都不能是可变的。让我们通过一个例子来理解它。在这里,我们将声明一个变量因子,并将在如下函数中使用。
Java
// Java program to demonstrate an
// declarative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int factor = 2;
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * factor)
.sum());
}
}
640
上面的代码运行良好,但现在让我们尝试在使用后对其进行变异,看看会发生什么:
Java
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int factor = 2;
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * factor)
.sum());
factor = 3;
}
}
上面的代码给出了一个编译时错误,说明在封闭范围中定义的局部变量因子必须是最终的或有效的最终。
这意味着在这里,变量因子默认被认为是最终的。简而言之,我们不应该尝试改变纯函数内部使用的任何变量。这样做会违反纯函数规则,即纯函数既不应该改变任何东西,也不应该依赖于任何改变的东西。改变任何闭包(这里是因子)被认为是一个坏闭包,因为闭包本质上总是不可变的。