📜  Java中的函数式编程与示例

📅  最后修改于: 2022-05-13 01:55:11.268000             🧑  作者: Mango

Java中的函数式编程与示例

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

  • 高阶函数:在函数式编程中,函数被视为一等公民。也就是说,到目前为止,在传统的编码风格中,我们可以用对象做下面的事情。
    1. 我们可以将对象传递给函数。
    2. 我们可以在函数中创建对象
    3. 我们可以从函数返回对象
    4. 我们可以将一个函数传递给一个函数。
    5. 我们可以在函数
    6. 我们可以从一个函数
  • 纯函数如果一个函数总是为相同的参数值返回相同的结果,并且它没有修改参数(或全局变量)或输出某些东西等副作用,则该函数称为纯函数。
  • 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;
    }
}

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

这意味着在这里,变量因子默认被认为是最终的。简而言之,我们不应该尝试改变纯函数内部使用的任何变量。这样做会违反纯函数规则,即纯函数既不应该改变任何东西,也不应该依赖于任何改变的东西。改变任何闭包(这里是因子)被认为是一个坏闭包,因为闭包本质上总是不可变的。