Java基础函数式编程

article/2025/11/5 21:02:20

本篇博文目录:

      • 前言
      • 1.什么是函数式接口
      • 2.函数式编程
        • (1) 使用Lambda表达式
        • (2) Lambda表达式的进一步简化
        • (3) Java内置函数式接口
      • 3.方法引用
        • (1) 方法引用的简单使用
        • (2) 方法引用的分类
      • 4.Stream API
        • (1) 什么是Stream
        • (2) 流式操作的执行流程
        • (3) Stream的创建
        • (4) Stream中间操作
        • (5) Stream的输出

前言

📢📢📢本篇博文部分内容借鉴了以下二篇博文:
廖雪峰官方博客函数式编程:https://www.liaoxuefeng.com/wiki/1252599548343744/1255943847278976
慕课教程Streamapi:https://www.imooc.com/wiki/javalesson/streamapi.html

1.什么是函数式接口

只包含一个抽象方法的接口,称为函数式接口,也称为SAM接口,即Single Abstract Method interfaces,用@FunctionalInterface注解进行标注,常见的函数式接口如Comparator,Runnable,Callable。

例子:

@FunctionalInterface
interface A {public void test(); // 只能包含一个抽象方法
}

并且还可以使用泛型,如下:

@FunctionalInterface
interface A<T> {public void test(T t); // 只能包含一个抽象方法
}

函数式接口不仅仅只定义一个抽象方法,还可以定义

  • 函数式接口里允许定义java.lang.Object里的public方法
  • 函数式接口里允许定义静态方法
  • 函数式接口里允许定义默认方法
  • 等等

下面以 Comparator函数式接口为例:

在这里插入图片描述

  • 函数式接口里允许定义java.lang.Object里的public方法

这里的 equals(Object obj)方法来自于java.lang.Object里的public方法boolean equals(Object obj);

在这里插入图片描述

  • 函数式接口里允许定义静态方法

在这里插入图片描述

  • 函数式接口里允许定义默认方法

在这里插入图片描述

2.函数式编程

函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式,Java从JDK1.8开始支持这种风格 ( 针对函数式接口的一种简单写法 )。

(1) 使用Lambda表达式

例子:

@FunctionalInterface
interface A {public void test(int a); // 只能包含一个抽象方法
}class TestB {public static void Test(Integer num, A  a) {a.test(num);}}
  • 使用匿名内部类(以前)
 // 使用匿名内部类TestB.Test(1, new A() {@Overridepublic void test(int a) {System.out.println("【方式1】实现A接口的test方法,num="+a);}});
  • 使用Lambda表达式

在以前对于这种 函数式接口参数 我们使用匿名内部类的方式传递变量过去,从Java 8开始,我们可以用Lambda表达式进行简化。

 // 使用Lambda表达式TestB.Test(2,(int a)->{System.out.println("【方式2】实现A接口的test方法,num="+a);});

运行效果:

在这里插入图片描述

(2) Lambda表达式的进一步简化

Lambda表达式在特殊情况下还可以进一步简化,如下:

未简化前:

// 使用Lambda表达式
TestB.Test(2,(int a)->{System.out.println("【方式2】实现A接口的test方法,num="+a);
});

【简化1】参数的数据类型可以省略

TestB.Test(2,(a)->{
System.out.println("【方式2】实现A接口的test方法,num="+a);
}
);

【简化2】只有一个参数的时候可以省略()

TestB.Test(2,a->{
System.out.println("【方式2】实现A接口的test方法,num="+a);
}
);

【简化3】执行代码块中只有一条语句/只有一个return语句的时候可以省略{}

TestB.Test(2,a->
System.out.println("【方式2】实现A接口的test方法,num="+a)
);

(3) Java内置函数式接口

前文中的函数式接口都是我们自己定义的,其实Java官方给我提供了一些函数式接口,我们可以直接使用这些接口,而无需自己去定义函数式接口。

  • Java内置4大核心函数式接口

在这里插入图片描述

  • 其他接口

在这里插入图片描述

