Java函数式编程 ¶
基础使用 ¶
1String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
2Arrays.sort(array,(o1,o2)->o2.compareTo(o1));传入方法 ¶
方法签名和接口一致时,在这里,方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系
1public static void main(String[] args) {
2 String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
3 Arrays.sort(array, Main::cmp);
4}
5
6//
7static int cmp(String s1, String s2) {
8 return s1.compareTo(s2);
9}构造方法引用
流式API ¶
全新的流式API:Stream API,位于Java.util.stream包
这个Stream不同于Java.io的InputStream和OutputStream,它代表的是任意Java对象的序列
这个Stream和List也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的,惰性计算
1Stream<BigInteger> naturals = createNaturalStream(); // 不计算
2Stream<BigInteger> s2 = naturals.map(BigInteger::multiply); // 不计算
3Stream<BigInteger> s3 = s2.limit(100); // 不计算
4s3.forEach(System.out::println); // 计算惰性计算的特点是:一个Stream转换为另一个Stream时,实际上只存储了转换规则,并没有任何计算发生
Stream API的基本用法就是:创建一个Stream,然后做若干次转换,最后调用一个求值方法获取真正计算的结果
1int result = createNaturalStream() // 创建Stream
2 .filter(n -> n % 2 == 0) // 任意个转换
3 .map(n -> n * n) // 任意个转换
4 .limit(100) // 任意个转换
5 .sum(); // 最终计算结果创建Stream ¶
Stream.of:
Stream<String> stream = Stream.of("A", "B", "C", "D")java1Stream.of("A","B","C").forEach(System.out::println);基于数组或Collection:
Arrays.stream(),collection.stream()java1Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" }); 2Stream<String> stream2 = List.of("X", "Y", "Z").stream();基于Supplier:Stream.generate()方法,它需要传入一个
Supplier对象;基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列基本类型:
IntStream、LongStream和DoubleStream这三种使用基本类型的Stream
Stream.map() ¶
map()方法用于将一个Stream的每个元素映射成另一个元素并转换成一个新的Stream;
可以将一种元素类型转换成另一种元素类型。
1//字符串转化为日期,并打印
2SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
3List.of("2021-12-12","2021-12-13","2021-12-14")
4 .stream()
5 .map(n->sdf.parse(n))
6 .forEach(System.out::println);Stream.flatMap() ¶
flatMap()对流扁平化处理
1private static void streamTest4() {
2 List<String> list = new ArrayList<>();
3 list.add("123,234");
4 list.add("3456");
5 list.add("456,789");
6
7 Set<String> strings = list.stream().map(e -> e.split(","))
8 .flatMap(Arrays::stream)
9 .collect(Collectors.toSet());
10
11 // 输出:[123, 234, 3456, 456, 789]
12 System.out.println(strings);
13}解释:map 操作获得一个 String[] 流,然后通过 flatMap 将 String[] 生成的新流的新流
Stream.filter() ¶
filter()操作,就是对一个Stream的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream
filter()方法接收的对象是Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件
1// 过滤成绩小于15的Person
2Person p1=new Person("张三",12);
3Person p2=new Person("李四",21);
4Person p3=new Person("王五",35);
5List.of(p1,p2,p3).stream()
6 .filter(n->n.score>15)
7 .forEach(System.out::println);Stream.reduce() ¶
reduce()是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果
reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果
1int sum = 0;
2for (n : stream) {
3 sum = (sum, n) -> sum + n;
4}
5
6// 将配置文件的每一行配置通过map()和reduce()操作聚合成一个Map<String, String>
7List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
8Map<String,String> map=props.stream()
9 .map((kv)->{
10 String[] ss = kv.split("\\=", 2);
11 return Map.of(ss[0],ss[1]);
12 })
13 .reduce(new HashMap<>(),(o,v)->{
14 o.putAll(v);
15 return o;
16 });
17map.forEach((k,v)->{
18 System.out.println(k+":"+v);
19});
20
21//分组后根据某个属性分组,然后每组再次聚合,自定义收集器,返回Map
22Map<String,BigDecimal> res=list.stream().collect(
23 Collectors.groupingBy(Material::getType,
24 Collectors.reducing(BigDecimal.ZERO, Material::getMoney, BigDecimal::add)));
25
26
27//对 bigdecimal 求和
28BigDecimal bigDecimal = decimalList.stream().filter(Objects::nonNull).reduce(BigDecimal.ZERO, (res,v)->{
29 return res.add(v);
30});输出集合 ¶
输出为List:调用
collect()并传入Collectors.toList()对象,类似有Collectors.toSet()输出集合java1//输出list,并过滤掉无用元素 2List<String> list=Stream.of("Apple", "", null, "Pear", " ", "Orange") 3 .filter(s -> s != null && !s.isBlank()) 4 .collect(Collectors.toList()); 5list.forEach((v)->{ 6 System.out.println(v); 7});输出为数组:
toArray(String[]::new)java1List<String> list = List.of("Apple", "Banana", "Orange"); 2String[] array = list.stream().toArray(String[]::new);输出为Map
java1Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft"); 2Map<String, String> map = stream 3 .collect(Collectors.toMap( 4 // 把元素s映射为key: 5 s -> s.substring(0, s.indexOf(':')), 6 // 把元素s映射为value: 7 s -> s.substring(s.indexOf(':') + 1)));分组输出:分组输出使用
Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,第二个是分组的valuejava1// 根据classID,分组,返回Map(Integer,List<Student>) 2List<Student> studentList=new ArrayList<>(); 3for (int i=1;i<10;i++){ 4 Student s1=new Student(20%i,"张"+i); 5 studentList.add(s1); 6} 7Map<Integer, List<Student>> groups=studentList.stream() 8 .collect(Collectors.groupingBy(stu->stu.classId,Collectors.toList())); 9 10groups.forEach((k,v)->{ 11 System.out.print(k+":"); 12 v.forEach((lv)->{ 13 System.out.print(lv+","); 14 }); 15 System.out.println(); 16});anyMatch表示,判断的条件里,任意一个元素成功,返回trueallMatch表示,判断条件里的元素,所有的都是,返回truenoneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回truejava1List<String> strs = Arrays.asList("a", "a", "a", "a", "b"); 2 boolean aa = strs.stream().anyMatch(str -> str.equals("a")); 3 boolean bb = strs.stream().allMatch(str -> str.equals("a")); 4 boolean cc = strs.stream().noneMatch(str -> str.equals("a")); 5 long count = strs.stream().filter(str -> str.equals("a")).count(); 6 System.out.println(aa);// TRUE 7 System.out.println(bb);// FALSE 8 System.out.println(cc);// FALSE 9 System.out.println(count);// 4
collectingAndThen() ¶
Collectors.collectingAndThen(Collector下游,函数完成器),先进行结果集的收集,然后将收集到的结果集进行下一步的处理
第一个参数是Collector接口的子类,所以还是对于Collector的处理,Collectors工具类里面的toList()、toSet()、joining()、mapping()、collectingAndThen()等几乎所有的方法都可以使用
第二个参数是一个Function函数,Function函数是这样的:R apply(T t),他调用的ArrayList的有参构造方法
1// list去重
2//将集合放到TreeSet中,然后再将TreeSet转为List, 其中TreeSet要传入一个根据哪个属性进行比较的比较器,然后使用public ArrayList(Collection<? extends E> c)将TreeSet放入构造器中生成List
3collect = personList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()->
4 new TreeSet<>(Comparator.comparing(e->e.name+e.score))
5), ArrayList::new));
6
7//创建不变map
8personList.stream().collect(Collectors.collectingAndThen(Collectors.toMap(e ->
9 e.score+e.name, e -> e.score
10), HashMap::new));Collector与Collectors ¶
Collector是专门用来作为Stream的collect方法的参数的
Collectors是作为生产具体Collector的工具类
Collector
Collector主要包含五个参数
1/*
2* T 入参类型
3* A 容器类型
4* R 结果类型
5*/
6public interface Collector<T, A, R> {
7 /*
8 * supplier参数用于生成结果容器,容器类型为A
9 * 返回一个结果为空的 Supplier,也就是一个无参数函数,在调用时它会创建一个空的累加器实例,供数据收集过程使用
10 */
11 Supplier<A> supplier();
12
13 /*
14 * accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作
15 * 返回执行归约操作的函数,当遍历到流中第n个元素时,
16 * 这个函数执行时会有两个参数:保存归约结果的累加器(已收集了流中的前 n-1 个项目),还有第n个元素本身
17 * 该函数将返回 void ,因为累加器是原位更新,即函数的执行改变了它的内部状态
18 */
19 BiConsumer<A, T> accumulator();
20
21 /*
22 * combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
23 * combiner 方法会返回一个供归约操作使用的函数,
24 * 它定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并
25 */
26 BinaryOperator<A> combiner();
27
28 /* finisher用于将之前整合完的结果R转换成为A
29 * 在遍历完流后, finisher 方法必须返回在累积过程的最后要调用的一个函数,
30 * 以便将累加器对象转换为整个集合操作的最终结果
31 */
32 Function<A, R> finisher();
33
34 /*
35 * NORDERED —— 归约结果不受流中项目的遍历和累积顺序的影响
36 * CONCURRENT —— accumulator 函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为 UNORDERED ,那它仅在用于无序数据源时才可以并行归约
37 * IDENTITY_FINISH —— 这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器 A 不加检查地转换为结果 R 是安全的
38 */
39 Set<Characteristics> characteristics();
40}Collector拥有两个of方法用于生成Collector实例,其中一个拥有上面所有五个参数,另一个四个参数,不包括finisher
Collectors
Collectors是一个工具类,是JDK预实现Collector的工具类,它内部提供了多种Collector,我们可以直接拿来使用
通过Collector的of方法自定义Collector收集器
1//自定义collector实现,作用将BigDecimal求和,对于null忽略
2Collector<BigDecimal,BigDecimal[],BigDecimal> collector=Collector.of(
3 ()-> new BigDecimal[1],
4 (a, t) -> {
5 //这里对结果容器赋初始值
6 if (a[0] == null) {
7 a[0] = BigDecimal.ZERO;
8 }
9 //这里忽略流中null元素
10 if (t!=null){
11 a[0] = a[0].add(t);
12 }
13 },
14 (a, b) -> {
15 a[0] = a[0].add(b[0]);
16 return a;
17 },
18 a -> a[0],
19 Collector.Characteristics.IDENTITY_FINISH);
20
21list.stream().collect(collector);自定义实现类实现Collectors
1// T 是流中要收集的项目的泛型
2// A 是累加器的类型,累加器是在收集过程中用于累积部分结果的对象
3// R 是收集操作得到的对象(通常但并不一定是集合)的类型
4new Collector<BigDecimal, BigDecimal[], String>() {
5 /**
6 * 建立新的结果容器
7 */
8 @Override
9 public Supplier<BigDecimal[]> supplier() {
10 return () -> new BigDecimal[1];
11 }
12
13 /**
14 * 将元素添加到结果容器
15 */
16 @Override
17 public BiConsumer<BigDecimal[], BigDecimal> accumulator() {
18 return (a,t)->{
19 if (a[0]==null){
20 a[0]=BigDecimal.ZERO;
21 }
22 if (t != null){
23 a[0]=a[0].add(t);
24 }
25 };
26 }
27
28 /**
29 * 合并两个结果容器
30 */
31 @Override
32 public BinaryOperator<BigDecimal[]> combiner() {
33 return (a1,a2)->{
34 a1[0]=a1[0].add(a2[0]);
35 return a1;
36 };
37 }
38
39 /**
40 * 对结果容器应用最终转换
41 */
42 @Override
43 public Function<BigDecimal[], String> finisher() {
44 return (a)-> a[0].toString();
45 }
46
47 /**
48 * 定义了收集器特性
49 */
50 @Override
51 public Set<Characteristics> characteristics() {
52 return Collections.unmodifiableSet(EnumSet.of(
53 Characteristics.CONCURRENT,
54 Collector.Characteristics.UNORDERED));
55 }
56}其他 ¶
Stream提供的常用操作有:
转换操作:map(),filter(),sorted(),distinct();
合并操作:concat(),flatMap();
并行处理:parallel();
聚合操作:reduce(),collect(),count(),max(),min(),sum(),average();
其他操作:allMatch(), anyMatch(), forEach()
java提供的函数接口 ¶
Function函数 ¶
| 接口 | 描述 |
|---|---|
| Function<T,R> | 接收一个参数并返回结果的函数 |
| BiFunction<T,U,R> | 接受两个参数并返回结果的函数 |
| DoubleFunction<R> | 接收一个double类型的参数并返回结果的函数 |
| DoubleToIntFunction | 接收一个double类型的参数并返回int结果的函数 |
| DoubleToLongFunction | 接收一个double类型的参数并返回long结果的函数 |
| IntFunction<R> | 接收一个int类型的参数并返回结果的函数 |
| IntToDoubleFunction | 接收一个int类型的参数并返回double结果的函数 |
| IntToLongFunction | 接收一个int类型的参数并返回long结果的函数 |
| LongFunction<R> | 接收一个long类型的参数并返回结果的函数 |
| LongToDoubleFunction | 接收一个long类型的参数并返回double结果的函数 |
| LongToIntFunction | 接收一个long类型的参数并返回int结果的函数 |
| ToDoubleBiFunction<T,U> | 接收两个参数并返回double结果的函数 |
| ToDoubleFunction<T> | 接收一个参数并返回double结果的函数 |
| ToIntBiFunction<T,U> | 接收两个参数并返回int结果的函数 |
| ToIntFunction<T> | 接收一个参数并返回int结果的函数 |
| ToLongBiFunction<T,U> | 接收两个参数并返回long结果的函数 |
| ToLongFunction<T> | 接收一个参数并返回long结果的函数 |
Consumer消费者 ¶
| 接口 | 描述 |
|---|---|
| Consumer<T> | 提供一个T类型的输入参数,不返回执行结果 |
| BiConsumer<T,U> | 提供两个自定义类型的输入参数,不返回执行结果 |
| DoubleConsumer | 表示接受单个double值参数,但不返回结果的操作 |
| IntConsumer | 表示接受单个int值参数,但不返回结果的操作 |
| LongConsumer | 表示接受单个long值参数,但不返回结果的操作 |
| ObjDoubleConsumer<T> | 表示接受object值和double值,但是不返回任何操作结果 |
| ObjIntConsumer<T> | 表示接受object值和int值,但是不返回任何操作结果 |
| ObjLongConsumer<T> | 表示接受object值和long值,但是不返回任何操作结果 |
Predicate谓语 ¶
| 接口 | 描述 |
|---|---|
| Predicate<T> | 对给定的输入参数执行操作,返回一个boolean类型的结果(布尔值函数) |
| BiPredicate<T,U> | 对给定的两个输入参数执行操作,返回一个boolean类型的结果(布尔值函数) |
| DoublePredicate | 对给定的double参数执行操作,返回一个boolean类型的结果(布尔值函数) |
| IntPredicate | 对给定的int输入参数执行操作,返回一个boolean类型的结果(布尔值函数) |
| LongPredicate | 对给定的long参数执行操作,返回一个boolean类型的结果(布尔值函数) |
Supplier供应商 ¶
| 接口 | 描述 |
|---|---|
| Supplier<T> | 不提供输入参数,但是返回结果的函数 |
| BooleanSupplier | 不提供输入参数,但是返回boolean结果的函数 |
| DoubleSupplier | 不提供输入参数,但是返回double结果的函数 |
| IntSupplier | 不提供输入参数,但是返回int结果的函数 |
| LongSupplier | 不提供输入参数,但是返回long结果的函数 |
Operator操作员 ¶
| 接口 | 描述 |
|---|---|
| UnaryOperator<T> | 提供单个类型参数,并且返回一个与输入参数类型一致的结果 |
| BinaryOperator<T> | 提供两个相同类型参数,并且返回结果与输入参数类型一致的结果 |
| DoubleBinaryOperator | 提供两个double参数并且返回double结果 |
| DoubleUnaryOperator | 提供单个double参数并且返回double结果 |
| IntBinaryOperator | 提供两个int参数并且返回int结果 |
| IntUnaryOperator | 提供单个int参数并且返回int结果 |
| LongBinaryOperator | 提供两个long参数并且返回long结果 |
| LongUnaryOperator | 提供单个long参数并且返回long结果 |
常用操作 ¶
求最大最小值 ¶
1List<BigDecimal> decimalList = Arrays.asList(
2 new BigDecimal("1"),
3 new BigDecimal("2"),
4 new BigDecimal("3"),
5 new BigDecimal("-10"),
6 null,
7 new BigDecimal("0")
8);
9
10//过滤null,并取最大值
11Optional<BigDecimal> max = decimalList.stream().filter(Objects::nonNull).max(BigDecimal::compareTo);
12if (max.isPresent()) {
13 BigDecimal maxdecimal = max.get();
14}
15
16//过滤null,并取最小值
17Optional<BigDecimal> min = decimalList.stream().filter(Objects::nonNull).max(BigDecimal::compareTo);
18if (max.isPresent()) {
19 BigDecimal mindecimal = max.get();
20}求decimal积 ¶
1List<BigDecimal> decimalList = List.of(
2 new BigDecimal("1"),
3 new BigDecimal("2"),
4 new BigDecimal("3"),
5 null,
6 new BigDecimal("-10"),
7 new BigDecimal("0")
8);
9
10//求decimalList所有元素乘积,并忽略null元素
11String res = decimalList.stream().collect(Collector.of(
12 () -> new BigDecimal[1],
13 (a, t) -> {
14 if (a[0] == null) {
15 a[0] = BigDecimal.ONE;
16 }
17 if (t != null && t.compareTo(BigDecimal.ZERO) != 0) {
18 a[0] = a[0].multiply(t);
19 }
20 },
21 (a1, a2) -> {
22 a1[0] = a1[0].multiply(a2[0]);
23 return a1;
24 },
25 (a) -> a[0].toString()
26));切分list ¶
1List<String> arr=new ArrayList<>();
2for (int i = 0; i < 10950; i++) {
3 arr.add(String.valueOf(i));
4}
5
6int step = 100;
7
8List<List<String>> lists = Stream.iterate(0L, n -> n + 1L) //返回一个无限有序流 0123456.......
9 .limit((arr.size() + step - 1) / step) //指定最后返回的 list大小,
10 .parallel() //转换并行流
11 .map(a -> arr.stream().skip(a * step)
12 .limit(step).parallel() //转换为并行流并进行归约
13 .collect(Collectors.toList()))
14 .collect(Collectors.toList());