ZetCode

Java 函数

最后修改于 2024 年 7 月 7 日

在本文中,我们将展示如何在 Java 中使用 Function 接口。该接口位于 java.util.function 包中。

函数定义

Function 是一个 Java 函数式接口,它表示接受一个参数并产生结果的函数。 它是一个具有单个抽象方法的接口,该方法接受输入,执行特定任务,并可以选择返回输出。

由于 Java 不支持纯函数,因此引入了该接口以克服此限制。 实际上,编译器会在后台从每个 Function 创建一个对象。

Function 用于在类外部创建小型纯函数和匿名表达式,尤其是在函数式编程范例中。

简单 Function 示例

以下是使用 Function 的一个简单示例。

Main.java
import java.util.function.Function;
import java.util.List;

Function<Integer, Integer> doubleNum = n -> n * 2;

void main() {

    var vals = List.of(1, 2, 3, 4, 5 );

    for (var e: vals) {

        int res = doubleNum.apply(e);
        System.out.println(res);
    }
}

我们定义一个将值乘以 2 的函数。

Function<Integer, Integer> doubleNum = n -> n * 2;

该函数声明了两种类型。 第一个类型是函数输入的类型,第二个类型是函数结果的类型。 等号后面的变量是输入变量。 -> 之后的表达式的结果是函数返回值。 请注意,我们已经在类外部定义了一个函数。

var vals = List.of(1, 2, 3, 4, 5 );

for (var e: vals) {

    int res = doubleNum.apply(e);
    System.out.println(res);
}

我们遍历整数列表,并将函数应用于每个元素。

$ java Main.java
2
4
6
8
10

在下一个示例中,我们使用 Function 定义一个问候语。

Main.java
import java.util.List;
import java.util.function.Function;

Function<String, String> greet = name -> String.format("Hello %s!", name);

void main() {

    var names = List.of("Peter", "Lucia", "Jozef", "Martin");

    for (var name : names) {

        String greeting = greet.apply(name);
        System.out.println(greeting);
    }
}

示例中的函数接受一个名称作为参数,并返回一个问候语。

Function<String, String> greet = name -> String.format("Hello %s!", name);

我们使用模板字符串从“Hello”文字和 name 输入变量构造问候语。

$ java Main.java
Hello Peter!
Hello Lucia!
Hello Jozef!
Hello Martin!

删除重复项

在下一个示例中,我们从整数列表中删除重复项。

Main.java
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.List;

Function<List<Integer>, List<Integer>> remDup = vals -> vals.stream()
        .distinct()
        .collect(Collectors.toList());

void main() {

    var nums = List.of(1, 2, 1, 2, 3, 4, 5, 1, 0, -1 );
    var nums2 = remDup.apply(nums);

    System.out.println(nums2);
}

我们有一个整数列表。 我们定义 remDup 函数以删除重复值。

Function<List<Integer>, List<Integer>> remDup = vals -> vals.stream()
    .distinct()
    .collect(Collectors.toList());

该函数接受一个整数列表作为输入,并返回一个整数列表。 在其主体中,它使用 distinct 方法来完成这项工作。

$ java Main.java
[1, 2, 3, 4, 5, 0, -1]

过滤器中的函数

stream filter 方法期望一个谓词函数。 我们有一种特殊的函数类型,称为 Predicate。 但是我们也可以使用 Function

Main.java
import java.util.function.Function;
import java.util.List;

Function<Integer, Boolean> isEven = n -> n % 2 == 0; 

void main() {

    var vals = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
    vals.stream().filter(isEven::apply).forEach(System.out::println);
}

我们定义 isEven 函数,如果该数字是偶数,则返回 true。

vals.stream().filter(isEven::apply).forEach(System.out::println);

我们传递对函数 apply 方法的引用。

$ java Main.java
2
4
6
8

谓词函数将定义如下

Predicate<Integer> isEven = n -> n % 2 == 0;

计算年龄

以下示例使用函数来计算用户的年龄。

Main.java
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

Function<LocalDate, Integer> findAge = dob -> Period.between(dob,
        LocalDate.now()).getYears();

Consumer<User> action = u -> System.out.printf("%s is %d years old%n", 
    u.name, findAge.apply(u.dob));

void main() {

    List<User> users = List.of(
            User.of("John Doe", "gardener", LocalDate.of(1956, 11, 12)),
            User.of("Roger Roe", "driver", LocalDate.of(1976, 11, 12)),
            User.of("John Doe", "teacher", LocalDate.of(1967, 1, 7)),
            User.of("John Doe", "gardener", LocalDate.of(1998, 5, 22)));

    users.forEach(action);
}

record User(String name, String occupation, LocalDate dob) {
    static User of(String name, String occupation, LocalDate dob) {
        return new User(name, occupation, dob);
    }
}

我们有一个用户列表。 每个用户都有一个出生日期作为字段。 我们在函数中从此字段计算用户的年龄。

Function<LocalDate, Integer> findAge = dob -> Period.between(dob,
    LocalDate.now()).getYears();

该函数使用一个简单的表达式,其中使用 Period.between 方法计算年龄。

Consumer<User> action = u -> System.out.printf("%s is %d years old%n", 
    u.name, findAge.apply(u.dob));

我们定义一个消费者,为每个元素打印消息。

$ java Main.java
John Doe is67 years old
Roger Roe is47 years old
John Doe is57 years old
John Doe is25 years old

年龄分类

下一个示例使用带有 switch 表达式的函数。

Main.java
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.List;

