您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java BiFunction 接口实例

时间:2019-07-30 09:44:38  来源:  作者:

1. 简介

JAVA8 引入了函数式编程,可以把函数作为参数传入实现通用方法。熟知的 Java 8 单个参数函数式接口比如 Function、Predicate 和 Consumer。

这篇教程会介绍如何使用支持两个参数的函数式接口。这样的函数被称为二元函数,在 Java 中使用 BiFunction 函数式接口。

2. 单个参数函数

让我们快速回顾一下如何使用单个参数函数或者一元函数,就像在Stream教程中实现的示例:

List<String> mApped = Stream.of("hello", "world")
 .map(word -> word + "!")
 .collect(Collectors.toList());
assertThat(mapped).containsExactly("hello!", "world!");

 

上面的单元测试中,map 方法接受1个 Function 类型作为参数,对给定的的输入操作后返回结果。

3. 双参操作

Java Stream 库提供了 reduce 函数,可以组合 Stream 中的元素。在这里定义已接收的数据如何与下一个操作转换。

reduce 接受 BinaryOperator<T> 类型作为参数,支持输入两个类型相同的对象。

假设要求把 Stream 中的数据通过破折号连接起来,下面是几种实现方案。

3.1. 使用 Lambda 表达式

用 Lambda 实现 BiFunction,前面是由括号包围的两个参数:

String result = Stream.of("hello", "world")
 .reduce("", (a, b) -> b + "-" + a);
assertThat(result).isEqualTo("world-hello-");

 

上面的示例中,a、b 两个值是字符串类型。Lambda 实现把两个参数按照要求组合,b 在前,a 在后,中间是破折号。

可以看到 reduce 的第一个参数是空字符串,Stream 中的第一个值会与空字符串连接。

另外可以注意到,Java 类型推断在大多数情况下可以忽略参数类型。在 Lambda 上下文类型不明确的情况下,可以为参数加上类型:

String result = Stream.of("hello", "world")
 .reduce("", (String a, String b) -> b + "-" + a);

 

3.2. 使用 Function

如果上面的算法要求不在结尾加上破折号该怎么处理?可以在 lambda 中编写更多代码,但这可能会让代码变得更杂乱。可以把它抽取成一个函数:

private String combineWithoutTrailingDash(String a, String b) {
 if (a.isEmpty()) {
 return b;
 }
 return b + "-" + a;
}

 

然后调用:

String result = Stream.of("hello", "world")
 .reduce("", (a, b) -> combineWithoutTrailingDash(a, b));
assertThat(result).isEqualTo("world-hello");

 

像上面这样,lambda 会调用抽取出来的函数。不仅更易于阅读,而且可以扩充到更复杂的实现。

3.3. 使用方法引用

一些 IDE 会自动提示,把面的 lambda 转换为方法引用,这样读起来会更清晰。

重写上面的代码,改为方法引用:

String result = Stream.of("hello", "world")
 .reduce("", this::combineWithoutTrailingDash);
assertThat(result).isEqualTo("world-hello");

 

方法引用通常让函数式代码自解释性变得更强

4. 使用 BiFunction

到目前为止,我们介绍了如何对两个相同类型的参数使用函数。BiFunction 接口支持不同参数的类型 且返回值可以是第三种类型。

假设要求把两个长度相等的列表合并成一个结果列表,对每对输入值执行操作计算结果:

List<String> list1 = Arrays.asList("a", "b", "c");
List<Integer> list2 = Arrays.asList(1, 2, 3);
List<String> result = new ArrayList<>();
for (int i=0; i < list1.size(); i++) {
 result.add(list1.get(i) + list2.get(i));
}
assertThat(result).containsExactly("a1", "b2", "c3");

 

4.1. 为 Function 增加范型

可以使用 BiFunction 为方法增加范型 combiner

private static <T, U, R> List<R> listCombiner(
 List<T> list1, List<U> list2, BiFunction<T, U, R> combiner) {
 List<R> result = new ArrayList<>();
 for (int i = 0; i < list1.size(); i++) {
 result.add(combiner.apply(list1.get(i), list2.get(i)));
 }
 return result;
}

 