上文例子中的函数式接口对照上表来看的话,和消费性接口相对应,这里我们采用Consumer接口来试试看:

定义TestB_2类,采用 Consumer<Integer> 函数式接口:

class TestB_2 {public static void Test(Integer num, Consumer<Integer>  c) {c.accept(num);}
}

使用Lambda表达式进行调用:

 TestB_2.Test(3,(a)-> System.out.println("【方式3】实现A接口的test方法,num="+a));

运行效果:

在这里插入图片描述

其实还可以进一步简化:

 BiConsumer<String,Integer> consumer = (a, b) -> System.out.println(a+""+b);consumer.accept("【方式4】实现A接口的test方法,num=",4);

运行效果:

在这里插入图片描述

3.方法引用

(1) 方法引用的简单使用

方法引用(Method References)是一种语法糖,它本质上就是 Lambda 表达式,我们知道Lambda表达式是函数式接口的实例,所以说方法引用也是函数式接口的实例,通过 类/对象::方法 进行引用。

例子:

@FunctionalInterface
interface A {public void test(int a); // 只能包含一个抽象方法
}class TestB {public static void Test(String[] arrays, A  a) {for (int i = 0; i < arrays.length; i++) {System.out.println(arrays[i]);}}}

在前文中我们是通过Lambda表达式进行简化:

// 使用Lambda表达式
TestB.Test(2,(int a)->{
System.out.println("【方式2】实现A接口的test方法,num="+a);
});

当存在如下类时,C类下的静态方法testC(int a)和函数式接口方法 test(int a);的形参和返回值类型保持一致时,就可以通过方法引用进行简化,如下:

class C{public static void testC(int a){System.out.println("【方式5】实现A接口的test方法,num="+a);}
}

使用方法引用进行简化:

 // 使用引用TestB.Test(5,C::testC);

运行效果:

在这里插入图片描述

(2) 方法引用的分类

对于方法引用的使用,通常可以分为以下 4 种情况:

  1. 对象 :: 非静态方法:对象引用非静态方法,即实例方法;
  2. 类 :: 静态方法:类引用静态方法;
  3. 类 :: 非静态方法:类引用非静态方法;
  4. 类 :: new:构造方法引用。

分清楚实例方法,静态方法和构造方法

下面的equals(Object o)方法就是实例方法:

public final class Integer {boolean equals(Object o) {...}
}

下面的parseInt(String s)方法就是静态方法:

public final class Integer {public static int parseInt(String s) {...}
}

下面的方法就是构造方法:

public class A{public A(){}
}

情况1( 对象 :: 非静态方法 ):

例子:

Consumer<String> consumer = System.out::println;
consumer.accept("【情况1】( 对象 :: 非静态方法 )方法引用");

对应的Lambda表达式如下:

 Consumer<String> consumer2 = m -> System.out.println(m);

运行效果:

在这里插入图片描述

System.out为对象:

在这里插入图片描述
println为实例方法:
在这里插入图片描述

情况2( 类 :: 静态方法 ):

例子:

Comparator<Integer> cm= Integer::compare;
System.out.println("【情况2】( 对象 :: 静态方法 )方法引用,100和99做比较:"+cm.compare(100, 99));

Lambda表达式如下:

 Comparator<Integer> cm2 = (x, y) -> Integer.compare(x, y);

运行效果:

在这里插入图片描述

Integer为类:

在这里插入图片描述

compare为静态方法:
在这里插入图片描述

情况3( 类 :: 非静态方法 ):

例子:

Comparator<String> cmStr = String::compareTo;
System.out.println("【情况3】( 对象 :: 非静态方法 )方法引用,字符串a和b做比较:"+cmStr.compare("a", "b"));

Lambda表达式如下:

Comparator<String> cmStr2 = (s1, s2) -> s1.compareTo(s2);

运行效果:

在这里插入图片描述

String为类:

在这里插入图片描述
compareTo为实例方法:

在这里插入图片描述

