个人总结最详细清晰集合Stream流,Lambda表达式的使用

一、Stream&Lambda表达式

为什么使用函数式编程:

  • 因为在大数量下的处理集合效率高,直接使用并行流可以不用自己去创建多线程处理,减少了复杂度

  • 代码的可读性高

  • 减少了代码嵌套

函数式编程思想主要是关注对数据进行了什么操作

1 .Lambda表达式

1.1 概述

是jdk8中的一个语法糖,可以对某些匿名内部类写法进行简化,是函数式编程的重要体现。

1.2核心原则

可推导可省略

1.3基本格式

(参数)-> {代码} 不关注方法名,只关注参数和方法体,所以可以直接将方法体那部分给拷贝过来

只能对函数式接口这么使用,如果接口Interface中需要实现的抽象方法只有一个,这样的接口就是函数式接口.

例子1:

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("方法体");
            }
        }).start();

lambda简化:

new Thread(()-> { System.out.println("方法体");} ).start(); //只关注方法体,所以直接将方法体的参数拿过来就可以使用

例子2:未使用lambda:

public interface TestLambda {
     <T, V> T method(T a, V b);
}

int method1(TestLambda<String, Integer> lambda) {
    System.out.println(lambda.method("2", 3));
    return 1;
}

@Test
public void testLambda() {
    int i = method1(
        new TestLambda<String, Integer>() {
            @Override
            public Integer method(String a, Integer b) {
                return Integer.parseInt(a) + b;
            }
        }
    );
    System.out.println(i);
}

使用lambda:

    int method1(TestLambda<String, Integer> lambda) {
        System.out.println(lambda.method("2", 3));
        return 1;
    }

    @Test
    public void testLambda() {
        int i = method1(
                (a, b) -> Integer.parseInt(a) + b
        );
        System.out.println(i);
    }

1.4 的省略规则

参数类型可以省略
方法体只有一句代码时,大括号return和唯一一句代码的分号可以省略
方法只有一个参数时小括号可以省略

2 .Stream流

2.1 概述

Java8中的Stream使用的是函数编程,可以用来对集合或数组进行链状流式操作。可以更加方便的让我们对集合或数组操作

java中 链式操作的 Debug会有一个链式追踪的效果
image-20231206113636711

2.2案例数据准备

准备了一个提供集合的函数,方便我们进行测试学习

public List<Author> getAuthors() {
    //数据初始化
    Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
    Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
    Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
    Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
    //书籍列表
    List<Book> books1 = new ArrayList<>();
    List<Book> books2 = new ArrayList<>();
    List<Book> books3 = new ArrayList<>();
    books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
    books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
    books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
    books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
    books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));
    books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
    books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
    books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
    author.setBooks(books1);
    author2.setBooks(books2);
    author3.setBooks(books3);
    author4.setBooks(books3);
    List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
    return authorList;
}

第一个案例:

// 没有使用lambda注解时候
private static void test01(List<Author> authors) {
    authors.stream()//把集合转换成流
        .distinct()
        .filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                System.out.println("test");
                return author.getAge() < 18;
            }
        })
        .forEach(new Consumer<Author>() {
            @Override
            public void accept(Author author) {
                System.out.println(author.getName());
            }
        });
}

private static void test01(List<Author> authors) {
	authors.stream()//把集合转换成流
		   .distinct()
		   .filter(author -> {
	System.out.println("test");
	return author.getAge() < 18;
	})
	.forEach(author -> System.out.println(author.getName()));
}

2.3快速入门

2.4常用操作

2.4.1创建流

单列集合:集合对象.stream(),可以将Map对象进行转化为set对象然后调用

List<Author> authors = new GetAuthors();
Stream<Author> stream = authors.stream();

数组:Arrays.Stream(数组)或者使用Stream.of来创建

Integer[] arr = {1,2,3,4}
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> Stream2 = Stream.of(arr)
2.4.2中间操作

filter 对流中的元素进行条件过滤,符合过滤调价你的才能继续留在流中

private static void test04() {
    List<Author> authors = getAuthors();
    //        打印所有姓名长度大于1的作家的姓名
    authors.stream()
        .filter(author -> author.getName().length() > 1)
        .forEach(author -> {System.out.println(author.getName());});

}

map 对流中的元素进行计算或转换

private static void test05() {
    // 打印所有作家的姓名
    List<Author> authors = getAuthors();
    authors.stream()
           .map(author -> author.getName())
           .forEach(s -> System.out.println(s));
}
//计算的话就是先转化为相应的类型,然后再使用map进行计算

distinct 去重操作

直接使用.distinct()就行

sorted 对流中的元素进行排序,没有参数默认从id开始

public void test07() {
    List<Author> authors = getAuthors();
    //        对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
    authors.stream()
        .sorted(new Comparator<Author>() {
            @Override
            public int compare(Author o1, Author o2) {
                return o1.getAge() - o2.getAge();
            }
        })
        .forEach(author -> System.out.println(author.toString()));
}

limit 将前面几个打印出来

skip 跳过前面的元素

