专业编程基础技术教程

网站首页 > 基础教程 正文

Java8精华-函数式编程(一)读完这篇,你将彻底理解

ccvgpt 2024-11-30 19:18:30 基础教程 1 ℃

介绍

本文说的是函数式编程的基础知识。将从非常基础的内容开始,本系列文章的主要目的是让新手快速上手。但我确信我们还将讨论大多数有经验的 Java 程序员并没有真正意识到的东西。我们还将研究一些很酷的函数式编程方法,以应对使用 Java 集合处理数据时常见的编程挑战。

函数式编程到底是什么?

说法很多。我们都知道什么是面向对象编程,对于 OOP,我们只是使用对象 - 我们将它们分配给变量,并将它们作为参数或参数发送到 java 方法或从 java 方法接收它们。 函数式编程也是如此。我们将函数分配给变量,然后发送或接收函数。

Java8精华-函数式编程(一)读完这篇,你将彻底理解

对函数式编程的支持使 Java 更加流行。它现在是一种混合语言,因为它支持面向对象和函数式编程

但是什么是Lambda 表达式,怎么使用以及和函数之间的关系是什么?

Lambda表达式

Java 函数式编程的核心是 Lambda 表达式。 它们是 Java 函数式编程的推动者。事实上,当我们提到函数式编程时,本质上就是Lambda 表达式

Lambda表达式本身就是对象。我们编写的所有 lambda 表达式都是称为 函数接口 的特殊接口的具体实现。

现在的问题是这些功能接口是什么?函数式接口是具有某些特征的特殊接口。

函数式接口及其特点

函数式接口是 Lambda 表达式的基础。以下是函数式接口的特征。

  1. 函数式接口 必须只有一个抽象方法。这就是为什么它们也被称为SAM(单一抽象方法)接口。
  2. 函数式接口可以有多个非抽象方法,但只能有一个单个抽象方法,并继承到所有实现类。这很奇怪。我们知道Java接口不能有非抽象方法。但为什么这个说法是矛盾的呢?Java 8 在接口中引入了Default关键字,可以在接口中定义方法的默认实现。
  3. 函数式接口还可以有static方法

上面说的就是下面的意思:

功能接口必须有一个抽象方法,并且可以有许多可选的static 或default 方法定义。

现在我们已经了解了函数式接口的特征,让我们看几个接口并确定它们是否是函数式接口

示例1

下面是函数式接口吗?

interface SampleInterface {
}

不,它不是函数式接口。因为函数式接口必须有一个单一的抽象方法。

示例2

interface SampleInterface {
    void foo(int f);
}

是的。因为它只有一个抽象方法。

示例3

interface SampleInterface {
    void foo();
    void bar(int b);
}

它不是。记住第一点。函数式接口必须只有一个抽象方法。但这里我们有两个抽象方法。

示例4

interface SampleInterface {
    void foo();
    static void bar() {
        System.out.println("Static");
    }
}

是的。包含一个抽象方法和一个静态方法定义。

示例5

interface SampleInterface {
    void foo();
    static void bar();
}

无法编译。因为静态方法必须有方法体。

示例6

interface SampleInterface {
    void foo();
    static void bar() {
        System.out.println("Static");
    }
    default void bar2() {
        System.out.println("Default");
    }
}

是的。它有一个抽象方法、一个静态方法定义和一个默认方法。

示例7

interface SampleInterface {

    void foo();

    static void sbar1() {
        System.out.println("Static Bar 1");
    }

    static void sbar2() {
        System.out.println("Static Bar 2");
    }

    default void dbar1() {
        System.out.println("Default Bar 1");
    }

    default void dbar2() {
        System.out.println("Default Bar 1");
    }

}

是的。除了单个抽象方法之外,函数式接口还可以有许多默认方法和抽象方法。

顾名思义,函数式接口首先只是接口。这意味着我们可以从中实现具体的类。上面示例7 中定义的接口的实现如下所示。

实现1:具体实现类

class SampleImpl implements SampleInterface {
    @Override
    public void foo() {
        System.out.println("实现!!");
    }
}

可以创建一个这样的对象

SampleInterface si = new SampleImpl();
si.foo();

或者,我们可以创建一个匿名内部类并重写方法 foo ,而不是创建另一个类,如下所示。

实现2:匿名内部类

SampleInterface si2 = new SampleInterface() {
    @Override
    public void foo() {
        System.out.println("匿名内部类");
    }
};
si2.foo();

我们知道重写的继承特性。如果我们想创建另一个实现,我们可以简单地通过复制粘贴部分代码来实现它。这就是接口的全部意义,是吗?