分清楚compareTo()方法和compare()方法的区别:

  • compareTo(Object o)方法是java.lang.Comparable接口中的方法,当需要对某个类的对象进行排序时,该类需要实现Comparable接口的,必须重写public int compareTo(T o)方法。
  • compare(Object o1,Object o2)方法是java.util.Comparator接口的方法,它实际上用的是待比较对象的compareTo(Object o)方法。

备注:compareTo()方法和compare()方法的区别更加详细解释,请参照这篇博文:java中compareTo和compare方法之比较

根据上面的理解我们来分析一下"a"和"b"是怎么进行比较的:

根据上文中二者的区别,可知使用 String::compareTo 表示采用String的排序操作,而 cmStr.compare("a", "b") 表示调用 "a"compareTo("b") 进行比较。

如下图所示:
在这里插入图片描述
"a" 从哪里来的呢?,其实这个 "a" 就是this本身,而 this.value = 'a':

在这里插入图片描述

情况4( 类 :: new ):

例子:

class Student{private String name;public Student(String name) {this.name = name;}public String getName() {return name;}
}

方法引用:

Function<String,Student> function = Student::new;
Student zs = function.apply("张三");
System.out.println("【情况4】( 类 :: new )方法引用,获取学生姓名:"+zs.getName());

Lambda表达式如下:

Function<String, Student> function2 = (name) -> new Student(name);

运行效果:

在这里插入图片描述

Student为类:

在这里插入图片描述

Function<String,Student> 这里的String对应Student的构造方法的参数类型

在这里插入图片描述

对上文的说法进行验证( 确实如此 ):

在这里插入图片描述

4.Stream API

(1) 什么是Stream

Java从8开始,不但引入了Lambda表达式,还引入了一个全新的流式API:Stream API。它位于java.util.stream包中,Stream是数据渠道,用于操作数据源所生成的元素序列(数据源可以无限大),它可以实现对集合(Collection)的复杂操作,例如查找、替换、过滤和映射数据等操作。

这个 Stream 不同于java.io的 InputStreamOutputStream ,它代表的是任意Java对象的序列。两者对比如下:

java.iojava.util.stream
存储顺序读写的byte或char顺序输出的任意Java对象实例
用途序列化至文件或网络内存计算/业务逻辑

这个 StreamList 也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。
换句话说,List的用途是操作一组已存在的Java对象,而Stream实现的是惰性计算,两者对比如下:

java.util.Listjava.util.stream
元素已分配并存储在内存可能未分配,实时计算
用途操作一组已存在的Java对象惰性计算

(2) 流式操作的执行流程

流式操作通常分为以下 3 个步骤:

  1. 创建Stream对象:通过一个数据源(例如集合、数组),获取一个流;
  2. 中间操作:一个中间的链式操作,对数据源的数据进行处理(例如过滤、排序等),直到执行终止操作才执行;
  3. 终止操作:一旦执行终止操作,就执行中间的链式操作,并产生结果。

在这里插入图片描述

(3) Stream的创建

通过Stream.Of()进行创建

第一种创建Stream的方式是直接用Stream.of()静态方法,传入可变参数即创建了一个能输出确定元素的Stream:

例子:

   Stream<String> strStream = Stream.of("张三", "李四", "王五");

基于数组或Collection

第二种创建Stream的方法是基于一个数组或者Collection,这样该Stream输出的元素就是数组或者Collection持有的元素:

例子:

  • 使用数组创建Stream
// 使用数组创建Stream
Stream<String> arrayStream = Arrays.stream(new String[]{"张三", "李四", "王五"});
  • 使用集合创建Stream
// 使用集合创建Stream
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
Stream<String> listStream = list.stream();

基于Supplier

创建Stream还可以通过Stream.generate()方法,它需要传入一个Supplier对象,基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列

例子:

先创建Supplier对象

class NameSupplier implements Supplier<String> {String name = "学生";int count = 0;@Overridepublic String get() {count++;return name+count;}
}

基于Supplier对象创建Stream

 Stream<String> supplierStream = Stream.generate(new NameSupplier());

其他方法

创建Stream的第三种方法是通过一些API提供的接口,直接获得Stream。

例如,Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:

        try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {} catch (IOException e) {e.printStackTrace();}