flatMap 可以把一个对象转化为多个对象作为流中的对象

    private static void test10() {
//        打印所有书籍的名字。要求对重复的元素进行去重。
        List<Author> authors = getAuthors();

        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book.getName()));

    }

//更难一点的
public void test11() {

    //        打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
    List<Author> authors = getAuthors();
    authors.stream()
        .flatMap(author -> author.getBooks().stream())
        .distinct() 	// 数组转化为流的方法
        .flatMap(book -> Arrays.stream(book.getCategory().split(","))) 
        .distinct()
        .forEach(category-> System.out.println(category));

}
2.4.3终结操作
2.4.3.1普通的结果

forEach: 对流中的元素进行遍历操作,我们通过传入的参数去指定遍历的元素进行操作

**count:**获取当前流中的元素个数

long count = authors.stream()
	.flatMap(author -> author.getBooks().stream())
	.distinct()
	.count();
System.out.println(count);

max & min:获取当前流中的最后一个或者第一个值,所以需要根据排序的类型决定

    public void test14() {
//        分别获取这些作家的所出书籍的最高分和最低分并打印。
        //Stream<Author>  -> Stream<Book> ->Stream<Integer>  ->求值

        List<Author> authors = getAuthors();
        Optional<Integer> max = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .max((score1, score2) -> score1 - score2);
        
        Optional<Integer> min = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .min((score1, score2) -> score1 - score2);
        System.out.println(max.get());
        System.out.println(min.get());
 }

**collect:**把当前的流转换成一个集合

    public void test17() {
//        获取一个Map集合,map的key为作者名,value为List<Book>
        List<Author> authors = getAuthors();

        Map<String, List<Book>> map = authors.stream()
                .distinct()
                .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));

        System.out.println(map);
    }
    
        public void test15() {
//        获取一个存放所有作者名字的List集合。
        List<Author> authors = getAuthors();
        List<String> nameList = authors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toList()); // Collectiors.toSet() 也可以
        System.out.println(nameList);
    }
2.4.3.2 查找和匹配

**anyMatch:**判断是否有符合匹配条件的元素,结果为Boolean类型 有一个满足true

**allMatch:**判断是否都符合匹配条件,返回结果为boolean类型 都满足true

**noneMatch:**判断流中的元素是否都不符合匹配条件,都不满足返回true

**findAny:**获取流中的任意一个元素,如果有则随机返回一个

**findFirst:**获取流中的第一个元素,类似于min的

2.4.3.3 Reduce归并

