>  기사  >  Java  >  Java 함수형 프로그래밍에 대한 심층 분석

Java 함수형 프로그래밍에 대한 심층 분석

WBOY
WBOY앞으로
2022-11-10 16:17:221291검색

이 기사에서는 함수형 프로그래밍에 대한 관련 내용을 주로 소개하는 java에 대한 관련 지식을 제공합니다. Java는 처음에는 함수형 프로그래밍을 지원하지 않았지만, 큰 버전의 java8에서는 함수형 프로그래밍을 지원하기 위해 Java가 많이 도입되었습니다. 중요한 기능을 함께 살펴보겠습니다. 모두에게 도움이 되기를 바랍니다.

Java 함수형 프로그래밍에 대한 심층 분석

추천 학습: "java 비디오 튜토리얼"

Java는 처음에 함수형 프로그래밍을 지원하지 않았습니다. 생각해 보면 이해하기 쉽습니다. 왜냐하면 Java에서는 클래스가 일급 시민이기 때문입니다. Java에서 프로그래밍을 구현하는 것은 쉬운 작업이 아니지만 어렵지만 결과는 이미 알고 있습니다. Java 8의 주요 버전에서 함수형 프로그래밍을 지원하기 위해 Java는 앞서 논의한 많은 중요한 기능을 도입했습니다. 이전 몇 장에서는 Stream API의 Lambda 표현식과 다양한 스트림 작업에 대해 배웠습니다. 오늘 기사에서는 Java에 내장된 기능적 인터페이스를 정리하겠습니다.

이 문서의 개요는 다음과 같습니다.

Java는 함수, 공급업체 등 Stream의 다양한 작업 메서드의 매개 변수 또는 반환 값 유형은 종종 이러한 내장 기능 인터페이스입니다. <code>FunctionSupplier 等等,Stream 中各种操作方法的参数或者是返回值类型往往就是这些内置的函数式接口。

比如 Stream 中 map 操作方法的参数类型就是 Function

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

那为什么我们在平时使用 Stream 操作的 map 方法时,从来没有见过声明这个类型的参数呢?大家可以回顾一下我们 Stream API 操作那一篇文章里使用 map 方法的例子,比如下面这个通过 map 方法把流中的每个元素转换成大写的例子。

List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();

Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());

map 方法的参数直接是一个 Lambada 表达式:

(value) -> value.toUpperCase()

这个Lambda 表达式就是Function接口的实现。

函数式接口的载体通常是 Lambda 表达式,通过 Lambda 表达式,编译器会根据 Lambda 表达式的参数和返回值推断出其实现的是哪个函数式接口。使用 Lambda 表达式实现接口,我们不必像匿名内部类那样--指明类要实现的接口,所以像 Stream 操作中虽然参数或者返回值类型很多都是 Java 的内置函数式接口,但是我们并没有显示的使用匿名类实现它们。

虽然Lambda 表达式使用起来很方便,不过这也从侧面造成了咋一看到那些 Java 内置的函数式接口类型时,我们会有点迷惑“这货是啥?这货又是啥?”的感觉。

下面我们先说一下函数式编程、Java 的函数式接口、Lambda 为什么只能实现函数式接口这几个问题,把这些东西搞清楚了再梳理 Java 内置提供了哪些函数式接口。

函数式编程

函数式编程中包含以下两个关键的概念:

  • 函数是第一等公民
  • 函数要满足一下约束
    • 函数的返回值仅取决于传递给函数的输入参数。
    • 函数的执行没有副作用。

即使我们在写程序的时候没有一直遵循所有这些规则,但仍然可以从使用函数式编程思想编写程序中获益良多。

接下来,我们来看一下这两个关键概念再 Java 函数编程中的落地。

函数是一等公民

在函数式编程范式中,函数是语言中的第一等公民。这意味着可以创建函数的“实例”,对函数实例的变量引用,就像对字符串、Map 或任何其他对象的引用一样。函数也可以作为参数传递给其他函数。

在 Java 中,函数显然不是第一等公民,类才是。所以 Java 才引入 Lambda 表达式,这个语法糖从表现层上让 Java 拥有了函数,让函数可以作为变量的引用、方法的参数等等。为啥说是从表现层呢?因为实际上在编译的时候 Java 编译器还是会把 Lambda 表达式编译成类。

纯函数

函数编程中,有个纯函数(Pure Function)的概念,如果一个函数满足以下条件,才是纯函数:

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