上面的代码包含了三种参数类型:第一个列表中的元素类型为 T,第二个列表中的元素类型为 U,组合函数调用后的元素类型为 R。

调用 BiFunction 的 apply 方法 得到结果。

4.2. 调用范型函数

combiner 的类型是 BiFunction,设计的算法可以处理不同类型的输入和输出。让我们试一下:

List<String> list1 = Arrays.asList("a", "b", "c");
List<Integer> list2 = Arrays.asList(1, 2, 3);
List<String> result = listCombiner(list1, list2, (a, b) -> a + b);
assertThat(result).containsExactly("a1", "b2", "c3");

 

也可以用来处理完全不同类型的输入和输出。

让我们加入一个算法,判断列表1中的值是否大于列表2,并生成一个 boolean 结果:

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);
List<Boolean> result = listCombiner(list1, list2, (a, b) -> a > b);
assertThat(result).containsExactly(true, true, false);

 

4.3. BiFunction 方法引用

用上面抽取的方法和方法引用重写上面的代码:

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);
List<Boolean> result = listCombiner(list1, list2, this::firstIsGreaterThanSecond);
assertThat(result).containsExactly(true, true, false);
private boolean firstIsGreaterThanSecond(Double a, Float b) {
 return a > b;
}

 

用方法引用加入算法 firstIsGreaterThanSecond 看起来代码更容易理解一些。

4.4. 使用 this 调用 BiFunction 方法引用

改变一下要求,用上面基于 BiFunction 算法确认两个列表是否相等:

List<Float> list1 = Arrays.asList(0.1f, 0.2f, 4f);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);
List<Boolean> result = listCombiner(list1, list2, (a, b) -> a.equals(b));
assertThat(result).containsExactly(true, true, true);

 

实际上可以简化成下面这样:

List<Boolean> result = listCombiner(list1, list2, Float::equals);

 

这是因为 Float 中的 equals 与 BiFunction 函数签名相同,第一个隐式参数是 Float 类型,第二个参数是 Object 类型与第一个参数比值。

5. BiFunctions 组合

如果通过方法引用实现数值比较该怎么实现?

如果使用方法引用实现数值列表比较示例会是怎样?

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Double> list2 = Arrays.asList(0.1d, 0.2d, 4d);
List<Integer> result = listCombiner(list1, list2, Double::compareTo);
assertThat(result).containsExactly(1, 1, -1);

 

这个例子与之前的很像,但是返回值类型为 Integer 而非原来的 Boolean。这是因为 Double 中的 compareTo 方法返回类型为 Integer。

这里可以使用 andThen 在原来的基础上增加额外的处理。这会生成 BiFunction,先对两个输入执行操作,然后接着执行另一个操作。

接下来,新建一个函数来把 Double::compareTo 方法引用强制转换为 BiFunction:

private static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> function) {
 return function;
}

 

lambda 或者方法引用只在转换后才能变成 BiFunction。可以使用下面 helper 函数把 lambda 显示转换为 BiFunction 对象。

现在,使用 andThen 扩展现有函数的行为:

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Double> list2 = Arrays.asList(0.1d, 0.2d, 4d);
List<Boolean> result = listCombiner(list1, list2,
 asBiFunction(Double::compareTo).andThen(i -> i > 0));
assertThat(result).containsExactly(true, true, false);
Java BiFunction 接口实例

6. 总结

本文从 Java Stream 库与自定义函数两个角度探索了 BiFunction 和 BinaryOperator 的用法,了解了如何使用 lambda 和方法引用传递 BiFunction 以及组合函数。

Java 库只提供单个参数和两个参数的函数式接口。需要更多参数,请参阅柯里化获得更多想法。完整的源代码可以在 GitHub 上找到。



Tags:Java 接口   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
Java8 引入了函数式编程,可以把函数作为参数传入实现通用方法。熟知的 Java 8 单个参数函数式接口比如 Function、Predicate 和 Consumer。...【详细内容】
2019-07-30  Tags: Java 接口  点击:(304)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(12)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(22)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条