Java8 函数式编程

article/2025/11/5 20:47:45

文章目录

  • Java 函数式编程
    • 1. Lambda 表达式
      • 1.1 标准格式
      • 1.2 使用前提
        • 1.2.1 一个参数
        • 1.2.2 多个参数
        • 1.2.3 有返回值
      • 1.3 省略简化
      • 1.4 函数式接口
        • 1.4.1 Supplier
        • 1.4.2 Consumer
        • 1.4.3 Predicate
        • 1.4.4 Function
      • 1.5 方法引用
        • 1.5.1 对象 :: 实例方法
        • 1.5.2 类 :: 静态方法
        • 1.5.3 类 :: 实例方法
        • 1.5.4 类 :: new
    • 2. Stream 流
      • 2.1 什么是流
      • 2.2 操作分类
      • 2.3 创建方式
      • 2.4 Stream API
        • 2.4.1 遍历: foreach
        • 2.4.2 筛选: filter
        • 2.4.3 去重: distinct
        • 2.4.4 排序: sorted
        • 2.4.5 提取与组合:limit & skip
        • 2.4.6 映射: map、flatMap
        • 2.4.7 查找: find
        • 2.4.9 查找: match
        • 2.4.10 归约: reduce
        • 2.4.11 收集: collect
    • 3. Optional
      • 3.1 创建和获取
      • 3.2 判断
      • 3.3 过滤
      • 3.4 映射

Java 函数式编程

什么是函数式编程

函数式编程是一种是一种编程范式,它将计算视为函数的运算,并避免变化状态和可变数据。它是一种声明式编程范式,也就是说,编程是用表达式或声明而不是语句来完成的,即强调做什么,而不是以什么形式去做。

Lamda 表达式:

(a,b)->a+b

比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

1. Lambda 表达式

在了解 Lambda 表达式之前,先看这样一个需求:

通过 Runnable 启动一个线程,并打印:线程已启动

对于这样一个需求,有以下三种方式:

方式一

实现 Runnable 接口,如下:

class MyRunnable implements Runnable{@Overridepublic void run() {log.info("线程已启动");}
}@Test
void test_runnable_01() {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();
}

方式二

匿名内部类,如下:

@Test
void test_runnable_02() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {log.info("线程已启动");}});thread.start();
}

方式三

Lambda 表达式,如下:

@Test
void test_runnable_03() {Thread thread = new Thread(() -> {log.info("线程已启动");});thread.start();
}

可以看到使用 Lambda 表达式使得代码看起来更简单。

1.1 标准格式

匿名内部类中重写 run() 方法的代码分析:

  • 方法形式参数为空,说明调用方法时不需要传递参数
  • 方法返回值类型为 void,说明方法执行没有结果返回
  • 方法体中的内容,是我们具体要做的事情