另外,正则表达式的Pattern对象有一个splitAsStream()方法,可以直接把一个长字符串分割成Stream序列而不是数组:

 Pattern p = Pattern.compile("\\s+");Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");

IntStream、LongStream和DoubleStream

对于一些基本数据类型使用Stream( 在Stream中使用基本数据类型需要拆箱,装箱这样会影响Stream的执行效率 ),可以使用Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream,它们的使用方法和范型Stream没有大的区别,设计这三个Stream的目的是提高运行效率

例子:

IntStream intStream = IntStream.of(1, 2, 3, 4);
LongStream longStream = Arrays.stream(new long[]{14946161, 252525, 23135666});
DoubleStream doubleStream = Arrays.stream(new double[]{25366.33, 23256.22, 466.33});

Stream.forEach(Consumer<? super T> action)用于内部遍历Stream的数据

根据forEach(Consumer<? super T> action)的源码可知,是一个函数式接口(Java内置的消费性函数式接口),所以这里
我们可以使用Lambda进行简化:

Stream<String> strStream = Stream.of("张三", "李四", "王五");
System.out.println("---------------Stream.of()静态方法------------------");
strStream.forEach((m)-> System.out.println(m));

当然我们还可以再进一步简化,就是使用方法引用,System.out为对象,println为实例方法:

Stream<String> strStream = Stream.of("张三", "李四", "王五");
System.out.println("---------------Stream.of()静态方法------------------");
strStream.forEach(System.out::println);

(4) Stream中间操作

Stream提供的常用操作有:

操作方法
转换操作:map(),filter(),sorted(),distinct();
合并操作:concat(),flatMap();
并行处理:parallel();
聚合操作:reduce(),collect(),count(),max(),min(),sum(),average();
其他操作:allMatch(), anyMatch(), forEach()。

下面只是简单的介绍几种Stream常用的操作:

使用Stream.map()

Stream.map()是Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream。

所谓map操作,就是把一种操作运算,映射到一个序列的每一个元素上。例如,对x计算它的平方,可以使用函数f(x) = x * x。我们把这个函数映射到一个序列1,2,3,4,5上,就得到了另一个序列1,4,9,16,25:
在这里插入图片描述
上文例子的代码如下:

 IntStream intStream = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);intStream.map(n -> n * n).forEach(System.out::println);

运行效果:

在这里插入图片描述
当Strem进行了中间操作后就不能再对Stream进行操作了:

Stream只能被消费一次,当其调用了终止操作后便说明其已被消费掉了。 如果还想重新使用,可考虑在原始数据中重新获得。

在这里插入图片描述

运行效果:

在这里插入图片描述

map的参数为函数式接口

根据 IntStream map(IntUnaryOperator mapper);的源码可知IntStream.map的参数为IntUnaryOperator :
在这里插入图片描述

根据IntUnaryOperator 源码可知IntUnaryOperator 为函数式接口,抽象方法为 int applyAsInt(int operand);

在这里插入图片描述

采用匿名内部类方式如下:

        IntStream intStream = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);intStream.map(new IntUnaryOperator() {@Overridepublic int applyAsInt(int operand) {return operand *operand;}}).forEach(System.out::println);

通过Lambda表达式进行简化就是我们上文中的写法:

在这里插入图片描述
当然我们也可以使用方法引用( 由于使用方法引用需要创建静态方法比较麻烦,所以上文采用Lambda表达式即可 ):

在这里插入图片描述

我们再来看看Stream.map的源码:

同样的Stream.map的参数也为函数式接口,并且为内置四大核心函数式接口中的Fuction接口

在这里插入图片描述

例子:

  Stream<String> stream = Stream.of("A ","B ","C ","D ");stream.map(String::toLowerCase).map(String::trim).forEach(System.out::println);

运行效果:

在这里插入图片描述

使用Stream.filter()

所谓filter()操作,就是对一个Stream的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream。

例如,我们对1,2,3,4,5这个Stream调用filter(),传入的测试函数f(x) = x % 2 != 0用来判断元素是否是奇数,这样就过滤掉偶数,只剩下奇数,因此我们得到了另一个序列1,3,5:

在这里插入图片描述
上文例子中的代码如下:

  IntStream intStream = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
intStream.filter(x -> x % 2 != 0).forEach(System.out::println);

运行效果:

在这里插入图片描述

根据 IntStream filter(IntPredicate predicate); 源码可知,filter的参数为IntPredicate:
在这里插入图片描述
根据IntPredicate源码可知,IntPredicate为函数式接口,抽象方法为boolean test(int value);:
在这里插入图片描述

使用Stream.reduce()

map()和filter()都是Stream的转换方法,而Stream.reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。

例子:

        IntStream intStream1 = IntStream.of(1, 2, 3, 4);int reduce = intStream1.reduce(new IntBinaryOperator() {@Overridepublic int applyAsInt(int left, int right) {return left + right;}}).getAsInt();System.out.println(reduce);

运行效果:

在这里插入图片描述

根据IntStream.reduce(IntBinaryOperator op);源码可知,IntStream.reduce的参数为IntBinaryOperator:

在这里插入图片描述
根据IntBinaryOperator的源码可知IntBinaryOperator为函数式接口,抽象方法为 int applyAsInt(int left, int right);

在这里插入图片描述

所以我们可以使用Lambda表达式进行简化:

 IntStream intStream1 = IntStream.of(1, 2, 3, 4);
int reduce = intStream1.reduce((left,right)->left+right).getAsInt();

上面例子中如果Stream中没有数据,会发生什么呢?

在这里插入图片描述
运行效果:

在这里插入图片描述

我们可以通过如下操作进行避免:

 IntStream intStream1 = IntStream.of();OptionalInt optionalInt = intStream1.reduce((left, right)->left+right);int asInt = 0;if (optionalInt.isPresent()) {asInt = optionalInt.getAsInt();}System.out.println(asInt == 0? "Stream中没有任何数据":asInt);

运行效果:

在这里插入图片描述

聚合操作是真正需要从Stream请求数据的,对一个Stream做聚合计算后,结果就不是一个Stream,而是一个其他的Java对象

NumSupplier类:

class NumSupplier implements Supplier<Long> {long num = 0;@Overridepublic Long get() {++num;return num;}}

测试代码:

 Stream<Long> s1 = Stream.generate(new NumSupplier());Stream<Long> s2 = s1.map(n -> n * n);Stream<Long> s3 = s2.map(n -> n - 1);Stream<Long> s4 = s3.limit(10);Long reduce = s4.reduce(0L, (left, right) -> left + right);System.out.println(reduce);

运行效果:

我们对s4进行reduce()聚合计算,会不断请求s4输出它的每一个元素。因为s4的上游是s3,它又会向s3请求元素,导致s3向s2请求元素,s2向s1请求元素,最终,s1从Supplier实例中请求到真正的元素,并经过一系列转换,最终被reduce()聚合出结果。

在这里插入图片描述

使用Stream.limit(long maxSize)

Stream的limit方法可以对数据进行截断,使其元素不超过给定数量。

例子:

   IntStream intStream = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);intStream.limit(5).forEach(System.out::println);

运行效果:
在这里插入图片描述

使用Stream.sorted()进行排序

关于排序中间操作,有下面几个常用方法:

  • sorted():产生一个新流,其中按照自然顺序排序;
  • sorted(Comparator com):产生一个新流,其中按照比较器顺序排序。

例子:

      List<Integer> integers = Arrays.asList(1, 69, 50, 700, 36, 92);System.out.println("按自然排序【升序】");integers.stream().sorted().forEach(System.out::println);System.out.println("按降序排序【降序】");integers.stream().sorted((num1,num2)-> - Integer.compare(num1,num2)).forEach(System.out::println);

运行效果:

在这里插入图片描述

使用Stream.distinct()去重

例子:

  Arrays.asList("A", "B", "A", "C", "B", "D").stream().distinct().forEach(System.out::println);

运行效果:

在这里插入图片描述

使用Stream.skip()进行截取

