当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Java Functional Programming用法及代码示例


到目前为止,Java 支持命令式编程风格和面向对象编程风格。 java 添加的下一件大事是 Java 已开始通过其 Java 8 版本支持函数式编程风格。在本文中,我们将讨论 Java 8 中的函数式编程。
什么是函数式编程?
它是一种声明式编程风格,而不是命令式编程风格。这种编程风格的基本目标是与传统的编码风格相比,使代码更简洁、更简单、更可预测、更容易测试。函数式编程涉及某些关键概念,例如纯函数,不可变状态、assignment-less编程等。
函数式编程与纯函数式编程:
纯函数式编程语言本质上不允许任何可变性,而函数式语言提供高阶函数,但通常允许可变性,但这样做的风险是我们无法做正确的事情,这给我们带来了负担,而不是保护我们。所以,一般来说,我们可以说,如果一种语言提供了高阶函数,那么它就是函数式语言,如果一种语言除了高阶函数之外还达到了限制可变性的程度,那么它就变成了纯粹的函数式语言。 Java 是一种函数式语言,类似于 Haskell 是一种纯函数式编程语言。
我们先来了解一下其中的几个概念函数式编程:

  • 高阶函数:在函数式编程中,函数被视为first-class公民。也就是说,到目前为止,在传统的编码风格中,我们可以使用对象执行以下操作。
    1. 我们可以通过对象到一个函数。
    2. 我们可以创造对象函数内。
    3. 我们可以返回对象来自一个函数。
    4. 我们可以通过一个函数到一个函数。
    5. 我们可以创建一个函数函数内。
    6. 我们可以返回一个函数来自一个函数。
  • 纯函数如果一个函数对于相同的参数值总是返回相同的结果,并且没有修改参数(或全局变量)或输出某些内容等副作用,则该函数称为纯函数。
  • 拉姆达表达式: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");
    }
}
输出:
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 表达式。因此,在这种情况下,我们可以看到一个函数已被评估并分配给可运行的接口,并且这里该函数已被视为first-class公民。
将一些函数从 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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 将数字加倍,称为关闭。请记住,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<Integer> 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<Integer> 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;
    }
}

上面的代码给出了一个编译时错误在封闭范围内定义的局部变量因子必须是最终的或有效的最终的。

该程序的时间复杂度为 O(n),其中 n 是列表中元素的数量。这是因为程序会迭代列表中的每个元素一次以应用过滤和映射操作。

空间复杂度这个程序的O(1),因为它在内存中只存储输入列表和几个整数,并且内存使用量不依赖于输入列表的大小。

这意味着这里的变量因子默认情况下被视为最终版本。简而言之,我们永远不应该尝试改变纯函数内使用的任何变量。这样做会违反纯函数规则,该规则规定纯函数既不应该改变任何东西,也不应该依赖于任何改变的东西。改变任何闭包(此处为因子)都被认为是不好的闭包,因为闭包本质上总是不可变的。



相关用法


注:本文由纯净天空筛选整理自asadaliasad大神的英文原创作品 Functional Programming in Java with Examples。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。