Lambda 表达式的代码分析:

  • ():里面没有内容,可以看成是方法形式参数为空
  • ->:用箭头指向后面要做的事情
  • {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

由此可得知,组成 Lambda 表达式的三要素:形式参数、箭头、代码块。

Lambda 表达式的格式

  • 格式:(形式参数)-> { 代码块 }
  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ->:由英文中画线和大于符号组成,固定写法,代表指向动作
  • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

1.2 使用前提

Lambda表达式的使用前提:

  • 有一个接口
  • 接口中有且仅有一个抽象方法

在 Runnable 的测试中,其 run()是一个无参数的方法,接下来我们按照上述前提来创建一个参数和多个参数的接口来实现 Lambda 表达式。

1.2.1 一个参数

定义一个名为 LambdaFunction 的接口,在定义其抽象方法为 func(String str),如下:

public interface LambdaFunction {public abstract void func(String str);}

测试:

@Test
void test_Lambda_function() {Lambda_function((String str) -> {log.info(str);}, "一个形参");
}void Lambda_function(LambdaFunction function, String str) {function.func(str);
}

1.2.2 多个参数

同上,定义包含 2 个参数的抽象方法。

public interface LambdaFunction2 {public abstract void func2(String str, int i);}

测试:

@Test
void test_Lambda_function2() {Lambda_function2((String str, int i) -> {log.info(str + ": " + i);}, "多个参数", 2);
}void Lambda_function2(LambdaFunction2 function, String str, int i) {function.func(str, i);
}

1.2.3 有返回值

除了多参数,还要存在返回值的情况,如下定义:

public interface LambdaFunction3 {public abstract int add(int i1, int i2);}

测试:

@Test
void test_Lambda_function3() {int i = Lambda_function3((int i1, int i2) -> {return i1 + i2;}, 1, 2);log.info(i + "");
}int Lambda_function3(LambdaFunction3 function, int i1, int i2) {return function.add(i1, i2);
}

1.3 省略简化

Lambda 表达式可以省略简化,它的规则如下:

  • 参数类型可以省略,如果有多个参数的情况下,同时省略
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果代码块的语句只有一条,可以省略大括号和分号,包括返回值 return

因此我们可以把上面的简化,如下:

@Test
void test_Lambda_function_simplify() {Lambda_function(str -> log.info(str), "一个参数");
}@Test
void test_Lambda_function2_simplify() {Lambda_function2((str, i) -> log.info(str + ": " + i), "多个参数", 2);
}@Test
void test_Lambda_function3_simplify() {int i = Lambda_function3((i1, i2) -> i1 + i2, 1, 2);log.info(i + "");
}

1.4 函数式接口

函数式接口:有且仅有一个抽象方法的接口

Java 中的函数式编程体现就是 Lambda 表达式,所以函数式接口就是可以使用于 Lambda 使用的接口只有确保接口中有且仅有一个抽象方法,Java 中的 Lambda 才能顺利地进行推导。

如何检测一个接口是不是函数式接口呢?

@FunctionalInterface 注解,放在接口定义的上方,如果接口是函数接口,编译通过;如果不是,编译失败。

注意:

我们自己定义函数式接口的时候,@FunctionalInterface 是可选的,就算我们不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上注解。

Java 8 在 java.util.function 包下预定了大量的函数式接口供我们使用,常用如下:

  • Supplier 接口
  • Consumer 接口
  • Predicate 接口
  • Function 接口

1.4.1 Supplier

方法说明
T get()生产数据的接口

Supplier 接口被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的 get 方法就会生产什么类型的数据供我们使用。

@FunctionalInterface
public interface Supplier<T> {/*** Gets a result.** @return a result*/T get();
}

该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据,如下:

@Test
void supplier_test() {String string = getString(() -> "String");log.info(string);Integer integer = getInteger(() -> 1);log.info(integer + "");
}String getString(Supplier<String> supplier) {return supplier.get();
}Integer getInteger(Supplier<Integer> supplier) {return supplier.get();
}

1.4.2 Consumer

方法说明
void accept(T t)消费一个指定泛型的数据
default Consumer andThen(Consumer<? super T> after)消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合

Consumer 接口被称为消费型接口,与 Supplier 接口相反,它消费的数据类型由泛型指定。

① accept

@FunctionalInterface
public interface Consumer<T> {/*** 消费一个指定泛型的数据** @param t the input argument*/void accept(T t);/*** 默认方法*/default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
}

对于 accept() 方法,它是消费,如下:

@Test
void consumer_test() {consumer_accept(100, money -> log.info("本次消费: " + money));
}/*** 定义一个方法** @param money    传递一个int类型的 money* @param consumer*/
void consumer_accept(Integer money, Consumer<Integer> consumer) {consumer.accept(money);
}

② andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是一步接一步操作。例如两个步骤组 合的情况:

@Test
void consumer_andThen_test() {consumer_andThen("abcdefg",// 先转为大写str -> log.info(str.toUpperCase()),// 在转为小写str -> log.info(str.toLowerCase()));
}void consumer_andThen(String str, Consumer<String> consumer1, Consumer<String> consumer2) {consumer1.andThen(consumer2).accept(str);
}

例子

给出一个字符数组:

String[] array = { "张三,女", "李四,女", "王五,男" };

让它们按照以下格式打印:

姓名: XX 性别: XX

代码如下:

@Test
void printInfo() {String[] array = {"张三,女", "李四,女", "王五,男"};printInfo(array,str -> System.out.print("姓名: " + str.split(",")[0]),str -> System.out.println(" 性别: " + str.split(",")[1]));
}
void printInfo(String[] arr, Consumer<String> consumer1, Consumer<String> consumer2) {for (String s : arr) {consumer1.andThen(consumer2).accept(s);}
}

结果如下:

image-20220830191550994

在实际开发中,可以应用为对象。

1.4.3 Predicate

方法说明
boolean test(T t)对给定的参数进行判断(判断逻辑由 Lambda 表达式实现),返回一个布尔值
default Predicate negate()返回一个逻辑的否定,对应逻辑非( ! )
default Predicate and (Predicate other)返回一个组合判断,对应短路与(&&)
default Predicate or (Predicate other)返回一个组合判断,对应短路或(||)

① test()

可以用来判断某个值是否满足条件,如下:

/*** 判断字符串的长度是否满足条件*/
@Test
void predicate_test() {boolean predicate = predicate_test("predicate", str -> str.length() > 10);System.out.println(predicate);
}boolean predicate_test(String str, Predicate<String> predicate) {boolean b = predicate.test(str);return b;
}

② and() && or()

这两个是相对的,的关系,会短路,如下:

/*** 判断字符串是否以 A 开头,并且 equals B*/
@Test
void predicate_and_test() {boolean b = predicate_and("A", x -> x.startsWith("A"), y -> y.equals("B"));System.out.println(b);
}
boolean predicate_and(String str, Predicate<String> predicate1, Predicate<String> predicate2) {boolean b = predicate1.and(predicate2).test(str);return b;
}输出: false/*** 判断字符串是否以 A 开头,或者 equals B*/
@Test
void predicate_or_test() {boolean b = predicate_or("A", x -> x.startsWith("A"), y -> y.equals("B"));System.out.println(b);
}
boolean predicate_or(String str, Predicate<String> predicate1, Predicate<String> predicate2) {boolean b = predicate1.or(predicate2).test(str);return b;
}输出: true

③ negate()

该方法就是对 test() 取反,该方法的源码如下:

default Predicate<T> negate() {return (t) -> !test(t);
}

例子:

/*** 判断字符串的长度是否大于 5*/
@Test
void predicate_negate_test() {Predicate<String> predicate = s -> s.length() > 5;boolean b1 = predicate.test("predicate");System.out.println(b1); // trueboolean b2 = predicate.negate().test("predicate");System.out.println(b2); // false
}

1.4.4 Function

该接口通常用于对参数进行处理,转换然后返回一个新的值。

方法说明
R apply(T t)根据类型 T 的参数获取类型 R 的结果
default Function<T,V> andThen(Function after)返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果

① apply()

该方法根据类型 T 的参数获取类型 R 的结果。 例如:将 String 类型转换为 int 类型。

@Test
void function_test() {int i = function_apply("1234", x -> Integer.valueOf(x));System.out.println(i);
}/*** 吧字符串类型转为 int 类型* @param str* @param fun* @return*/
int function_apply(String str, Function<String, Integer> fun) {Integer apply = fun.apply(str);return apply;
}

② andThen()

该方法和 Consumer#andThen 类似,如下例子:

@Test
void function_andThen_test() {double d = function_andThen("10", x -> Integer.parseInt(x) + 10, y -> Double.valueOf(y));System.out.println(d);
}/*** 把 String 类型转为 Integer 加10,然后再转为 Double 类型*/
double function_andThen(String str, Function<String, Integer> fun1, Function<Integer, Double> fun2) {double apply = fun1.andThen(fun2).apply(str);//自动拆箱return apply;
}

1.5 方法引用

方法引用可以看成是对 Lambda 表达式的一种简化写法,但它的前提是:

如果 Lambda 表达式的方法体中只调用了一个方法,并且调用的方法函数式接口中定义的抽象方法参数列表和返回值都一致,就可以使用方法引用进行简化。

方法引用通过方法引用符来表示:::

常见的几种引用方式:

  1. 对象 :: 实例方法
  2. 类 :: 静态方法
  3. 类 :: 实例方法
  4. 类 :: new

1.5.1 对象 :: 实例方法

引用对象的实例方法,其实就是引用类中的成员方法。

格式:对象 :: 实例方法

示例:System.out::println

/*** 1. 对象::实例方法*/
@Test
void test1() {// 1.匿名内部类Consumer<String> consumer = new Consumer<>() {@Overridepublic void accept(String s) {System.out.println(s);}};consumer.accept("使用方式1: 匿名内部类");// 2.lambda表达式consumer = s -> System.out.println(s);consumer.accept("使用方式2: lambda表达式");//3.方法引用consumer = System.out::println;consumer.accept("使用方式3: 方法引用");
}

分析

  1. 只调用了一个方法

    省略的代码块中只调用了System.out.println(s)方法。

  2. 调用的方法和函数式接口中定义的抽象方法的参数列表和返回值都一致

    上面定义的Consumer<String>,因此 accept(T t) 的入参和 println(String x)都是 String 类型且都没有返回值。

1.5.2 类 :: 静态方法

格式:类名::静态方法

示例:Integer::parseInt,Integer::compare

/*** 2. 类::静态方法*/
@Test
void test2() {// 1.匿名内部类Comparator<Integer> comparator = new Comparator<>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1, o2);}};System.out.println("匿名内部类: " + comparator.compare(10, 20));// 2.lambda表达式comparator = (o1, o2) -> Integer.compare(o1, o2);System.out.println("lambda表达式: " + comparator.compare(10, 20));// 3.方法引用comparator = Integer::compare;System.out.println("方法引用: " + comparator.compare(10, 20));
}

1.5.3 类 :: 实例方法

格式:类 :: 实例方法

这个比较特殊,如下:

/*** 3. 类::实例方法*/
@Test
void test3() {Person person = new Person("Lambda");// 1.匿名内部类Function<Person, String> function = new Function<Person, String>() {@Overridepublic String apply(Person person) {return person.getName();}};System.out.println("匿名内部类: " + function.apply(person));// 2.lambda表达式function = p -> p.getName();System.out.println("lambda表达式: " + function.apply(person));// 3.方法引用function = Person::getName;System.out.println("方法引用: " + function.apply(person));
}class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return this.name;}
}