下面是一个 Java 中的纯函数(方法)示例

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

上面这个sum()方法的返回值仅取决于其输入参数,而且sum()

예를 들어 Stream의 map 연산 메소드의 매개변수 유형은 Function

public class ObjectWithNonPureFunction{    private int value = 0;    public int add(int nextValue) {        this.value += nextValue;        return this.value;
    }
}
그렇다면 일반적으로 Stream 연산의 map 메소드를 사용할 때 이런 유형의 매개변수가 선언된 것을 볼 수 없는 이유는 무엇일까요? 스트림의 각 요소를 대문자로 변환하기 위해 맵 메서드를 사용하는 아래 예제와 같이 Stream API 작업 문서에서 맵 메서드를 사용하는 예제를 검토할 수 있습니다. 🎜
public interface MyInterface {    public void run();
}
🎜map 메소드의 매개변수는 바로 Lambada 표현식입니다. 🎜
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");
    }
}
🎜이 Lambda 표현식은 Function 인터페이스의 구현입니다. 🎜🎜기능적 인터페이스의 전달자는 일반적으로 Lambda 표현식을 통해 컴파일러는 Lambda 표현식의 매개변수와 반환 값을 기반으로 구현하는 기능적 인터페이스를 추론합니다. Lambda 표현식을 사용하여 인터페이스를 구현하면 익명 내부 클래스처럼 클래스에서 구현할 인터페이스를 지정할 필요가 없습니다. 따라서 Stream 작업의 많은 매개 변수나 반환 값 유형은 Java의 내장 기능 인터페이스이지만 이를 지정하지 않습니다. 표시하고 익명 클래스를 사용하여 구현합니다. 🎜🎜람다 표현식은 사용하기 매우 편리하지만 Java에 내장된 기능적 인터페이스 유형을 보면 "이게 뭐야? 이게 뭐야?"라는 느낌이 들 때 약간 혼란스럽기도 합니다. 🎜🎜먼저 함수형 프로그래밍, Java의 함수형 인터페이스, 그리고 Lambda가 함수형 인터페이스만 구현할 수 있는 이유에 대해 이야기해 보겠습니다. 이러한 사항을 명확히 한 후 Java가 기본 제공하는 함수형 인터페이스가 무엇인지 정리하겠습니다. 🎜

함수형 프로그래밍

🎜함수형 프로그래밍에는 다음 두 가지 주요 개념이 포함됩니다. 🎜
  • 함수는 일급 시민입니다
  • 함수는 다음과 같습니다. 다음 제약 조건을 충족하세요.
    • 함수의 반환 값은 함수에 전달된 입력 매개 변수에만 의존합니다.
    • 함수 실행에는 부작용이 없습니다.
🎜 프로그램을 작성할 때 항상 이러한 규칙을 모두 따르지는 않더라도 함수형 프로그래밍 아이디어를 사용하여 프로그램을 작성하면 많은 이점을 얻을 수 있습니다. 🎜🎜다음으로 Java 함수형 프로그래밍에서 이 두 가지 핵심 개념의 구현을 살펴보겠습니다. 🎜

함수는 일급 시민입니다

🎜함수형 프로그래밍 패러다임에서 함수는 언어에서 일급 시민입니다. 이는 문자열, 맵 또는 기타 객체에 대한 참조와 마찬가지로 함수의 "인스턴스"와 함수 인스턴스에 대한 변수 참조를 생성할 수 있음을 의미합니다. 함수는 다른 함수에 인수로 전달될 수도 있습니다. 🎜🎜Java에서 함수는 분명히 일급 시민이 아니지만 클래스는 그렇습니다. 이것이 Java가 Lambda 표현식을 도입한 이유입니다. 이 구문 설탕을 사용하면 Java가 프레젠테이션 계층의 함수를 가질 수 있어 함수를 변수, 메서드 매개 변수 등에 대한 참조로 사용할 수 있습니다. 왜 프리젠테이션 레이어에서 왔다고 하나요? 실제로 Java 컴파일러는 컴파일 중에 Lambda 표현식을 클래스로 컴파일하기 때문입니다. 🎜

순수 함수

🎜함수형 프로그래밍에는 함수가 다음 조건을 만족하면 순수 함수라는 개념이 있습니다. 순수 함수: 🎜
  • 이 함수를 실행해도 부작용이 없습니다.
  • 함수의 반환 값은 함수에 전달된 입력 매개변수에만 의존합니다.
