java函数式编程
java函数式编程
函数式编程是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。
Lambda 表达式
在java中有单方法接口,即一个接口定义一个方法,如 Comparator:
String[] array = ...
Arrays.sort(array, new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
上述写法比较繁琐,从java8开始可以使用Lambda表达式
String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
Arrays.sort(array, (s1, s2) -> {
return s1.compareTo(s2);
});
在Lambda中只要写出方法定义:
Arrays.sort(array, (s1, s2) -> {
return s1.compareTo(s2);
});
s1与s2是参数,类型可以省略由编译器自动推断出。-> {}
表示方法体。
如果只有一行可以省略return
。
(s1, s2) -> { s1.compareTo(s2)}
方法引用
除了直接使用Lambda方法外还可以直接传入方法引用。
public class Main {
public static void main(String[] args) {
String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
Arrays.sort(array, String::compareTo);
System.out.println(String.join(", ", array));
}
}
构造方法调用
可以将构造方法当作参数传入,用于构建新的对象。
// 调用构造函数 `Person::new`,字符串作为参数,生成Person对象。
public class Main {
public static void main(String[] args) {
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
System.out.println(persons);
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
public String toString() {
return "Person:" + this.name;
}
}
使用 Stream 对象
流在表面上和集合很类似,但他们之间存在显著差异:
- 流并不存储数据,这些元素可能底层储存在集合中,或按需生成。
- 流的操作不会修改其数据源。而是生成新的流。
- 流的执行是惰性的。
stream 与输入输出流的区别
位于 java.util.stream
包中。这个Stream不同于java.io
中的InputStream
和OutputStream
,它表示的是Java对象的序列。
java.io | java.util.stream | |
---|---|---|
存储 | 顺序读写的byte 或char |
顺序输出的任意Java对象实例 |
用途 | 序列化至文件或网络 | 内存计算/业务逻辑 |
Stream与List也不同,List是存储在内存中的Java对象,而Stream输出的元素可能并没有预先存在内存中而是实时计算出来的。
List用于操作一组已存在的Java对象,而Stream实现的是惰性计算
java.util.List | java.util.stream | |
---|---|---|
元素 | 已分配并存储在内存 | 可能未分配,实时计算 |
用途 | 操作一组已存在的Java对象 | 惰性计算 |
例子 | List<BigInteger> list = ??? // 只能存储有限数字 |
Stream<BigInteger> naturals = createNaturalStream(); // 全体自然数 |
naturals
中全体自然数并非存储在内存中,而是通过惰性计算在用时才获得。
对全体自然数去前一百计算平方:
Stream<BigInteger> naturals = createNaturalStream(); naturals.map(n -> n.multiply(n)) // 1, 4, 9, 16, 25... .limit(100) // 截取前100个元素 .filter(n -> n % 2 == 0) // 过滤 .forEach(System.out::println); // 处理每个元素
因为是惰性计算,在一个Stream转换为另一个Stream时不会修改原Stream本身。一个Stream
转换为另一个Stream
时,实际上只存储了转换规则,并没有任何计算发生,真正的计算结果在最后获取。
Stream<BigInteger> naturals = createNaturalStream(); // 不计算
Stream<BigInteger> s2 = naturals.map(BigInteger::multiply); // 不计算
Stream<BigInteger> s3 = s2.limit(100); // 不计算
s3.forEach(System.out::println); // 计算
创建 Stream
Stream.of()
静态方法
Stream<String> stream = Stream.of("A", "B", "C", "D");
// forEach()方法相当于内部循环调用,
// 可传入符合Consumer接口的void accept(T t)的方法引用:
stream.forEach(System.out::println);
基于数组或Collection
Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
Stream<String> stream2 = List.of("X", "Y", "Z").stream();
stream1.forEach(System.out::println);
stream2.forEach(System.out::println);
将数组转为Stream
使用 Arrays.stream()
方法
对于 Collection
直接调用 stream()
方法可获得 Stream
Supplier
对象
使用Stream.generate()
方法,它需要传入一个Supplier
对象。
Stream<String> s = Stream.generate(Supplier<String> sp);
创建出的 Stream
会不断调用Supplier.get()
方法不断产生下一个元素,这种Stream
保存的不是元素而是算法,可以用于表示无限序列。
public class Main {
public static void main(String[] args) {
Stream<Integer> natual = Stream.generate(new NatualSupplier());
// 注意:无限序列必须先变成有限序列再打印:
natual.limit(20).forEach(System.out::println);
}
}
// 调用 get 方法返回全体自然数(受int范围限制)
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get() {
n++;
return n;
}
}
File.lines()
将文件变为一个 Stream 对象。
try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
...
}
基本类型
为了避免频繁的装箱、拆箱操作,Java提供了 IntStream、
LongStream和
DoubleStream 三种基本类的Stream。
// 将int[]数组变为IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// 将Stream<String>转换为LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
map
用于将一个Stream对象转换为另一个Stream对象。
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n); // 获得元素的平方
map接收的接口:
@FunctionalInterface
public interface Function<T, R> {
// 将T类型转换为R:
R apply(T t);
}
filter
对所有元素进行测试,不满足的进行过滤。
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(n -> n % 2 != 0)
.forEach(System.out::println);
filter 函数接收的是一个接口
@FunctionalInterface
public interface Predicate<T> {
// 判断元素t是否符合条件:
boolean test(T t);
}
reduce
一个聚合方法,用于把Stream的所有元素按照聚合函数集合为一个结果。
int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.reduce(0, (acc, n) -> acc + n);
// 45
计算过程
// 计算过程:
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
函数接收的接口
@FunctionalInterface
public interface BinaryOperator<T> {
// Bi操作:两个输入,一个输出
T apply(T t, T u);
}
输出
map与filter属于转换操作不会触发计算,reduce为聚合操作。
输出为List
将Stream中的元素保存到集合,这个一个聚合操作,会触发计算得到结果。
List<String> list = stream.filter(s -> s != null && !s.isBlank())
.collect(Collectors.toList());
输出为数组
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
输出为Map
需要实现两个接口,分别用于映射key与value。
Map<String, String> map = stream
.collect(Collectors.toMap(
// 把元素s映射为key:
s -> s.substring(0, s.indexOf(':')),
// 把元素s映射为value:
s -> s.substring(s.indexOf(':') + 1)));
System.out.println(map);
排序
使用 sorted()
函数。这个一个转换操作,返回一个新 Stream
List<String> list = List.of("Orange", "apple", "Banana")
.stream()
.sorted()
.collect(Collectors.toList());
List<String> list = List.of("Orange", "apple", "Banana")
.stream()
.sorted(String::compareToIgnoreCase) // 自定义排序方法,要实现Comparable接口
.collect(Collectors.toList());
去重
List.of("A", "B", "A", "C", "B", "D")
.stream()
.distinct()
截取
List.of("A", "B", "C", "D", "E", "F")
.stream()
.skip(2) // 跳过A, B
.limit(3) // 截取C, D, E
.collect(Collectors.toList()); // [C, D, E]
合并
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]
flatMap
将元素为集合的Stream
转换为一个Stream
Stream<List<Integer>> s = Stream.of(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9));
Stream<Integer> i = s.flatMap(list -> list.stream());
// 获得一个包含123456789的Stream
并行
String[] result = s.parallel() // 变成一个可以并行处理的Stream,无需其他操作即可加速
.sorted() // 可以进行并行排序
简单约简
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
Optional<T> count(Comparator<? super T> comparator)
Optional<T> findFirst()
Optional<T> findAny()
boolean anyMatch(Predicate<? super T> predicate)
boolean allMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
返回一个最终值
Optional 类型
这是一个包装器对象,要么包装了类型T的对象,要么没有包装任何对象,即null。
创建 Optional
static <T> Optional<T> of(T value) // value 为null报错
static <T> Optional<T> ofNullable(T value) // value 为null产生一个空Optional
static <T> Optional<T> empty() // 产生一个空 Optional
使用 Optional
T get()
// 返回包含的值,为null时返回other
T orElse(T other)
// 产生这个Optional的值,为null时调用other产生调用结果
T orElseGet(Supplier<? extends T> other)
// 返回包含的值,为null时抛出错误
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
// 如果不为空,将值传给consumer
void ifPresent(Consumer<? super T> consumer)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了