1.5.4 类 :: new

引用构造器,其实就是引用构造方法

格式:类 :: new

/*** 4. 类::new*/
@Test
void test4() {// 1.匿名内部类Supplier<String> supplier = new Supplier<String>() {@Overridepublic String get() {return new String("匿名内部类");}};System.out.println(supplier.get());// 2.lambda表达式supplier = () -> new String("lambda表达式");System.out.println(supplier.get());// 3.方法引用supplier = String::new;System.out.println(supplier.get());
}

注:在使用 Lanbda 表达式时方法体中使用到了某个局部变量,则这个变量默认被 final 修饰

2. Stream 流

2.1 什么是流

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来对 Java 集合运算和表达的高阶抽象。 Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 变换,聚合等。

image-20220906162012149

2.2 操作分类

流的种类分为两种,元素流在管道中经过中间操作的处理,最后由终端操作得到前面处理的结果:

  1. 中间操作:对流中的数据进行各种各样的处理,可以连续操作,每个操作的返回值都是 Stream 对象;
  2. 终端操作:终端操作执行结束后,会关闭这个流,流中的所有数据都会销毁。

因此它具有以下特性:

  1. 不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果;
  2. 不改变数据源,通常情况下会产生一个新的集合或一个值;
  3. 具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

stream

2.3 创建方式