🎜다음은 Java🎜
@FunctionalInterface 
// 标明接口为函数式接口
public interface MyInterface {    public void run(); 
//抽象方法}
🎜의 순수 함수(메서드)의 예입니다. 위 sum() 메서드의 반환 값은 입력 매개변수에만 의존합니다. sum()에는 부작용이 없으며 어디에서나 함수 외부의 상태(변수)를 수정하지 않습니다. 🎜🎜대신, 다음은 불순한 함수의 예입니다: 🎜
public class ObjectWithNonPureFunction{    private int value = 0;    public int add(int nextValue) {        this.value += nextValue;        return this.value;
    }
}

add()方法使用成员变量value来计算其返回值,并且它还修改了value成员变量的状态,这代表它有副作用,这两个条件都导致add方法不是一个纯函数

正如我们看到的,函数式编程并不是解决所有问题的银弹。尤其是“函数是没有副作用的”这个原则就使得在一些场景下很难使用函数式编程,比如要写入数据库的场景,写入数据库就算是一个副作用。所以,我们需要做的是了解函数式编程擅长解决哪些问题,把它用在正确的地方。

函数式接口

Java中的函数式接口在 Lambda 表达式那篇文章里提到过,这里再详细说说。函数式接口是只有一个抽象方法的接口(抽象方法即未实现方法体的方法)。一个 Interface 接口中可以有多个方法,其中默认方法和静态方法都自带实现,但是只要接口中有且仅有一个方法没有被实现,那么这个接口就可以被看做是一个函数式接口

下面这个接口只定义了一个抽象方法,显然它是一个函数式接口:

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");
    }
}

因为doIt方法在接口中定义了默认实现,静态方法也有实现,接口中只有一个抽象方法run没有提供实现,所以它满足函数式接口的要求。

这里要注意,如果接口中有多个方法没有被实现,那么接口将不再是函数式接口,因此也就没办法用 Java 的 Lambda 表达式实现接口了

编译器会根据 Lambda 表达式的参数和返回值类型推断出其实现的抽象方法,进而推断出其实现的接口,如果一个接口有多个抽象方法,显然是没办法用 Lambda 表达式实现该接口的。

@FunctionalInterface 注解

这里扩充一个标注接口是函数式接口的注解@FunctionalInterface

@FunctionalInterface 
// 标明接口为函数式接口
public interface MyInterface {    public void run(); 
//抽象方法}

一旦使用了该注解标注接口,Java 的编译器将会强制检查该接口是否满足函数式接口的要求:“确实有且仅有一个抽象方法”,否则将会报错。

需要注意的是,即使不使用该注解,只要一个接口满足函数式接口的要求,那它仍然是一个函数式接口,使用起来都一样。该注解只起到--标记接口指示编译器对其进行检查的作用。

Java 内置的函数式接口

Java 语言内置了一组为常见场景的用例设计的函数式接口,这样我们就不必每次用到Lambda 表达式、Stream 操作时先创建函数式接口了,Java 的接口本身也支持泛型类型,所以基本上 Java 内置的函数式接口就能满足我们平时编程的需求,我自己在开发项目时,印象里很少见过有人自定义函数式接口。

在接下来的部分中,我们详细介绍下 Java 内置为我们提供了的函数式接口。

Function

Function接口(全限定名:java.util.function.Function)是Java中最核心的函数式接口。 Function 接口表示一个接受单个参数并返回单个值的函数(方法)。以下是 Function 接口定义的:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
            return t -> t;
    }

Function接口本身只包含一个需要实现的抽象方法apply,其他几个方法都已在接口中提供了实现,这正好符合上面我们讲的函数式接口的定义:“有且仅有一个抽象方法的接口”。

Function 接口中的其他三个方法中compseandThen 这两个方法用于函数式编程的组合调用,identity用于返回调用实体对象本身,我们之前在把对象 List 转换为 Map 的内容中提到过,可以回看前面讲 List 的文章复习。

Function接口用Java 的类这么实现

public class AddThree implements Function<Long, Long> {

    @Override
    public Long apply(Long aLong) {
        return aLong + 3;
    }

    public static void main(String[] args) {
        Function<Long, Long> adder = new AddThree();
		Long result = adder.apply(4L);
		System.out.println("result = " + result);
    }
}

不过现实中没有这么用的,前面说过 Lambda 表达式是搭配函数式接口使用的,用Lambda表达式实现上Function 接口只需要一行,上面那个例子用 Lambda 实现的形式是:

Function<Long, Long> adder = (value) -> value + 3;Long resultLambda = adder.apply(8L);
System.out.println("resultLambda = " + resultLambda);

是不是简洁了很多。后面的接口示例统一用 Lambda 表达式举例,不再用类实现占用太多篇幅。

Function接口的常见应用是 Stream API 中的 map 操作方法,该方法的参数类型是Function接口,表示参数是一个“接收一个参数,并返回一个值的函数”。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

所以我们在代码里常会见到这样使用 map 操作:

stream.map((value) -> value.toUpperCase())

Predicate

Predicate 接口 (全限定名:java.util.function.Predicate)表示一个接收单个参数,并返回布尔值 true 或 false 的函数。以下是 Predicate 功能接口定义:

public interface Predicate<T> {    boolean test(T t);
}

Predicate 接口里还有几个提供了默认实现的方法,用于支持函数组合等功能,这里不再赘述。 用 Lambda 表达式实现 Predicate 接口的形式如下:

Predicate predicate = (value) -> value != null;

Stream API 中的 filter 过滤操作,接收的就是一个实现了 Predicate 接口的参数。

Stream<T> filter(Predicate<? super T> predicate);

写代码时,会经常见到这样编写的 filter 操作:

Stream<String> longStringsStream = stream.filter((value) -> {    
// 元素长度大于等于3,返回true,会被保留在 filter 产生的新流中。
    return value.length() >= 3;
});

Supplier

Supplier 接口(java.util.function.Supplier),表示提供某种值的函数。其定义如下:

@FunctionalInterfacepublic interface Supplier<T> {
    T get();
}

Supplier接口也可以被认为是工厂接口,它产生一个泛型结果。与 Function 不同的是,Supplier 不接受参数。

Supplier<Integer> supplier = () -> new Integer((int) (Math.random() * 1000D));

上面这个 Lambda 表达式的 Supplier 实现,用于返回一个新的 Integer 实例,其随机值介于 0 到 1000 之间。

Consume

Consumer 接口(java.util.function.Consume)表示一个函数,该函数接收一个参数,但是不返回任何值。

@FunctionalInterfacepublic interface Consumer<T> {    void accept(T t);
}

Consumer 接口常用于表示:要在一个输入参数上执行的操作,比如下面这个用Lambda 表达式实现的 Consumer,它将作为参数传递给它的value变量的值打印到System.out标准输出中。

Consumer<Integer> consumer = (value) -> System.out.println(value);

Stream API 中的 forEach、peek 操作方法的参数就是 Consumer 接口类型的。

Stream<T> peek(Consumer<? super T> action);
void forEach(Consumer<? super T> action);

比如,Stream API 中的 forEach 操作,会像下面这样使用 Consume 接口的实现

Stream<String> stream = stringList.stream();
// 下面是Lambda 的简写形式
// 完整形式为:value -> System.out.println(value);
stream.forEach(System.out::println);

Optional

最后再介绍一下 Optional 接口,Optional 接口并不是一个函数式接口,这里介绍它主要是因为它经常在一些 Stream 操作中出现,作为操作的返回值类型,所以趁着学习函数式编程的契机也学习一下它。

Optional 接口是预防NullPointerException的好工具,它是一个简单的容器,其值可以是 null 或非 null。比如一个可能返回一个非空结果的方法,方法在有些情况下返回值,有些情况不满足返回条件返回空值,这种情况下使用 Optional 接口作为返回类型,比直接无值时返回 Null 要更安全。 接下来我们看看 Optional 怎么使用:

// of 方法用于构建一个 Optional 容器
Optional<String> optional = Optional.of("bam");
// 判断值是否为空
optional.isPresent();           // true
// 取出值,如果不存在直接取会抛出异常
optional.get();                 // "bam"
// 取值,值为空时返回 orElse 提供的默认值
optional.orElse("fallback");    // "bam"
// 如果只存在,执行ifPresent参数中指定的方法
optional.ifPresent((s) -> System.out.println(s.charAt(0)));// "b"

Stream 操作中像 findAny、 findFirst这样的操作方法都会返回一个 Optional 容器,意味着结果 Stream 可能为空,因此没有返回任何元素。我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素。Java 编程那些绕不开的接口 这个子系列的文章已经更新完毕,感兴趣的请持续关注,后面还有更多实用、精彩的内容。


推荐学习:《java视频教程

위 내용은 Java 함수형 프로그래밍에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제