Java8 函数式编程

news/2024/4/30 9:06:53/文章来源:https://blog.csdn.net/weixin_43477531/article/details/127213876

文章目录

  • 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://www.luyixian.cn/news_show_20224.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

期货价格怎么算出来的?

期货价格怎么算出来的&#xff1f; 期货价格现货价格融资成本 如果对应资产是一个支付现金股息的股票组合&#xff0c;那么购买期货合约的一方因没有马上持有这个股票组合而没有收到股息。相反&#xff0c;合约卖方因持有对应股票组合收到了股息&#xff0c;因而减少了其持仓成…

数据结构-泛型(Java)

文章目录一、什么是泛型&#xff1f;1、非泛型2、泛型3、泛型的使用 泛型类 泛型接口 泛型方法二、泛型类1、 泛型类 正确使用分析 错误使用分析2、泛型类实现抽奖器3、泛型类派生子类 泛型类派生子类第一种第二种 非泛型三、泛型接口第一种&#xff1a;泛型类实现泛型接口第二…

使用python的pygame做的小游戏项目:小船打鱼

python小游戏项目&#xff1a;小船打鱼成果展示代码解析go_fishing.pygame_function.pygame_stats.pyscoreboard.pyalien.pysettings.pyship.pybullet.pybutton.py存在的问题代码都在这里&#xff0c;只需要创建好项目&#xff0c;将对应的代码保存在对应文件名的文件中即可&am…

【微搭低代码】Javascript基础知识-函数及模块介绍

低代码要想入门&#xff0c;首先需要学习javascript&#xff0c;我们已经有了两篇基础文章 变量定义及初始化 循环及条件控制 我们本篇介绍两个知识点&#xff0c;一个是函数&#xff0c;一个是模块 函数 在js中函数是可以重复使用的代码块&#xff0c;定义函数是为了去除冗余…

在Windows下自制ARM交叉编译工具链

参考链接&#xff1a;gnu工具链 1.Download MinGW and MSys packages. 安装MSys 参考此链接https://www.msys2.org/安装&#xff0c;注意只需要安装即可。 安装开发环境,设置镜像,需要进入安装路径中的/etc/pacman.d/进行修改 // /etc/pacman.d/mirrorlist.mingw32 Serve…

【5G RRC】5G 切换(handover)那点事儿

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

python去图片背景

Remove Image Background using Python https://youtu.be/RkdFkhfMK2k

跨境电商必读,WhatsApp营销入门指南!

关键词&#xff1a;WhatsApp营销、跨境电商营销 现在&#xff0c;跨境社交媒体和Messengers不仅仅是私人交流的渠道了。很多跨境电商已经找到了在WhatsApp营销的秘诀&#xff0c;如果你还没开始&#xff0c;你可能已经落后了。同时&#xff0c;与其他平台相比&#xff0c;在 W…

Vue组件-卡片动画倒计时

前言 最近有朋友在做投票的项目&#xff0c;里面有用到一个倒计时的组件&#xff0c;还想要个动画效果。cv大法浸染多年的我&#xff0c;首先想到的是直接找个现有的组件。 通过一通搜索&#xff0c;看上的只有一个 vue2-flip-countdown&#xff0c;但是当我要修改大小和颜色…

(附源码)计算机毕业设计SSM游乐园娱乐项目管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Github的使用教程

文章目录注册查找仓库下载代码fork仓库管理创建仓库添加文件提交issue提交/接受PRpages一直想进入工程这块领地&#xff0c;但是好像没咋学过github&#xff0c;今天学一下&#xff0c;先上个名词解释 注册 首先&#xff0c;github其实是不需要邮箱和手机号的&#xff0c;可以…

window11下安装.framework3.5的方法

window11下安装.framework3.5的方法 如果正常安装报错了&#xff0c;可采用如下方法重新安装 一、把安装iso文件 zh-cn_windows_11_business_editions_version_22h2_updated_sep_2022_x64_dvd_515a832b.iso 装载到虚拟盘中H:\sources\sxs\中的文件拷贝到硬盘已存在的盘符F:\w…

容器适配器——stack/queue/priority_queue

目录 一. stack 二. queue 三. priority_queue 1. empty()&#xff0c;top()&#xff0c;size()的实现 2. pop和push的实现 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将一个类的接口转…

C语言:数组参数、指针参数

目录 一.字符指针&#xff0c;指针数组&#xff0c;数组指针简单回顾 二.数组参数、指针参数 一维数组传参 二维数组传参 这里需要注意&#xff1a; 一级指针传参 思考 二级指针传参 思考 一.字符指针&#xff0c;指针数组&#xff0c;数组指针简单回顾 #include<std…

java虚拟机中的双亲委派机制

文章目录双亲委派机制工作原理工作场景调用过程三种加载器调用范围String类加载过程StringTest类加载过程双亲委派机制优点双亲委派机制 Java虚拟机对class文件采用的是按需加载的方式&#xff0c;也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加…

一些有趣的小项目合集~

pyqt人脸识别&#xff1a; nullhttps://www.jb51.net/article/168718.htmpyqt目标检测&#xff1a; 利用PyQt5为目标检测Faster-rcnn-Pytorch添加GUI界面&#xff08;二&#xff09;-python黑洞网 (pythonheidong.com)https://www.pythonheidong.com/blog/article/337144/e2d…

最常见的IMU:MPU6050

I2CI^2CI2C通讯 ​ I2CI^2CI2C is a two-wire interface comprised of the signals serial data (SDA) and serial clock (SCL). In general, the lines are open-drain and bi-directional. In a generalized I-C interface implementation, attached devices can be a maste…

优雅的处理参数校验以及异常

1、前言 编写控制层时&#xff0c;我们可能会自己去校验请求参数&#xff0c;就会出现这样的代码&#xff1a; if (StringUtils.isEmpty(memberSid)) {return new JsonResult(false, "参数memberSid为空"); } if (null test) {return new JsonResult(false, "…

油溶性PbS量子点近红外发射光PL800nm-1600nm

油溶性PbS量子点近红外发射光PL800nm-1600nm 油溶性PbS量子点产品&#xff0c;表面由疏水配体包覆&#xff0c;平均的量子产率为50%&#xff0c;储存时应避免阳光直射&#xff0c;4度密封暗处保存&#xff0c;可以为客户订制生产800nm&#xff5e;1600nm任一波长不同克数的产品…

叶毓睿:元宇宙发展与治理中,治理的主体是谁?治理的对象是谁?

中国移联元宇宙产业委员会联席秘书长、《元宇宙十大技术》著者之一、高效能服务器和存储技术国家重点实验室首席研究员叶毓睿&#xff1a;治理之可能性的关键在于延续性和开创性。 2022年9月24日&#xff0c;元宇宙产业委特别筹备的“发展与治理”2022元宇宙共治大会暨《元宇宙…