Stream 的创建方式主要有以下几种:

image-20220906172116411

1. Collection.stream()

@Test
void collection_stream_test() {List<String> list = Arrays.asList("a", "b", "c", "d");Stream<String> stream = list.stream();
}

2. Arrays.stream(T[] array)

@Test
void arrays_stream_test() {int[] array = {1,2,3,4,5,6};IntStream stream = Arrays.stream(array);
}

3. Stream.of

@Test
void stream_of_test() {Stream<Integer> stream = Stream.of(1, 2, 3);
}

4. Stream.iterate

/*** 指定一个常量seed,生成从seed到常量 f(由 UnaryOperator返回的值得到)的流。* 如下:* 根据起始值seed(0),每次生成一个指定递增值(n+1)的数,limit(5)用于截断流的长度,即只获取前5个元素。*/
@Test
void stream_iterate_test() {Stream<Integer> stream = Stream.iterate(0, n -> n + 1).limit(5);stream.forEach(System.out::println);
}

5. Stream.generate

/*** 返回一个新的无限顺序无序的流*/
@Test
void stream_generate_test() {Stream<Double> stream = Stream.generate(Math::random).limit(3);stream.forEach(System.out::println);
}

2.4 Stream API

Stream 有很多的 API 供我们使用,如下:

image-20220908160356229

数据准备如下:

@Data
@AllArgsConstructor
class User {// 姓名private String name;// 年龄private int age;//性别private String sex;// 薪资private int salary;
}@BeforeEach
void init() {list = List.of(new User("亚索", 20, "男", 1000),new User("阿狸", 18, "女", 2000),new User("李青", 21, "男", 5000),new User("菲奥娜", 25, "女", 2500),new User("乐芙兰", 18, "女", 8000),new User("千珏", 30, "男", 10000));
}

2.4.1 遍历: foreach

对于数组、集合等可以通过 foreach 遍历。

@Test
void foreach_test() {List<String> list = Arrays.asList("a", "b", "c", "d");Stream<String> stream = list.stream();stream.forEach(System.out::println);
}

2.4.2 筛选: filter

条件过滤,仅保留流中满足指定条件的数据,其他不满足的数据都会被删除。

因为 filter 方法接收的是一个 Predicate 接口,该接口的 boolean test(T t) 方法对给定的参数进行判断,然后返回一个布尔值。

@Test
void filter_test() {// 查询年纪大于 20 的对象Stream<User> stream = list.stream().filter(user -> user.getAge() > 20);stream.forEach(System.out::println);System.out.println("----------------------分割线--------------------------");// 查询年纪大于 20 且性别为 男 的对象stream = list.stream().filter(user -> user.getAge() > 20 && "男".equals(user.getSex()));stream.forEach(System.out::println);
}

测试如下:

StreamAPITests.User(name=李青, age=21, sex=, salary=5000)
StreamAPITests.User(name=菲奥娜, age=25, sex=, salary=2500)
StreamAPITests.User(name=千珏, age=30, sex=, salary=10000)
------------------------分割线------------------------
StreamAPITests.User(name=李青, age=21, sex=, salary=5000)
StreamAPITests.User(name=千珏, age=30, sex=, salary=10000)

2.4.3 去重: distinct

去除集合中重复的元素,该方法没有参数,去重的规则与 HashSet 相同。

@Test
void distinct_test() {List<String> list = Arrays.asList("a", "b", "a", "d");Stream<String> stream = list.stream().distinct();stream.forEach(System.out::println);
}

2.4.4 排序: sorted

将流中的数据进行排序,存在默认排序和自定义排序。

@Test
void sorted_test() {// 按薪资排序Stream<User> stream = list.stream().sorted((o1, o2) -> o1.getSalary() - o2.getSalary());stream.forEach(System.out::println);
}

2.4.5 提取与组合:limit & skip

limit:限制,截取流中开头指定数量的元素

skip:跳过,跳过流中的指定数量的元素

配合使用,截取中间部分元素

@Test
void limit_skip_test() {// 截取 list 中的前 3 个Stream<User> stream = list.stream().limit(3);stream.forEach(System.out::println);System.out.println("----------------------------分割线--------------------------");// 跳过 list 中的前 2 个stream = list.stream().skip(2);stream.forEach(System.out::println);System.out.println("----------------------------分割线--------------------------");// 跳过 list 中的前 2 个,然后截取 2个stream = list.stream().skip(2).limit(2);stream.forEach(System.out::println);
}

2.4.6 映射: map、flatMap

① map

对流中的数据进行映射,用新的数据替换旧的数据,简单来说就是转为为你想要的 Stream 流。

因为 map 方法接收的是一个 Function 接口,该接口的 R apply(T t) 方法可以根据类型 T 的参数获取类型 R 的结果。

@Test
void map_test() {// 把所有人的年龄改为 18Stream<User> stream = list.stream().map(user -> {user.setAge(18);return user;});stream.forEach(System.out::println);System.out.println("----------------------------分割线--------------------------");// 返回所有人的名字Stream<String> streamAllName = list.stream().map(user -> user.getName());streamAllName.forEach(System.out::println);
}

② flatMap

对流扁平化处理,如下面 2 个例子。

一、获取用户所有的角色信息