例子:

  Arrays.asList("A", "B", "C", "D", "E", "F").stream().skip(2) // 跳过A, B.limit(3) // 截取C, D, E.forEach(System.out::println); // [C, D, E]

运行效果:

在这里插入图片描述

使用Stream.concat()合并

例子:

  Stream<String> s1 = Arrays.asList("A", "B", "C").stream();Stream<String> s2 = Arrays.asList("D", "E").stream();// 合并:Stream<String> s = Stream.concat(s1, s2);s.forEach(System.out::println); // [A, B, C, D, E]

运行效果:
在这里插入图片描述

使用Stream.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,就可以使用flatMap():

Stream<Integer> i = s.flatMap(list -> list.stream());

因此,所谓flatMap(),是指把Stream的每个元素(这里是List)映射为Stream,然后合并成一个新的Stream:
在这里插入图片描述

使用Stream.parallel()并行

通常情况下,对Stream的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理Stream的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。

把一个普通Stream转换为可以并行处理的Stream非常简单,只需要用parallel()进行转换:

 Stream<String> s = Arrays.asList("A", "B", "C").stream();s.parallel() // 变成一个可以并行处理的Stream.sorted() // 可以进行并行排序.forEach(System.out::println);

运行效果:

经过parallel()转换后的Stream只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。

在这里插入图片描述

(5) Stream的输出

输出为List

 Stream<String> stream = Stream.of("Apple ", "", null, "Pear", "  ", "Orange");
List<String> list = stream.filter(s -> s != null).map(String::trim).filter(k->!(k.isEmpty())).collect(Collectors.toList());System.out.println(list);

运行效果:
在这里插入图片描述

输出为数组

 Stream<String> stream = Stream.of("Apple ", "", null, "Pear", "  ", "Orange");String[] arrays = stream.filter(s -> s != null).map(String::trim).filter(k -> !(k.isEmpty())).toArray(new IntFunction<String[]>() {@Overridepublic String[] apply(int value) {return new String[value];}});for (String str : arrays) {System.out.println(str);}

运行效果:

在这里插入图片描述
通过Lambda表达式进行简化:

Stream<String> stream = Stream.of("Apple ", "", null, "Pear", "  ", "Orange");String[] arrays = stream.filter(s -> s != null).map(String::trim).filter(k -> !(k.isEmpty())).toArray((value) ->new String[value]);for (String str : arrays) {System.out.println(str);}

通过方法引用进行简化:

Stream<String> stream = Stream.of("Apple ", "", null, "Pear", "  ", "Orange");String[] arrays = stream.filter(s -> s != null).map(String::trim).filter(k -> !(k.isEmpty())).toArray(String[]::new);for (String str : arrays) {System.out.println(str);}

输出为Map

toMap的二个参数分别为key和value,从字符串中映射的key和value。

 Stream<String> stream = Stream.of("1:Apple ", "", null, "2:Pear", "  ", "3:Orange");Map<String,String> map =  stream.filter(s -> s != null).map(String::trim).filter(k -> !(k.isEmpty())).collect(Collectors.toMap(new Function<String, String>() {@Overridepublic String apply(String s) {return  s.substring(0, s.indexOf(':'));}}, new Function<String, String>() {@Overridepublic String apply(String s) {return s.substring(s.indexOf(':') + 1);}}));System.out.println(map);

运行效果:

在这里插入图片描述

通过Lambda表达式进行简化:

  Stream<String> stream = Stream.of("1:Apple ", "", null, "2:Pear", "  ", "3:Orange");Map<String,String> map =  stream.filter(s -> s != null).map(String::trim).filter(k -> !(k.isEmpty())).collect(Collectors.toMap(s->s.substring(0, s.indexOf(':')), s->s.substring(s.indexOf(':') + 1 )));System.out.println(map);

分组输出

例子:

 List<String> list = Arrays.asList("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");Map<String, List<String>> groups = list.stream().collect(Collectors.groupingBy(new Function<String, String>() {@Overridepublic String apply(String s) {return s.substring(0, 1);}}, Collectors.toList()));System.out.println(groups);

