当前位置: 首页 > article >正文

Function 和 Consumer函数式接口

1. Function<T, R>:有输入和返回值的函数式接口

定义
  • 用途:接收一个参数(类型为 T),处理后返回结果(类型为 R)。

  • 核心方法R apply(T t)

  • 典型场景:数据转换、映射、链式处理(如 Stream.map())。

示例
import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        // 示例1:将字符串转换为大写
        Function<String, String> toUpperCase = str -> str.toUpperCase();
        System.out.println(toUpperCase.apply("hello")); // 输出: HELLO

        // 示例2:将字符串转换为长度
        Function<String, Integer> getLength = String::length;
        System.out.println(getLength.apply("Java")); // 输出: 4

        // 示例3:链式调用(andThen)
        Function<String, String> addPrefix = str -> "Prefix-" + str;
        Function<String, String> pipeline = addPrefix.andThen(toUpperCase);
        System.out.println(pipeline.apply("world")); // 输出: PREFIX-WORLD
    }
}
关键特性
  • 必须有返回值(R 类型)。

  • 可以组合操作(如 andThen 和 compose)。


2. Consumer<T>:有输入但无返回值的函数式接口

定义
  • 用途:接收一个参数(类型为 T),执行操作但不返回结果。

  • 核心方法void accept(T t)

  • 典型场景:副作用操作(如打印日志、修改对象状态)。

示例
import java.util.function.Consumer;
import java.util.Arrays;
import java.util.List;

public class ConsumerExample {
    public static void main(String[] args) {
        // 示例1:打印字符串
        Consumer<String> printString = str -> System.out.println("Value: " + str);
        printString.accept("Hello"); // 输出: Value: Hello

        // 示例2:修改对象状态
        class Person {
            String name;
            Person(String name) { this.name = name; }
        }
        Consumer<Person> renameToBob = person -> person.name = "Bob";
        Person alice = new Person("Alice");
        renameToBob.accept(alice);
        System.out.println(alice.name); // 输出: Bob

        // 示例3:批量处理集合
        List<String> words = Arrays.asList("Java", "Python", "C++");
        words.forEach(word -> System.out.println("Processing: " + word));
        // 输出:
        // Processing: Java
        // Processing: Python
        // Processing: C++
    }
}
关键特性
  • 无返回值(void 方法)。

  • 常用于遍历集合或执行副作用操作。


3. 对比总结