public class FlatMapTests {static List<User> list = new ArrayList<>();@Data@AllArgsConstructorclass User {// idprivate Integer id;// 姓名private String name;// 年龄private int age;//性别private String sex;// 薪资private int salary;// 角色public List<String> roles;}/*** 应在当前类中的每个@Test方法之前执行注解方法*/@BeforeEachvoid init() {list = List.of(new User(1, "admin", 20, "男", 1000, List.of("ROLE_ADMIN", "ROLE_USER")),new User(2, "张三", 18, "女", 2000, List.of("ROLE_USER")),new User(3, "李四", 21, "男", 5000, List.of("ROLE_ADMIN")));}/*** 获取用户所有的角色信息* 不使用 flatmap*/@Testpublic void without_flatMap_test() {List<List<String>> roleList = list.stream().map(User::getRoles).peek(roles -> System.out.println(roles)).collect(toList());System.out.println(roleList);}/*** 获取用户所有的角色信息* 使用 flatmap*/@Testpublic void flatMap_test() {List<String> roleList = list.stream().flatMap(user -> user.getRoles().stream()).peek(roles -> System.out.println(roles)).collect(toList());System.out.println(roleList);}
}

可以看到,如果不使用 flatMap 返回值为 List<List<String>> 类型,使用之后为 List<String>

二、处理流种产生的 Optional 元素

当我们在调用某个方法时,它的返回值可能是一个 Optional 类型,如下:

/*** 不使用 flatmap*/
@Test
public void without_flatMap_test1() {List<Optional<User>> collect = list.stream().map(user -> findUserByName(user.getName())).peek(roles -> System.out.println(roles)).collect(toList());System.out.println(collect);
}
/*** 使用 flatmap*/
@Test
public void flatMap_test1() {List<User> userList = list.stream().map(user -> findUserByName(user.getName())).flatMap(Optional::stream).collect(toList());System.out.println(userList);
}public Optional<User> findUserByName(String name) {if ("admin".equals(name)) {return Optional.of(list.get(0));}return Optional.empty();
}

2.4.7 查找: find

findFirst:从流中获取第一个元素

findAny:从流中获取任意一个元素

@Test
void findFirst_test() throws Exception {// 查询第一个,返回一个 OptionalOptional<User> first = list.stream().findFirst();User user = first.get();System.out.println(user);// 随机查询一个Optional<User> any = list.stream().findAny();User anyUser = any.get();System.out.println(anyUser);
}

2.4.9 查找: match

allMatch:只有当流中所有的元素,都匹配指定的规则,才会返回 true

anyMatch:只要流中有任意的数据,满足指定的规则,都会返回 true

noneMatch:只有当流中的所有的元素,都不满足指定的规则,才会返回 true

@Test
void match_test() throws Exception {// 判断年龄是否都大于 20boolean allMatch = list.stream().allMatch(user -> user.getAge() > 20);System.out.println(allMatch);// 判断是否有年龄大于 20boolean anyMatch = list.stream().anyMatch(user -> user.getAge() > 20);System.out.println(anyMatch);// 判断是否都不大于 20boolean noneMatch = list.stream().noneMatch(user -> user.getAge() > 20);System.out.println(noneMatch);
}

2.4.10 归约: reduce

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

2.4.11 收集: collect

collect 主要是对一些进行操作过后的流收集起来,然后形成一个新的集合。

① 集合

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。常用是toListtoSettoMap

@Test
void to_list_set_map_test() throws Exception {// 查询出所有人的名字并转为一个 List 集合List<String> names = list.stream().map(User::getName).collect(Collectors.toList());System.out.println(names);// 查询所有人的年龄去重Set<Integer> ages = list.stream().map(User::getAge).collect(Collectors.toSet());System.out.println(ages);// 查询工资大于 5000 的每个人的名字及其对应的工资Map<String, Integer> map = list.stream().filter(user -> user.getSalary() > 5000).collect(Collectors.toMap(User::getName, User::getSalary));System.out.println(map);
}

② 统计

Collectors提供了一系列用于数据统计的静态方法:

  • 计数:count

  • 平均值:averagingIntaveragingLongaveragingDouble

  • 最值:maxByminBy

  • 求和:summingIntsummingLongsummingDouble