运行效果:

在这里插入图片描述

使用Lambda简化:

分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组,第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List。

 List<String> list = Arrays.asList("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");Map<String, List<String>> groups = list.stream().collect(Collectors.groupingBy(s->s.substring(0, 1), Collectors.toList()));System.out.println(groups);

http://chatgpt.dhexx.cn/article/55lWZLPf.shtml

相关文章

Java8新特性【函数式编程API、新时间日期处理API、Optional容器类】总结

文章目录 1、Lambda表达式1.1什么是Lambda表达式1.2从匿名类到 Lambda 的转换1.3Lambda表达式语法 2、函数式接口2.1什么是函数式接口2.2自定义函数式接口2.3内置核心函数式接口2.4接口中常用的默认方法 3、方法引用与构造器引用3.1 推荐用法3.2 基本格式3.3 语法详解(了解)3.3…

一文带你入门 Java 函数式编程

Java 在最开始是不支持函数式编程的&#xff0c;想来也好理解&#xff0c;因为在 Java 中类 Class 才是第一等公民&#xff0c;这就导致在 Java 中实现编程不是件那么容易的事儿&#xff0c;不过虽然难&#xff0c;但是结果我们也已经知道了&#xff0c;在 Java 8 这个大版本里…

Oracle数据库 存储过程入门

oracle存储过程:简单入门 一、定义 存储过程是一组为了完成特定功能的SQL语句&#xff0c;经编译后存储在数据库中。点击查看优缺点。二、存储过程简单入门 ***第一个存储过程&#xff1a;打印hello word, my name is stored procedure内容*** create or replace procedure m…

数据库储存过程超简单实例

网上看了半天都没找到一个完整储存过程从创建到调用的实例,于是自己写了一个简单的实例. 数据库创建存储过程,定义个函数 格式如下,开头DELIMITER //和结尾/DELIMITER 和BEGIN 和 END 是固定格式 定了一个叫test2()的方法(在mapper.xml中会指定这个函数名),in表示入参,varc…

DM8达梦数据库存储过程函数使用

DM8数据库的过程函数的编写主要分为4个部分&#xff1a;过程头部分&#xff0c;声明定义部分&#xff0c;执行部分和异常处理部分。在编写方面&#xff0c;过程和函数的主要区别还是函数可以返回一个值&#xff0c;但是过程没有。下面就从这4个部分来分别介绍过程的编写和一些常…

数据库:存储过程实验

一、实验目的及要求 目的 掌握存储过程的编写与调用 要求 掌握存储过程的编写&#xff1b;掌握存储过程的调用 二、实验条件 安装有SQL Server2014数据库的计算机 三、实验内容 使用事务、锁和游标&#xff1b;编写和使用存储过程&#xff1b;使用触发器 四、实验结果…

达梦数据库存储过程注意事项

引言&#xff1a;达梦数据库是一款国产数据库&#xff0c;在语法使用和函数方面和MySQL&#xff0c;Oracle有着很多相似的地方。但是也有一 些细微的区别。 1、先看一下达梦数据库的存储过程模板&#xff1a; CREATE OR REPLACE FUNCTION getName() AS OR IS DECLARE ... BEGI…

MySQL数据库-存储过程详解

存储过程简单来说&#xff0c;就是为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批件&#xff0c;虽然它们的作用不仅限于批处理。在我看来&#xff0c; 存储过程就是有业务逻辑和流程的集合&#xff0c; 可以在存储过程中创建表&#xff0c;更新数据&#xff0c…

EXTJS5 入门指南

EXTJS5带领EXTJS步入了新的时代&#xff0c;Ext JS 5已经不再支持IE6、IE7和其他旧版本的浏览器了&#xff0c;这样可以显著减少跨整个框架的逻辑和样式设置。再加上额外的优化&#xff0c;Ext JS 5已经为企业级的Web应用程序迈出了惊人的一步。 EXTJS5不在和EXTJS4一样&#x…