Function<Integer, String> ageCategory = age -> switch (age) {
    case Integer n when n < 18 && n > 0 -> "minor";
    case Integer n when n >= 18 && n < 64 -> "adult";
    case Integer n when n > 64 -> "senior";
    default -> "n/a";
};

Consumer<String> action = System.out::println;

void main() {

    List<Integer> ages = List.of(11, 18, 17, 19, 21, 55, 86, 99, 43, 65, 63);
    ages.stream().map(age -> ageCategory.apply(age)).forEach(action);
}

我们有一个年龄值列表。 我们将每个值放入一个类别:未成年人、成年人和老年人。

$ java Main.java
minor
adult
minor
adult
adult
adult
senior
senior
adult
senior
adult

键提取器 Function

我们定义一个函数,用作比较器的键提取器。

Main.java
import java.util.function.Function;
import java.util.Comparator;
import java.util.List;

Function<String, Integer> strLen = String::length;

void main() {

    var words = List.of("peculiar", "up", "blue", "atom", "by", "nice",
            "storm", "edible", "sky", "bookworm", "stronghold");

    words.forEach(System.out::println);

    System.out.println("----------------------");

    var sorted = words.stream().sorted(Comparator.comparing(strLen)).toList();

    sorted.forEach(System.out::println);
}

我们有一个单词列表。 我们按单词的长度对单词进行排序。

Function<String, Integer> strLen = String::length;

该函数返回传递值的字符串长度。 它用作比较器对象的键。

var sorted = words.stream().sorted(Comparator.comparing(strLen)).toList();

我们将键提取器函数传递给 Comparator.comparing,该函数为 sorted 方法生成一个比较器。

$ java Main.java
peculiar
up
blue
atom
by
nice
storm
edible
sky
bookworm
stronghold
----------------------
up
by
sky
blue
atom
nice
storm
edible
peculiar
bookworm
stronghold

函数组合

函数可以使用 composeandThen 方法进行组合。 区别在于调用函数的顺序。

Main.java
import java.util.function.Function;

void main() {

    Function<String, String> upperFun = String::toUpperCase;
    Function<String, String> reverseFun = val -> new StringBuilder(val).reverse().toString();

    var res = upperFun.compose(reverseFun).apply("falcon");
    System.out.println(res);
}

compose 函数将 reverseFunupperFun 应用于参数。

$ java Main.java
NOCLAF

有时,函数组合的顺序很重要。

Main.java
import java.util.function.Function;

void main() {

    Function<Double, Double> half = a -> a / 2;
    Function<Double, Double> square = a -> a * a;
    
    Function<Double, Double> squareAndThenHalf = square.andThen(half);
    Double res1 = squareAndThenHalf.apply(3d);
    
    System.out.println(res1);

    Function<Double, Double> squareComposeHalf = square.compose(half);
    Double res2 = squareComposeHalf.apply(3d);
    System.out.println(res2);
}

我们有两个方法 halfsquare

Function<Double, Double> squareAndThenHalf = square.andThen(half);
Double res1 = squareAndThenHalf.apply(3d);  

andThen 方法首先求平方,然后减半。

Function<Double, Double> squareComposeHalf = square.compose(half);
Double res2 = squareComposeHalf.apply(3d);

compose 方法首先减半,然后求平方。

$ java Main.java
4.5
2.25

在下一个示例中,我们将三个函数组合在一起。

Main.java
import java.util.function.Function;

Function<Integer, Integer> fn1 = n -> n + 1;
Function<Integer, Integer> fn2 = n -> n * 2;
Function<Integer, Integer> fn3 = n -> n * n;

Function<Integer, Integer> cfn1 = fn1.andThen(fn2).andThen(fn3);
Function<Integer, Integer> cfn2 = fn1.compose(fn2).compose(fn3);

void main() {

    Integer res = cfn1.apply(10);
    System.out.println(res);

    Integer res2 = cfn2.apply(10);
    System.out.println(res2);
}

首先,我们使用 andThen 组合这三个函数,然后使用 compose 组合。

$ java Main.java
484
201

将 Function 作为参数传递

在以下示例中,我们将定义的 Function 作为参数传递给另一个函数。

Main.java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

void main() {

    List<String> words = List.of("coin", "pen", "Monday", "NASA", "Moscow",
            "cup", "notebook", "class");

    Function<String, String> uf = String::toUpperCase;
    Function<String, String> lf = String::toLowerCase;

    var res = modify(words, uf);
    System.out.println(res);

    var res2 = modify(words, lf);
    System.out.println(res2);
}

List<String> modify(List<String> data, Function<String, String> f) {
    var uppered = new ArrayList<String>();

    for (var e : data) {
        uppered.add(f.apply(e));
    }

    return uppered;
}

在该程序中,我们有一个单词列表。 我们定义 modify 方法,该方法接受一个函数作为参数。 该函数转换数组元素,并返回一个修改后的字符串新列表。

Function<String, String> uf = String::toUpperCase;
Function<String, String> lf = String::toLowerCase;

我们有两个函数。 它们将传递的单词大写和小写。

List<String> modify(List<String> data, Function<String, String> f) {

modify 函数接受一个函数作为第二个参数。

$ java Main.java
[COIN, PEN, MONDAY, NASA, MOSCOW, CUP, NOTEBOOK, CLASS]
[coin, pen, monday, nasa, moscow, cup, notebook, class]

来源

Java Function - 语言参考

在本文中,我们使用了 Java Function 接口。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程