  • 统计以上所有:summarizingIntsummarizingLongsummarizingDouble

@Test
void count_test() throws Exception {// 统计平均工资Double avg = list.stream().collect(Collectors.averagingDouble(User::getSalary));System.out.println(avg);// 最高工资Optional<Integer> max = list.stream().map(User::getSalary).collect(Collectors.maxBy(Integer::compare));System.out.println(max.get());
}

③ 分组

partitioningBy 和 groupingBy 都是用于将数据进行分组的函数:

partitioningBy

public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {return partitioningBy(predicate, toList());
}

可以看出函数的参数一个 Predicate 接口,那么这个接口的返回值是 boolean 类型的,也只能是 boolean 类型,然后他的返回值是 Map 的 key 是 boolean 类型,也就是这个函数的返回值只能将数据分为两组也就是 ture 和 false 两组数据。

groupingBy

public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {return groupingBy(classifier, toList());
}

groupingBy 的函数参数为 Function 然后他的返回值也是 Map,但是他的 key 是泛型,那么这个分组就会将数据分组成多个 key 的形式。

@Test
void group_test() throws Exception {// 按工资是否高于 5000 分为两部分Map<Boolean, List<User>> partitioningBy = list.stream().collect(Collectors.partitioningBy(user -> user.getSalary() > 5000));System.out.println(partitioningBy);// 按照性别分组Map<String, List<User>> groupingBySex = list.stream().collect(Collectors.groupingBy(User::getSex));System.out.println(groupingBySex);// 先按照性别分组,在按照年龄分组Map<String, Map<Integer, List<User>>> groupingBySexAge = list.stream().collect(Collectors.groupingBy(User::getSex, Collectors.groupingBy(User::getAge)));System.out.println(groupingBySexAge);
}

④ joining

joining可以将 stream 中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。比如常见的逗号分割字符。

@Test
void joining_test() throws Exception {// 将姓名按照逗号分割String names = list.stream().map(User::getName).collect(Collectors.joining(","));System.out.println(names);
}

3. Optional

Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,调用 get() 方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类的引入很好的解决空指针异常。

3.1 创建和获取

方法说明
Optional.empty()创建一个空的 Optional 实例
Optional.of(T t)创建一个 Optional 实例,当 t 为 null 时抛出异常
Optional.ofNullable(T t)创建一个 Optional 实例,但当 t 为 null 时不会抛出异常,而是返回一个空的实例
get()获取 Optional 实例中的对象,当 Optional 容器为空时报错
@Test
void optional_stream_test() {Optional<Object> empty = Optional.empty();System.out.println(empty.get());Optional<String> of = Optional.of("of");System.out.println(of.get());Optional<String> nullable = Optional.ofNullable(null);System.out.println(nullable.get());
}

3.2 判断

方法说明
isPresent()判断 Optional 是否为空,如果空则返回 false,否则返回 true
isEmpty()判断 Optional 是否为空,如果空则返回 true,否则返回 false
orElse(T other)如果 Optional 不为空,则返回 Optional 中的对象;如果为 null,则返回 other 这个默认值
orElseGet(Supplier other)如果 Optional 不为空,则返回 Optional 中的对象;如果为 null,则使用Supplier 函数生成默认值 other
orElseThrow(Supplier exception)如果 Optional 不为空,则返回 Optional 中的对象;如果为 null,则抛出Supplier 函数生成的异常
ifPresentOrElse()JDK1.9新增,Optional 类 ifPresent 方法对else的操作提供支持

3.3 过滤

方法说明
filter(Predicate p)如果 Optional 不为空,则执行断言函数 p,如果 p 的结果为 true,则返回原本的Optional,否则返回空的 Optional

3.4 映射

方法说明
map(Function<T, U> mapper)如果 Optional 不为空,则将 Optional 中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的 Optional 容器中
flatMap(Function< T,Optional> mapper)同上,在 Optional 不为空的情况下,将对象 t 映射成另外一个 Optional

http://chatgpt.dhexx.cn/article/eIZohRDy.shtml

相关文章

入门 Java 函数式编程,看完这篇就清晰了

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

Java函数式编程详解

Java从1.8以后引入了函数式编程&#xff0c;这是很大的一个改进。函数式编程的优点在提高编码的效率&#xff0c;增强代码的可读性。本文历时两个多月一点点写出来&#xff0c;即作为心得&#xff0c;亦作为交流。 1.Java函数式编程的语法&#xff1a; 使用Consumer作为示例&…

Java 函数式编程 详细介绍

在兼顾面向对象特性的基础上&#xff0c;Java语言通过Lambda表达式与方法引用等&#xff0c;为开发者打开了函数式编程的大门。 下面我们做一个初探。 Lambda的延迟执行 有些场景的代码执行后&#xff0c;结果不一定会被使用&#xff0c;从而造成性能浪费。而Lambda表达式是延…

Java基础函数式编程

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

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代码…