简介
为了更好的将函数作为参数,Java遂引入了Function接口
Function<T, R>的使用
看其源码
要素察觉
- 该接口会接收一个参数,且会产生结果
- 在使用这个接口前需要明确定义参数类型和返回的结果类型(Java的泛型就是这么回事)
- 里面有一个
apply
方法将会对参数进行操作,并返回结果 - 因为是函数式接口(
@FunctionalInterface
),可以通过lambda表达式实现该接口
测试
1 | java复制代码public class FunctionTest { |
Function<T, R>
进阶玩法,使用Function的默认方法compose
和andThen
再观察一波Function<T, R>接口源码
要素察觉
- 两个方法都使用了两次
apply
,只是this.apply
执行的先后不同 - 说明两个
Function
可以组合起来用 => 两个apply
可以组合使用 => 可以用两个Lambda表达式实现apply
测试
1 | java复制代码 |
解释
- compose
执行f1之前(before)先执行f2
- andThen
执行f1之后(after)再执行f2
小结
- 总之,要分清当前function,也就是当前对象,判断执行顺序。对象有方法和成员变量。
- 本例中,function1都是当前对象,调用它的方法,以及将function2作为参数传给它。
- function是对象,只是有函数的功能而已。面向对象不能丢
BiFunction<T,U,R>的使用
看其源码
要素察觉
BiFunction
接收两个参数返回一个结果- 它有一个抽象方法
apply
,接收两个参数 - 它有一个
andThen
的默认方法 - 和
Function
比起来多了一个参数,少了一个compose
方法- 因为
Function
只需要一个参数,BiFunction
刚好可以返回一个参数,可以先BiFuction
再Function
,所以有andThen
方法 BiFunction
是当前对象,如果它后执行,其他Function
先执行,且它们都只会返回一个结果,就会导致BiFunction
只有一个参数,满足不了BiFunction的需要两个参数的需求,所以没有compose
方法
- 因为
测试
1 | java复制代码public class FunctionTest { |
解释
其实
Bifuntction
和Function
差不多,BiFunction
接收两个参数,Function
接收一个参数
最后
这两个接口有啥用?
- 当需要对数据进行操作时且你想用lambda表达式时,就可以用它,而不需要自己去写一个满足函数式接口的接口。
- 而且对一个或两个数据的修改和操作是不同的,比如两个数的加减乘除,就不需要定义四个方法,只要Bifunction接口,用不同的Lambda表达式实现就好了
- 某个操作只用一次用Lamdba表达式即可,就不需要单独封装成方法了
1 | java复制代码System.out.println(test.operate3(2,3, (a,b) -> a+b));// 只用一次,就不需要封装成说明add方法之类的 |
- 如果你一个方法都不想封装,可以这么写
1 | java复制代码public class FunctionTest { |
另外一个小样例,对集合进行操作
- People
1 | java复制代码public class People { |
- 需求:获取People集合中年龄小于特定的元素
1 | java复制代码public class FunctionTestPro { |
- Stream流,将对象以流水的形态通过各种方法过滤,得到所需结果
- 对于Lambda表达式,如果方法体只有一行,不需要
{}
也不需要return
,它会自动return
,如果需要return
的话。- 如果多行请用
{}
和return
(a, b) ->{ a=a*b; return a+b; }
什么时候用Java 提供的Function接口
- 主要是要有将函数作为参数的思想。
- 你可以在以下情况下使用Java提供的Function接口:
- 数据映射:当你需要对集合中的每个元素进行某种映射操作,生成一个新的集合时,可以使用Function接口。
1
2
3
4
java复制代码List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Function<String, Integer> nameLengthMapper = String::length;
List<Integer> nameLengths = names.stream().map(nameLengthMapper).collect(Collectors.toList());
// nameLengths 现在包含 [5, 3, 7]
+ **连续操作**:当你需要在一系列的操作中传递行为时,Function接口可以作为方法参数,使代码更具灵活性。
1 | java复制代码public void processWithFunction(String data, Function<String, Integer> processor) { |
+ **函数组合**:你可以将多个Function接口连接在一起,形成一个复杂的函数组合。
1 | java复制代码Function<Integer, Integer> addOne = x -> x + 1; |
更好的语义
当涉及多个转换步骤或操作组合时,Function接口可以提供更具说服力的例子。让我们考虑以下情况:假设你有一个字符串列表,其中包含表示人员年龄的字符串,但是你需要计算这些成年的人员年龄的平均值。
我们可以使用Function接口来先将字符串转换为整数,然后计算平均值。尽管这个例子中的三个操作似乎很简单,但考虑到功能的可组合性和代码的可重用性,Function接口仍然非常有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29java复制代码public class Main {
public static void main(String[] args) {
List<String> ageStrings = Arrays.asList("25", "30", "40", "22", "28");
// 将字符串转换为整数的方法
Function<List<String>, List<Integer>> stringToInteger = ageStringLs -> ageStringLs
.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
// 过滤掉未成年人的方法
Function<List<Integer>, List<Integer>> filterAdults = ages -> ages.stream()
.filter(age -> age >= 18)
.collect(Collectors.toList());
// 计算平均值的函数
Function<List<Integer>, Double> averageFunction = list -> list.stream()
.mapToDouble(Integer::doubleValue)
.average()
.orElse(0.0);
// 将三个函数组合在一起,其实就是三个步骤,计算平均年龄,一套下来就获得了所需数据
Double averageAge = stringToInteger.andThen(filterAdults)
.andThen(averageFunction)
.apply(ageStrings);
System.out.println("平均年龄:" + averageAge);
}
}这样,就不用写成,像下面那样的代码了,按顺序调用方法,并获取各自的返回值
1
2
3
4
5
6
7
8java复制代码public static void main(String[] args) {
List<String> ageStrings = Arrays.asList("25", "30", "40", "22", "28");
List<Integer> ages = convertToIntegerList(ageStrings); // 将字符串转换为整数列表
List<Integer> adultsAges = filterAdults(ages); // 过滤未成年人
double averageAge = calculateAverage(adultsAges); // 计算平均年龄
System.out.println("平均年龄:" + averageAge);
}//下面定义若干个static方法,convertToIntegerList()、filterAdults()、calculateAverage()
本文转载自: 掘金