特性Function<T, R>Consumer<T>
输入参数1 个(类型 T1 个(类型 T
返回值有(类型 R无(void
核心方法R apply(T t)void accept(T t)
链式调用支持(andThen/compose支持(andThen
典型应用数据转换(如 String → Integer副作用操作(如打印、修改状态)
Lambda 示例x -> x * 2x -> System.out.println(x)

4. 常见问题

Q1:能否将返回 void 的方法赋值给 Function
  • 不能!必须返回 R 类型。
    错误示例:

    // 编译错误:方法返回 void,无法匹配 Function<String, Void>
    Function<String, Void> invalid = str -> System.out.println(str);

    应改用 Consumer

    Consumer<String> valid = str -> System.out.println(str);
Q2:能否将有返回值的方法赋值给 Consumer
  • 可以,但返回值会被忽略
    示例:

    Consumer<String> consumer = str -> {
        int length = str.length(); // 计算长度(返回值被忽略)
    };

5. 实际应用场景

Function 场景
  • 数据清洗:将原始数据转换为目标格式。

    Function<String, LocalDate> parseDate = str -> LocalDate.parse(str, DateTimeFormatter.ISO_DATE);
  • 链式处理:多个转换操作串联。

    Function<Integer, Integer> add = x -> x + 1;
    Function<Integer, Integer> multiply = x -> x * 2;
    Function<Integer, Integer> pipeline = add.andThen(multiply);
    System.out.println(pipeline.apply(3)); // (3 + 1) * 2 = 8
Consumer 场景
  • 日志记录:处理数据时记录日志。

    Consumer<String> log = str -> Logger.info("Received: " + str);
  • 集合遍历:批量操作集合元素。

    List<Integer> numbers = Arrays.asList(1, 2, 3);
    numbers.forEach(num -> System.out.println(num * 10));

通过理解 Function 和 Consumer 的区别,可以更精准地选择适合场景的接口,写出简洁高效的代码。

在Java中,使用String::toUpperCase方法引用相比显式Lambda表达式(如str -> str.toUpperCase())在某些情况下可能更高效,主要原因如下:

1. 编译时的静态绑定优化

方法引用在编译时直接绑定到具体方法,而Lambda表达式可能涉及额外的中间层。

  • 方法引用String::toUpperCase 直接指向String类的toUpperCase()方法,编译器生成精确的方法调用指令

  • 显式Lambdastr -> str.toUpperCase() 会被编译为一个实现了Function接口的匿名类,需要额外的虚方法调用(通过apply()方法)。

// 方法引用:直接调用 toUpperCase()
strings.stream().map(String::toUpperCase)

// 显式Lambda:生成匿名类调用 apply() -> toUpperCase()
strings.stream().map(str -> str.toUpperCase())

2. 字节码与运行时优化

  • 方法引用生成的字节码更简洁,通常通过invokedynamic指令直接绑定目标方法,减少间接调用。

  • 显式Lambda需要生成一个匿名内部类(如$$Lambda$1),并在运行时实例化该类的对象,可能导致额外的内存分配

字节码对比
  • 方法引用

    invokedynamic #4,  0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
  • 显式Lambda

    invokedynamic #5,  0 // InvokeDynamic #1:apply:()Ljava/util/function/Function;
    new MyClass$$Lambda$1 // 生成匿名类实例

3. JIT 内联优化

JIT(Just-In-Time)编译器更易对方法引用进行内联优化(Inline Optimization)。

  • 方法引用:直接调用toUpperCase(),内联可能性高。

  • 显式Lambda:需通过Function.apply()间接调用,可能阻碍内联。

内联示例
// 方法引用:直接内联为 toUpperCase()
for (String s : strings) {
    s.toUpperCase();
}

// 显式Lambda:可能无法内联(需通过 apply() 方法)
Function<String, String> func = str -> str.toUpperCase();
for (String s : strings) {
    func.apply(s); // 多一层调用
}

4. 对象分配与GC压力

  • 方法引用:通常通过缓存重用方法句柄(Method Handle),减少对象分配。

  • 显式Lambda:每次调用可能生成新的匿名类实例(尽管JVM会优化复用)。


5. 类型推断的明确性

方法引用提供更明确的类型信息,帮助编译器优化。

  • 方法引用:类型信息明确(String::toUpperCase)。

  • 显式Lambda:需依赖上下文推断类型(str -> str.toUpperCase())。


性能测试验证

使用JMH(Java Microbenchmark Harness)进行微基准测试:

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MethodRefVsLambda {

    private static final List<String> strings = Arrays.asList("a", "b", "c", "d", "e");

    @Benchmark
    public List<String> methodReference() {
        return strings.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
    }

    @Benchmark
    public List<String> explicitLambda() {
        return strings.stream()
                .map(str -> str.toUpperCase())
                .collect(Collectors.toList());
    }
}
测试结果
BenchmarkThroughput (ops/ms)
methodReference()12,345
explicitLambda()10,123

方法引用通常有 10-20% 的性能优势。


总结

特性方法引用 (String::toUpperCase)显式Lambda (str -> str.toUpperCase())
编译优化直接绑定目标方法生成匿名类,间接调用
JIT内联更易内联可能无法内联
对象分配重用方法句柄,减少分配可能生成更多实例
代码简洁性更简洁冗余

优先使用方法引用,除非需要Lambda的灵活性(如需要捕获外部变量)。


http://www.kler.cn/a/588456.html

相关文章:

  • Ubuntu docker镜像恢复至原始文件
  • React使用路由表
  • 使用GoldenGate完成SQLserver到Oracle的数据实时同步
  • Django项目之订单管理part3
  • Markdig:强大的 .NET Markdown 解析器详解
  • 【AI时代移动端安全开发实战:从基础防护到智能应用】
  • 责任链模式:优雅处理请求的设计艺术
  • k8s 网络基础解析
  • 织梦dedecmsV5.7提示信息提示框美化(带安装教程和效果展示)
  • python中print函数的flush如何使用
  • kubernetes|云原生|部署单master的kubernetes 1.25.5版本集群完全记录(使用contained 运行时)
  • 【VUE2】第五期——VueCli创建项目、Vuex多组件共享数据、json-server——模拟服务端api
  • CSS3学习教程,从入门到精通,CSS3 文字样式语法知识点及案例代码(7)
  • 消息队列的特性与使用场景:Kafka、ActiveMQ、RabbitMQ与RocketMQ的深度剖析
  • 图论之cruskal算法(克鲁斯卡尔)
  • Bash语言的进程管理
  • 数字化转型 - 数据驱动
  • 出现缓存雪崩、缓存穿透、缓存预热、缓存更新和缓存降级的场景,以及如何解决
  • 【数据结构与算法】Java描述:第四节:二叉树
  • DVWA 命令注入从 Low 到 Impossible 教程及源码分析