但你看到这里的问题了吗?我将其称为复制粘贴编程。如果我们必须提供数百个这样的实现,我们的项目有太多的实现类。

但如果你仔细观察的话,只有方法中的逻辑发生了变化,包含有很多的重复代码,我们所需要的只是一行打印语句。但我们最终只是将其放入样板代码中使其变得臃肿庞大。

这是面向对象编程的主要缺点我们必须告诉编译器或运行时它必须执行的每一步。

用Lambda解决问题

现在我们了解了这个问题,我们将看看如何使用 Lambda 表达式来解决它。

很简单。使用 lambda 表达式,我们将优化上面的重复代码,只保留方法体,如下所示,并在方法参数后面嵌入一个数组运算符。

没有参数的时候

在我们的示例中,我们没有任何方法参数,因此参数是一个()

SampleInterface si = () -> {
    System.out.println("hello world");
};
si.foo();

我们需要做的就是放置一个箭头运算符 (->),告诉编译器这是一个 lambda 表达式。如果只有一个语句,我们甚至可以省略方法体的大括号。如下所示。

SampleInterface si = () -> System.out.println("hello world");
si.foo();

有参数的时候

让我们看一下带有方法参数的另一个版本。假设下面是我们的功能接口定义。

interface SampleInterface {
    void foo(int param);
}

匿名内部类实现

SampleInterface si2 = new SampleInterface() {
    @Override
    public void foo(int param) {
        System.out.println("param = " + param);
    }
};
si2.foo(100);

要转换上述匿名类,只需遵循与上述相同的过程。如下所示。

SampleInterface si2 = (int f) -> System.out.println("F = " + f);

甚至可以忽略数据类型关键字int。编译器将通过查看接口中的方法原型自动推断数据类型。所以更精致的会是这样的。

SampleInterface si2 = (param) -> System.out.println("param = " + param);

当我们只有一个参数时,我们甚至可以忽略括号。否则,参数是强制性的。

SampleInterface si2 = param -> System.out.println("param = " + param);
si2.foo(100);

赋值运算符 f -> System.out.println(“F = “ + f) 的右侧称为 lambda 表达式。它也称为函数表达式或简称为函数。它是功能接口SampleInterface的具体实现。

处理返回类型

现在如何处理返回类型。到目前为止,我们的抽象方法返回 void。假设我们的抽象方法有一个返回类型,如下所示。

interface Adder {
    int add(int n1, int n2);
}

首先让我们从匿名内部类开始,然后将其转换为 lambda 表达式。

Adder adder = new Adder() {
    @Override
    public int add(int n1, int n2) {
       return n1 + n2;
    }
}

转换:

Adder adder = (int n1, int n2) -> return n1 + n2;

现在我们甚至不需要指定数据类型,因为编译器会推断它们。

Adder adder = (n1, n2) -> { return n1 + n2;} 

这里括号是强制性的,因为我们的 lambda 表达式中有多个参数。

如果 lambda 表达式中只有一行,我们甚至可以省略 return 关键字(如下所示)和大括号。

Adder adder = (n1, n2) -> n1 + n2;

但如果我们必须使用 return 关键字,则必须使用大括号。否则,会导致编译时错误。例如,下面的代码会导致编译时错误。

Adder adder = (n1, n2) -> return n1 + n2;

还有一种情况。如果 lambda 表达式主体中有多个语句,那么我们需要将它们括在一对大括号中,如下所示。

Adder adder = (n1, n2) -> {
    int sum = n1 + n2;
    System.out.println(n1 + n2);
    return n1 + n2;
}

这就是本文的全部内容。在下一篇文章中,我们将深入探讨几种函数式接口及其在各种情况下对应的 lambda 表达式

总结

  1. 与面向对象编程一样,我们将对象分配给变量,并将它们发送到方法或从方法接收它们,在函数式编程中,我们将函数分配给变量,并将它们作为实参/参数发送到方法或从方法接收它们。这里所说的函数,是指函数表达式。
  2. Lambda 表达式支持 Java 中的函数式编程。
  3. 函数式接口充当 lambda 表达式的契约。它们指定 lambda 表达式将接受哪些参数以及返回值是什么。
  4. 通过 lambda 表达式,所有实现 SAM 接口的匿名内部类都可以转换为 lambda 表达式。
  5. 在编写 lambda 表达式时,如果有多个方法参数,我们需要将它们括在括号中。如果我们有多个语句,我们必须将它们括在一对花括号中。如果我们必须包含 return 语句,则无论主体中的语句数量如何,都必须将其括在一对花括号中。

最近发表
标签列表