ExtJS-入门(转载自http://www.blogjava.net/puras/archive)

2010 - 01 - 13 缩略显示 ExtJS-入门&#xff08;转载自http://www.blogjava.net/puras/archive&#xff09; 文章分类:Web前端 在ExtJS里最常用的,应该就是Ext.onReady这个方法了, 而且它也可能是你学习ExtJS所接触的第一个方法,这个方法在当前的DOM加载完毕后自动调用,保证…

Ext JS 6学习文档–第1章–ExtJS入门指南

Ext JS 入门指南 前言 本来我是打算自己写一个系列的 ExtJS 6 学习笔记的&#xff0c;因为 ExtJS 6 目前的中文学习资料还很少。google 搜索资料时找到了一本国外牛人写的关于 ExtJS 6 的电子书 [Ext JS 6 By Example]。这份资料在 PACKT 上卖 35.99 刀的&#xff0c;当然了万…

Extjs——初步学习

最近在系统学习Extjs框架&#xff0c;从刚一开始接触Extjs到现在发现对Extjs越来越喜欢了。刚开始只是想在页面上实现一个展示大量图片的功能&#xff0c;就像在线订餐系统展示菜单的效果那样&#xff0c;每幅图片上都有一些必要的信息、动作、链接等。效果如下图&#xff1a; …

Extjs基础(一)

1.1基础学习 说明&#xff1a; 本示例的所有代码均在extjs6.2版本上测试通过,学习内容来源于官方文档和自己的一些见解。 1.1.1window组件 简单的一个window面板&#xff1a; title: 窗口标题,height: 220, //可以使用百分比width: 220, html: 内容部分,resizable: true, //…

ExtJS基础入门

公司需要用ExtJS搭建系统框架&#xff0c;然后&#xff0c;这个很老了&#xff0c;没有用过 。 开始进行时候一脸懵逼&#xff0c;因为搜索了相关的知识&#xff0c;面临如下问题&#xff1a; 1.版本太多&#xff0c;从一到六&#xff0c;不知从何入手 2.提供的教程和视频都…

extjs初学者教程

layout 1.面板 (1)类结构 Ext.Base Ext.AbstractComponent Ext.Component Ext.container.AbstractContainer Ext.container.Container Ext.panel.AbstractPanel …

ext.js入门

序言&#xff1a;extjs 是一种OOP语言&#xff0c;可以按照学习Java 的过程来进行学习&#xff0c;可以类比 Java中的图像界面JWT来进行学习。 工具 这些是sencha提供的用于Ext JS应用程序开发的工具&#xff0c;主要用于生产级别。Sencha Cmd Sencha CMD是一个提供Ext JS代码…

EXTJS入门教程及其框架搭建

EXTJS是一个兼容AJAX的前台WEB UI的框架&#xff0c;在普通的HTML文件的 BODY 元素中无须写任何HTML代码&#xff0c;就能产生相应的表格等元素。 原创不易&#xff0c;转载请注明出处&#xff1a;EXTJS入门教程及其框架搭建 代码下载地址:http://www.zuidaima.com/share/17244…

EXTJS详细教程

布局和容器 普通布局 Ext.create(Ext.panel.Panel, {renderTo: Ext.getBody(),width: 400,height: 300,title: Container Panel,items: [{xtype: panel,title: Child Panel 1,height: 100,width: 75%}, {xtype: panel,title: Child Panel 2,height: 100,width: 75%}] });列布…

国嵌视频,买了就是坑

国嵌买视频的&#xff0c;更新慢&#xff0c;谁敢抱怨&#xff0c;踢你&#xff0c;锁你账号&#xff0c;真无语&#xff0c;安卓没录完&#xff0c;3月拖到6月&#xff0c;拖到10月&#xff0c;引起公愤了&#xff0c;就说送路由视频&#xff0c;结果路由视频还没录呢&#xf…

【我爱嵌入式】

童鞋们 自动化嵌入式复习 总结了期末考试简答题的内容 第一章 嵌入式的定义 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪&#xff0c;对功能、可靠性、成本、体积、功耗有严格要求的专用计算机系统。嵌入式系统主要由嵌入式微处理器、外围硬件设备…