使用 Java 实现函数式编程

2025-06-04

使用 Java 实现函数式编程

Java 自推出以来一直是最受欢迎的语言,因为它的创造者确保该语言不会遗漏任何东西,并在不断出现的变化中保持其独创性。

函数式编程

函数式编程是一种受 Lambda 演算启发的编程概念。在这个概念中,每个计算都被视为一个函数。尽管这些函数不应该被允许在其作用域之外更改状态/数据。
功能

为什么?

软件开发是一个迭代过程,不仅涉及编写代码,还涉及理解他人编写的代码。

你可能会发现它很有挑战性,反之亦然:D

如果某个对象被使用该对象作为参数的函数改变了状态,那么仅仅弄清楚该对象的状态就可能花费大量时间。因此,很难预测程序的行为。

把它想象成一个没有文档的第三方 API。你不确定调用某个函数时会发生什么。

它与函数式编程的概念不同。通过不允许更改状态和可变数据,它避免了副作用

如何?

所有这些理论都很好,但是作为 Java 开发人员我该如何使用它呢?

大家肯定都在思考这个问题,答案就是 Java 家族的新成员Lambda

Lambda

Lambda 是一个像对象一样运作的函数。它可以传递到任何地方,可以在任何需要的时候执行。它可以一行定义,无需类即可创建。还有更多其他功能。它有助于减少 Java 之前所需的大量样板代码。

Syntax - (Parameter Declaration) -> {Lambda Body}
Enter fullscreen mode Exit fullscreen mode
Examples -
Without parameters - () -> System.out.println("Hello lambda")
With one parameter - s -> System.out.println(s)
With two parameters - (x, y) -> x + y
With multiple line body - (x, y) -> {}
Enter fullscreen mode Exit fullscreen mode

很酷吧?但是 Java 如何知道哪个 lambda 表达式映射到哪个函数呢?

深入挖掘

由于接口是 lambda 表达式的骨干,Java 引入了一个称为函数式接口的新概念,lambda 表达式就是针对该概念定义的。

函数式接口

它们与 Java 中的普通接口类似,但有一个主要区别。它们遵循SAM 规则。根据 SAM 规则,一个接口只允许一个抽象方法 (SAM)。

可以在编译时使用@FunctionalInterface注释强制执行此检查。

对于每个 lambda,我们都需要创建一个新的函数式接口?
这很不方便,对吧?

Java 似乎已经解决了这个问题。java.util.function
包含近 50 个函数式接口。你不太可能需要它们范围之外的东西。

功能接口系列

掌握所有接口似乎很困难。其实,只要我们了解它们的接口家族,就能轻松找到正确的接口。

函数接口有 4 个系列 -

消费者——消费并丢弃

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
Enter fullscreen mode Exit fullscreen mode

它接受一个对象,执行一些操作,但不返回任何输出。真够意思 :D

例子 -
Consumer<String> stringConsumer = string -> System.out.println(string);
Enter fullscreen mode Exit fullscreen mode

由于 lambda 函数和 println() 都接受相同的参数,因此也可以使用方法引用来编写,如下所示 -

Consumer<String> stringConsumer = System.out::println;
Enter fullscreen mode Exit fullscreen mode

函数 - 映射、转换或计算

@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;
    }
}
Enter fullscreen mode Exit fullscreen mode

它接受一个对象,对其执行一些操作并返回另一个对象。

例子 -
Function<String, String> stringStringFunction = String::toUpperCase;
Enter fullscreen mode Exit fullscreen mode

谓词 - 测试或过滤

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

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
Enter fullscreen mode Exit fullscreen mode

它接受一个对象,并返回一个布尔值。通常定义规则。

例子 -
Predicate<String> stringPredicate = String::isEmpty;
Enter fullscreen mode Exit fullscreen mode

供应商 - 创建

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
Enter fullscreen mode Exit fullscreen mode

它不接受任何参数,但返回一个对象。真是慷慨啊 :D

例子 -
Supplier<String> stringSupplier = () -> "Hello, World";
Enter fullscreen mode Exit fullscreen mode

现实世界

现在是时候将我们的知识应用到一些现实世界的挑战中了。让我们从 HackerRank 中挑选一个基本的编程挑战 - camelCase

Problem - Count number of words in a camelCase string.

Example - saveChangesInTheEditor
Result - 5
Enter fullscreen mode Exit fullscreen mode

因此,我们的动机不仅是解决这个问题,而且要用函数式的方法解决它。

解决方案很简单,计算字符串中大写字母的数量,结果是 count + 1。

为了以功能性的方式做到这一点,我们需要-

  • 字符串中单个字符的流,以及
  • 有助于过滤大写字符的谓词。
Solution -

/* Stream - s.chars()
Predicate - Character::isUpperCase */

static long camelcase(String s) {
    return s.chars().filter(Character::isUpperCase).count() + 1;
}
Enter fullscreen mode Exit fullscreen mode

好哇!

一行代码就能解决所有问题。

这种方法有助于开发人员轻松理解代码片段的行为。上面这行代码很容易理解,它的作用是从字符串中过滤掉大写字母,并返回 (字符数 + 1) 的值,没有任何副作用。这正是我们想要的。

尽管变得实用并不局限于上述例子,但它是随着经验而发展起来的,因为它关乎我们如何处理问题。

Java 开发人员可能需要付出额外的努力来学习这种方法,因为我们的思维被训练来创建比所需更多的代码。:D

文章来源:https://dev.to/ajiteshtiwari/go-function-with-java-45ac
PREV
前端工程师的后端基础知识:使用 NodeJS、Prisma 和 PostgreSQL 深入研究 SQL 和 API
NEXT
向您的 React 应用程序添加简单的身份验证注册 Okta 仪表板注册您的 React 应用程序启动您的代码编辑器创建登录组件创建注销功能恭喜!🎉