函数式编程(
Functional Programming
)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda
演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式。
1.Lambda
1.1 单方法接口
把只定义了单方法的接口称之为FunctionalInterface
(单接口方法),用注解@FunctionalInterface
标记。如:
-
Callable
:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Comparator
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
...
}
...
}
虽然
Comparator
接口有很多方法,但只有一个抽象方法int compare(T o1, T o2)
,其他的方法都是default
方法或static
方法。另外注意到boolean equals(Object obj)
是Object
定义的方法,不算在接口方法内。因此,Comparator
也是一个FunctionalInterface
。
1.2 Lambda
表达式,
使用Lambda
表达式,我们就可以不必编写FunctionalInterface
接口的实现类,从而简化代码:
public class LambdaDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//匿名内部类写法:
Collections.sort(list, new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
});
//lambda表达式写法:
Collections.sort(list, (String o1, String o2)->{
return o1.length()-o2.length();
});
//lambda表达式中的参数类型可以不写
Collections.sort(list, (o1,o2)->{
return o1.length()-o2.length();
});
//lambda表达式方法体中只有一句代码时,方法体的{}可以不写,如果这句话有return,也一并不写
Collections.sort(list, (o1,o2)->o1.length()-o2.length());
//lambda表达式的方法参数只有1个,那么()可以忽略不写---本案例不适合
}
}
1.3 方法引用
- 引用静态方法
除了Lambda
表达式,我们还可以直接传入方法引用。
public class Main {
public static void main(String[] args) {
String[] arr = {"Apple","Orange", "Banana"};
Arrays.sort(arr,Main::cmp);
System.out.println(Arrays.toString(arr));
}
static int cmp(String a, String b) {
return a.compareTo(b);
}
}
所谓方法引用,是指如果某个方法签名(参数和返回类型)和接口恰好一致,就可以直接传入方法引用。
因为Comparator<String>
接口定义的方法是int compare(String, String)
,和静态方法int cmp(String, String)
相比,除了方法名外,方法参数一致,返回类型相同,因此,我们说两者的方法签名一致,可以直接把方法名作为Lambda
表达式传入。
- 引用实例方法
public class Main {
public static void main(String[] args) {
String[] arr = {"Apple","Orange", "Banana"};
Arrays.sort(arr,String::compareTo);
System.out.println(Arrays.toString(arr));
}
}
实例方法也可以是因为实例方法有一个隐含的this
参数,String
类的compareTo()
方法在实际调用的时候,第一个隐含参数总是传入this
,this
也是String
,相当于静态方法:
public static int compareTo(this, String o);
2. Stream
从Java 8
开始,不但引入了Lambda
表达式,还引入了一个全新的流式API:Stream API
。它位于java.util.stream
包中。
2.1 创建Stream
Stream.of()
/**
* 创建 Stream.of
*/
public static void streamOf()
{
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.forEach(System.out::println);
}
2.2 基于数组或Collection
- 把数组变成
Stream
使用Arrays.stream()
方法。 - 对于
Collection
(List、Set、Queue等),直接调用stream()
方法就可以获得Stream
。
/**
* 创建:数组和集合
*/
public static void collectionAndArray()
{
// 使用数据
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
stream.forEach(System.out::println);
// 使用集合
Stream<Integer> stream2 = List.of(1, 2, 3, 4, 5).stream();
stream2.forEach(System.out::println);
}
2.3 基于Supplier
Stream<String> s = Stream.generate(Supplier<String> sp);
基于Supplier
创建的Stream
会不断调用Supplier.get()
方法来不断产生下一个元素,这种Stream
保存的不是元素,而是算法,它可以用来表示无限序列。
/**
* 使用Supplier
*/
public static void useSupplier()
{
Stream<Integer> stream = Stream.generate(new SupplierChild());
// limit限制数量输出
stream.limit(10).forEach(System.out::println);
}
import java.util.function.Supplier;
public class SupplierChild implements Supplier<Integer> {
int n;
public Integer get()
{
n++;
return n;
}
}
Stream
几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算。如果用List
表示,即便在int
范围内,也会占用巨大的内存。
对于无限序列,如果直接调用forEach()
或者count()
这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列。
2.4 其他方法
创建Stream
的第三种方法是通过一些API
提供的接口,直接获得Stream
。
- Files类
Files
类的lines()
方法可以把一个文件变成一个Stream
,每个元素代表文件的一行内容:
try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
...
}
- 正则表达式的Pattern对象
正则表达式的Pattern
对象有一个splitAsStream()
方法,可以直接把一个长字符串分割成Stream
序列而不是数组:
Pattern p = Pattern.compile("\\s+");
Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
s.forEach(System.out::println);
3. Stream转换
Stream.map()
是Stream
最常用的一个转换方法,它把一个Stream
转换为另一个Stream
。
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);
map
操作,把一个Stream
的每个元素一一对应到应用了目标函数的结果上。
map()
方法接收的对象是Function
接口对象,它定义了一个apply()
方法,负责把一个T
类型转换成R
类型:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
4. 过滤
Stream.filter()是Stream的另一个常用过滤方法。
/**
* Stream 过滤
*/
public static void filterStream()
{
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5).filter(n -> n % 2 == 0);
s.forEach(System.out::println);
}
filter()
方法接收的对象是Predicate
接口对象,它定义了一个test()
方法,负责判断元素是否符合条件:
@FunctionalInterface
public interface Predicate<T> {
// 判断元素t是否符合条件:
boolean test(T t);
}
filter()
除了常用于数值外,也可应用于任何Java
对象。例如,从一组给定的LocalDate
中过滤掉工作日,以便得到休息日:
/**
* Stream 过滤
*/
public static void filterStream()
{
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5).filter(n -> n % 2 == 0);
s.forEach(System.out::println);
Stream.generate(new LocalDateSupplier()).limit(10).filter(ldt -> !(ldt.getDayOfWeek()== DayOfWeek.SUNDAY || ldt.getDayOfWeek()==DayOfWeek.SATURDAY)).forEach(System.out::println);
}
import java.time.LocalDate;
import java.util.function.Supplier;
public class LocalDateSupplier implements Supplier<LocalDate> {
LocalDate start = LocalDate.of(2025, 1, 1);
int n =-1;
public LocalDate get()
{
n = n + 1;
return start.plusDays(n);
}
}
5. 聚合
Stream.reduce()
则是Stream
的一个聚合方法,它可以把一个Stream
的所有元素按照聚合函数聚合成一个结果。
reduce()
方法传入的对象是BinaryOperator
接口,它定义了一个apply()
方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果:
@FunctionalInterface
public interface BiFunction<T, U, R> {
// Bi操作:两个输入,一个输出
T apply(T t, T u);
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
...
}
- 实例
/**
* 聚合
*/
public static void reduceStream()
{
int stream = Stream.of(1, 2, 3, 4, 5).reduce(0, (m,n) -> m + n);
System.out.println(stream);
}
reduce()
操作首先初始化结果为指定值(这里是0),reduce()
对每个元素依次调用(m, n) -> m+ n
,计算过程如下:
// 计算过程:
acc = 0 // 初始化为指定值
acc = acc + n = 0 + 1 = 1 // n = 1
acc = acc + n = 1 + 2 = 3 // n = 2
acc = acc + n = 3 + 3 = 6 // n = 3
acc = acc + n = 6 + 4 = 10 // n = 4
acc = acc + n = 10 + 5 = 15 // n = 5
acc = acc + n = 15 + 6 = 21 // n = 6
acc = acc + n = 21 + 7 = 28 // n = 7
acc = acc + n = 28 + 8 = 36 // n = 8
acc = acc + n = 36 + 9 = 45 // n = 9
6. 输出集合
6.1 输出为List
把Stream
变为List
不是一个转换操作,而是一个聚合操作,它会强制Stream
输出每个元素。
把非空字符串保存到List
中:
List<Integer> list = Stream.of(1, 2, 3, 4, 5).toList();
System.out.println(list);
6.2 输出为数组
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
6.3 输出为Map
Stream
的元素收集到Map
中,就稍微麻烦一点。因为对于每个元素,添加到Map
时需要key
和value
:
Stream<String> stream = Stream.of("a:apple", "b:banana", "c:cat");
Map<String,String> map = stream.collect(Collectors.toMap(n -> n.substring(0,n.indexOf(":")), n -> n.substring(n.indexOf(":")+1)));
System.out.println(map);// {a=apple, b=banana, c=cat}
6.3 分组输出
List<String> list = List.of("apple", "banana", "cat", "orange", "dog","zero");
Map<String,List<String>> stream = list.stream().collect(Collectors.groupingBy(s -> s.substring(0,1),Collectors.toList()));
System.out.println(stream);// {a=[apple], b=[banana], c=[cat], d=[dog, duck], z=[zero], o=[orange]}
分组输出使用
Collectors.groupingBy()
,它需要提供两个函数:一个是分组的key
,这里使用s -> s.substring(0, 1)
,表示只要首字母相同的String
分到一组,第二个是分组的value
,这里直接使用Collectors.toList()
,表示输出为List
7. 排序
sorted()
Stream<String> s = Stream.of("apple", "banana", "cat", "orange", "dog","zero","duck");
s.sorted().forEach(System.out::println);
此方法要求
Stream
的每个元素必须实现Comparable
接口。如果要自定义排序,传入指定的Comparator
即可:
List<String> list = List.of("Orange", "apple", "Banana")
.stream()
.sorted(String::compareToIgnoreCase)
.collect(Collectors.toList());
8.去重
对一个Stream
的元素进行去重,没必要先转换为Set
,可以直接用distinct()
:
List.of("A", "B", "A", "C", "B", "D")
.stream()
.distinct()
.collect(Collectors.toList()); // [A, B, C, D]
9.截取
截取操作常用于把一个无限的Stream
转换成有限的Stream
,skip()
用于跳过当前Stream
的前N
个元素,limit()
用于截取当前Stream
最多前N
个元素:
List.of("A", "B", "C", "D", "E", "F")
.stream()
.skip(2) // 跳过A, B
.limit(3) // 截取C, D, E
.collect(Collectors.toList()); // [C, D, E]
10.合并
将两个Stream
合并为一个Stream
可以使用Stream
的静态方法concat()
:
Stream<String> s1 = List.of("A", "B", "C").stream();
Stream<String> s2 = List.of("D", "E").stream();
// 合并:
Stream<String> s = Stream.concat(s1, s2);
System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]
11. flatMap
如果Stream
的元素是集合:
Stream<List<Integer>> s = Stream.of(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9));
把上述Stream
转换为Stream<Integer>
,就可以使用flatMap()
:
Stream<Integer> i = s.flatMap(list -> list.stream());
┌─────────────┬─────────────┬─────────────┐
│┌───┬───┬───┐│┌───┬───┬───┐│┌───┬───┬───┐│
││ 1 │ 2 │ 3 │││ 4 │ 5 │ 6 │││ 7 │ 8 │ 9 ││
│└───┴───┴───┘│└───┴───┴───┘│└───┴───┴───┘│
└─────────────┴─────────────┴─────────────┘
│
│flatMap(List -> Stream)
│
│
▼
┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┘
12. 并行
parallel()
把一个普通Stream
转换为可以并行处理的Stream
Stream<String> s = ...
String[] result = s.parallel() // 变成一个可以并行处理的Stream
.sorted() // 可以进行并行排序
.toArray(String[]::new);
13. 其他聚合方法
-
count()
:用于返回元素个数; -
max(Comparator<? super T> cp)
:找出最大元素; -
min(Comparator<? super T> cp)
:找出最小元素。
针对IntStream
、LongStream
和DoubleStream
,还额外提供了以下聚合方法:
-
sum()
:对所有元素求和; -
average()
:对所有元素求平均数。
测试Stream
的元素是否满足以下条件:
-
boolean allMatch(Predicate<? super T>)
:测试是否所有元素均满足测试条件; -
boolean anyMatch(Predicate<? super T>)
:测试是否至少有一个元素满足测试条件。