对流中的数据按照指定的计算方式计算出一个结果(缩减操作
reduce的作用就是把stream的元素组合起来,可以再传入一个初始值,他会按照我们的自定义的计算方式依次拿取流中的元素和在初始化值的基础上进行计算,计算结果后再和后面的元素进行计算

T result = identity; // T 变量中的类型和流中类型一致,所以计算的时候可能首先需要使用map进行流的转换
for(T element : this stream)
	result = accumulator.apply(result, element)
return result; 

两个参数reduce:T reduce(T identity, BinaryOperator accumulator);

测试1:计算所有作家的年龄

 public void testReduce01() {
        // 获取所有作者的年龄和
        List<Author> authors = getAuthors();
        Stream<Author> stream = authors.stream();
        Integer sum_age = stream.map(Author::getAge)
                .distinct()
                .reduce(0, new BinaryOperator<Integer>() { // 第一个参数就是初始化参数
                    @Override
                    public Integer apply(Integer identity, Integer element) {  // 进行迭代
                        return identity += element;
                    }
                });
        System.out.println("sum_age = " + sum_age);
    }

自我练习:

// 求作家中年龄最大或者最小的
    @Test
    public void testReduce02() {
        // 求作家年龄中最小和最大
        Stream<Author> stream = getAuthors().stream();
        Integer max = stream.map(Author::getAge)
                .reduce(-1, (integer, integer2) -> integer > integer2 ? integer : integer2);
        System.out.println("max = " + max);
    }

一个参数reduce Optional reduce(BinaryOperator accumulator);

**区别:**将第一个参数直接变成初始化值,并且返回一个 optional类型

    @Test
    public void testReduce03() {
        Stream<Author> stream = getAuthors().stream();
        Optional<Integer> max1 = stream.map(Author::getAge)
                .reduce((identity, integer) -> identity > integer ? identity : integer);
        System.out.println("max1 = " + max1);
    }

2.5 注意事项

  • 惰性求值(如果没有终结操作,中间的操作是不会执行的)

  • 流是一次性的(一旦一个流进行操作以后,这个流就不能再被使用,不然会报以下错误)
    image-20231206140431064

  • 不会影响原数据(我们在流中可以进行很多处理,但是正常情况下是不会影响原来集合中的元素的)

3 .Optional

3.1 概述

编写代码的时候很容易出现空指针异常,很多时候需要做非空判断

例如:

Person lin = new Person();
if(lin != null) {
	system.out.println(lin.toString());
}

这样的判断在对象特别的多的时候,代码会显得很臃肿。所以jdk8中引入了Optional类,可以写出更加优雅的代码防止空指针异常。

3.2 使用

3.2.1创建对象

Optional类似于一个包装类, 可以把我们对具体的实例对象封装到Optional对象内部去,然后我们去调用Optional封装好的方法进行非空判断了

一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题

Person lin = new Person();
Optional<List<Person>> optionalLin = Optional.ofNullable(lin);

大致就是这样 但是我看我自己做的项目中没什么人用 就留着后面用的时候再了解了

4 .函数式接口

4.1 概述

只有一个抽象方法的接口我们称为函数式接口,一个函数输入一个函数输出

JDK的函数式接口都加上了**@FunctionalInterface进行标识,但是没有加这个注解的只要接口中只有一个抽象方法**的都是函数式接口

4.2 常见的函数式接口

我们之前测试Lambda表达式的时候就涉及到了很多的函数式接口

  • Consumer 消费型接口: 我们可以在方法中对传入的参数进行消费
  • Function 计算转换接口: 可以在方法中对传入的参数进行计算或者转换进行返回
  • Predicate 判断接口: 可以根据传入的参数条件进行判断,判断的结果进行返回
  • Supplier 生产型接口: 在方法中创建对象,然后把创建好的对象进行返回

4.3 常用的默认方法

  • Predicate的add方法:
    // 判断add操作
    @Test
    public void testAdd() {
        Stream<Author> stream = getAuthors().stream();
        stream.filter(((Predicate<Author>) author -> author.getAge() > 10).and(author -> author.getAge() < 20)).forEach(System.out::println);

    }
  • Predicate的or方法: 使用相同

  • Predicate的negate方法: 相当于在判断的条件前面加了一个 !

5 .方法引用

是JDK8中的一个语法糖,当我们使用Lambda时候,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以使用方法引用进一步简化代码

5.1 推荐用法

在我们使用lambda表达式的时候,写完了发现方法体只有一行方法调用的代码的时候

stream.map(author1 -> {
    return author1.getAge(); // 方法体内部只有一个方法的调用的时候 可以直接简化代码
}).forEach(System.out::println);
// 简化后的代码
stream.map(Author::getAge).forEach(System.out::println);

5.2 基本格式

类名或者对象 **::**方法名

6 .高级用法

6.1 类型转换优化

我们在进行集合的操作的时候,有时候会对我们map转化完的数据进行计算,比如map转化完成的integer包装类后再进行计算的时候会重复大量的进行装箱和拆箱操作,所以会浪费很多时间和资源

stream.map(new Function<Author, Integer>() {
    @Override
    public Integer apply(Author author) { // 转化后的为 integer包装类
        return author.getAge();
    }
})
    .map(age -> age +1);

为了应对这样的情况,所以Stream提供了很多针对基本数据类型的方法
例如: mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble

stream.mapToInt(new ToIntFunction<Author>() {
    @Override
    public int applyAsInt(Author author) { // 转化后的
        return author.getAge();
    }
})

6.2 并行流

当流中的大量数据时候,我们可以使用并行流去提高操作的效率,把任务分配给多个线程去执行。Stream提供了快速使用并行流的实现,只需要修改一个方法的调用就可以.

这里使用parallel()peek()方法开启并行流和查看当前的是哪个线程正在执行

    @Test
    public void testHight() {
        List<Author> authors = getAuthors();
        for (int i = 0; i < 100; i++) {
            authors.addAll(getAuthors());
        }
        System.out.println("authors.size() = " + authors.size());
        long start = System.currentTimeMillis();
        Stream<Author> stream = authors.stream();
        Integer sum = stream.parallel()  //
                .peek(author -> System.out.println(Thread.currentThread().getName() + author))
                .map(Author::getAge)
                .filter(age -> age > 15)
                .reduce((identity, integer) -> identity + integer)
                .get();
        System.out.println("sum = " + sum);
        long end = System.currentTimeMillis();
        System.out.println("end - start = " + (end - start));
    }

ream.mapToInt(new ToIntFunction() {
@Override
public int applyAsInt(Author author) { // 转化后的
return author.getAge();
}
})




### 6.2 并行流

当流中的**大量数据**时候,我们可以使用并行流去提高操作的效率,把任务分配给多个线程去执行。Stream提供了快速使用并行流的实现,只需要修改一个方法的调用就可以.

这里使用`parallel()`和`peek()`方法开启并行流和查看当前的是哪个线程正在执行

```java
    @Test
    public void testHight() {
        List<Author> authors = getAuthors();
        for (int i = 0; i < 100; i++) {
            authors.addAll(getAuthors());
        }
        System.out.println("authors.size() = " + authors.size());
        long start = System.currentTimeMillis();
        Stream<Author> stream = authors.stream();
        Integer sum = stream.parallel()  //
                .peek(author -> System.out.println(Thread.currentThread().getName() + author))
                .map(Author::getAge)
                .filter(age -> age > 15)
                .reduce((identity, integer) -> identity + integer)
                .get();
        System.out.println("sum = " + sum);
        long end = System.currentTimeMillis();
        System.out.println("end - start = " + (end - start));
    }