java8新特性lambda和Stream新手springboot案例2020年新版

一、前言

本文,前段是原理,后半段是案例,如果懒得看原理的朋友,可以直接跳到案例

敲黑板,跟我边做边学,直接到案例那一段,非常详细。

什么是java8—关键字:2014年3月发布,提高与旧代码的兼容性

目前已经到了java14了,JDK8是Oracle在2014年3月19日发布正式版的,最大的改进是Lambda表达式(以及因之带来的函数式接口,很多原有类都做了变更,但能够与以往版本兼容,堪称奇功!),还有Stream API流式处理,joda-time等等一些新特性。

  1. default关键字
  2. Lambda 表达式(函数式编程)
  3. 函数式接口
  4. 方法与构造函数引用
  5. 局部变量限制
  6. Date Api更新
  7. 流(声明性方式

什么是lambda—关键字:“语法糖”,

  • 用逗号分隔的参数列表
  • -> 符号
  • 和 语句块 组成

虽然看着很先进,其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。

Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。

在 Java8 以前,我们想要让一个方法可以与用户进行交互,比如说使用方法内的局部变量;

  1. 这时候就只能使用接口做为参数,让用户实现这个接口或使用匿名内部类的形式,把局部变量通过接口方法传给用户。
    传统匿名内部类缺点:代码臃肿,难以阅读

  2. 使用Lambda 表达式
    Lambda 表达式将函数当成参数传递给某个方法,或者把代码本身当作数据处理;

// 使用匿名内部类  
btn.setOnAction(new EventHandler<ActionEvent>() {  
          @Override  
          public void handle(ActionEvent event) {  
              System.out.println("Hello World!");   
          }  
    });  
   
// 或者使用 lambda expression  
btn.setOnAction(event -> System.out.println("Hello World!"));

Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。

什么是函数式接口—关键字:默认方法允许在不打破现有继承体系的基础上改进接口

  • 接口中只能有一个接口方法
  • 可以有静态方法和默认方法
  • 使用 @FunctionalInterface 标记
  • 默认方法可以被覆写

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等

下图,查看容器map的foreach的方法:

查看容器map的foreach
已经存在的 Java8 定义的函数式接口

我们基本不需要定义自己的函数式接口,Java8 已经给我们提供了大量的默认函数式接口,基本够用,在 rt.jar 包的 java.util.function 目录下可以看到所有默认的函数式接口,大致分为几类

Function<T,R> T 作为输入,返回的 R 作为输出
Predicate<T> T 作为输入 ,返回 boolean 值的输出
Consumer<T> T 作为输入 ,没有输出
Supplier<R> 没有输入 , R 作为输出
BinaryOperator<T> 两个 T 作为输入 ,T 同样是输出
UnaryOperator<T> 是 Function 的变种 ,输入输出者是 T

什么是stream—关键字:集合,高级版本的iterator

java8中的集合支持一个新的Stream方法,它会返回一个流,到底什么是流呢?

流的使用包括三件事:

  • 1.数据源,集合
  • 2.中间操作,流水线
  • 3.终端操作,执行流水线,生成结果

经典案例:

//题目,排序,删选大于6的
       //初始化
       List<Integer> integers = new ArrayList<>();
       integers.add(5);
       integers.add(7);
       integers.add(3);
       integers.add(8);
       integers.add(4);
       //传统 1先排序(倒叙),2比较大小
       Collections.sort(integers, new Comparator<Integer>() {
           @Override
           public int compare(Integer o1, Integer o2) {
               return o2.compareTo(o1);
           }
       });
       Iterator<Integer> iterator = integers.iterator();
       while (iterator.hasNext()){
           Integer next = iterator.next();
           if (next > 6){
               iterator.remove();
           }
       }
       integers.forEach(System.out::println);

       //使用Stream
       List<Integer> collect = integers.stream().filter(i -> i < 6).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
       collect.forEach(System.out::println);

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;
高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如:
所有元素求和
过滤掉长度大于 10 的字符串
获取每个字符串的首字母
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作

Stream 的另外一大特点是,数据源本身可以是无限的

二、lambda案例

1、从打印学lambds表达

我是用springboot做案例的,版本是 2.2.5.RELEASE

@SpringBootTest
class LambdsStreamApplicationTests {
    
    @Test
    public void printTest(){
        //打印list
        List<String> list = Arrays.asList("帅哥","灿灿");
        list.forEach(System.out::println);
        //打印map
        Map<Integer,String> map = new HashMap<>(5);
        map.put(1,"帅哥");
        map.put(2,"灿灿");
        map.forEach((k,v)->System.out.println("key: "+k+" value: "+v));
    }
    
}

输出:

帅哥
灿灿
key: 1 value: 帅哥
key: 2 value: 灿灿
用了上面的打印后,循环遍历打印感觉low出天际了

2、从打印学lambds表达2

@Test
    public void printTest2(){
        //打印list
        List<String> list = Arrays.asList("帅哥","灿灿");
        list.forEach(v->System.out.println("至理名言:"+v));
        //打印map
        Map<Integer,String> map = new HashMap<>(5);
        map.put(1,"帅哥");
        map.put(2,"灿灿");
        map.forEach((k,v)->{
            if(k>1){
                System.out.println("key大于1,输出至理名言, 我是"+v);
            }
        });
    }

输出:

  • 至理名言:帅哥
  • 至理名言:灿灿
  • key大于1,输出至理名言,我是灿灿

为什么我要举两个打印的例子,因为真的很重要,要消化一下。

3、从匿名内部类的对比来学lambds

采用对比,来学匿名内部类和lambds的转换,其实我看来,lambds就是用来解决匿名内部类的复杂问题

@Test
    public void lambdsTest(){

        //之前,线程,匿名内部类,java8 之前
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

        //之后,lambds表达式
        new Thread(()->System.out.println(Thread.currentThread().getName())).start();

        //==============================================================================
        
        //之前,线程,匿名内部类,java8 之前
        Runnable race1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello ccan !");
            }
        };
        //之后,lambds表达式
        Runnable race2 = () -> System.out.println("Hello ccan !");
        race1.run();
        race2.run();
    }

4、函数式接口的案例,非常重要

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。

@Test
    public void funcTest(){
        
        //函数式接口,这个非常重要,接口中只能有一个接口方法,可以有静态方法和默认方法,使用 @FunctionalInterface 标记,默认方法可以被覆写
        //函数式接口存在的意义非常重要, 默认方法允许在不打破现有继承体系的基础上改进接口
        //给 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等
        Function<String,String> function = (x) -> {return x+"Function";};
        // hello world Function
        System.out.println(function.apply("hello world"));

        //==================================
        
        //使用函数式接口的map
        Map<Integer,String> map = new HashMap<>(5);
        map.put(1,"帅哥");
        map.put(2,"灿灿");
        //以下BiConsumer为函数式接口,源码贴有 @FunctionalInterface
        BiConsumer<Integer, String> integerStringBiConsumer = (k, v) -> {
            if (k > 1) {
                System.out.println("key大于1,输出至理名言, 我是" + v);
            }
        };
        map.forEach(integerStringBiConsumer);

5 从排序学lambda表达式

@Test
    public void sortTest(){
        String[] players = {"Xiaoming", "Jack",
                "Cancan", "Tom","Alin"};

        //使用匿名内部类根据 name 排序 players
        //Arrays.sort(players, new Comparator<String>() {
        //    @Override
        //    public int compare(String s1, String s2) {
        //        return (s1.compareTo(s2));
        //    }
        //});
        //for (String player : players) {
        //    System.out.println(player);
        //}


        //使用lambds表达式
        Arrays.sort(players, Comparator.naturalOrder());
        for (String player : players) {
            System.out.println(player);
        }
    }

三、Stream操作

流的使用包括三件事:

  • 数据源,集合
  • 中间操作,流水线
  • 终端操作,执行流水线,生成结果
    其实流水线的背后理念类似于构建器模式,构建器模式就是用来设置一套配置,也就是这里的中间操作,接着调用built方法,也就是这里的终端操作。

Stream操作分类 :
Stream操作分类

1、流的创建 — 一般都是通过list或者map生成流的

@Test
    public void streamTest(){
        //直接复值的形式
        Stream stream = Stream.of("a", "b", "c", 23);
        stream.forEach(key -> System.out.println(key));

        System.out.println("===============");
        //通过数组生成
        String[] array = new String[]{"abc", "efg"};
        stream = Arrays.stream(array);
        stream.forEach(key -> System.out.println(key));
        System.out.println("===============");

        //通过list生成
        List<String> list = Arrays.asList(array);
        stream = list.stream();
        stream.forEach(key -> System.out.println(key));
        System.out.println("===============");

        //IntStream、LongStream、DoubleStream
        IntStream stream2 = IntStream.of(1, 2, 3, 3);
        DoubleStream stream4 = DoubleStream.of(1, 2, 3, 3.4);

        stream2.forEach(key -> System.out.println(key));
        stream4.forEach(key -> System.out.println(key));
    }

2 测试 — 跟着我一起做

写在前面,java8新特性其实不难,唯一难的就是没有多练,只有不断的敲代码,java8新特性的原理自然了然于胸,下面都是代码,请读者跟着我一起做一起练,掌握基础的,遇到问题自然迎刃而解

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

   //学生编号
   private String sNo;
   //学生名字
   private String name;
   //性别
   private String gender;
   // 住宿地址编号
   private Integer addressId;
   // 个人评分
   private Double score;
}

测试类(不要嫌弃多,跟着练,敲重点)

@SpringBootTest
class LambdsStreamApplicationTests {

    static List<Student> students = new ArrayList<>();

    @BeforeEach
    public void init(){
        students.add(new Student("XS1001","大军","男",1,4.5));
        students.add(new Student("XS1011","河马","男",2,1.4));
        students.add(new Student("XS1002","小刀","女",1,3.0));
        students.add(new Student("XS1022","柯灵","男",1,3.9));
        students.add(new Student("XS1003","钟归","男",2,4.9));
    }


    @Test
    public void test1(){
        //遍历打印
        students.forEach(System.out::println);
    }

    @Test
    public void filterTest(){
        //去掉频繁为3以下的学生
        //中间操作,流水线 .filter(student -> student.getScore() >= 3)
        //终端操作 .collect(Collectors.toList());
        List<Student> collect = students.stream().filter(student -> student.getScore() >= 3).collect(Collectors.toList());
        collect.forEach(System.out::println);

        //Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
        //Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
        //Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
        //Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
    }

    @Test
    public void mapTest(){
        //对一个 List<Object> 大部分情况下,我们只需要列表中的某一列,或者需要把里面的每一个对象转换成其它的对象,这时候可以使用 map 映射
        List<String> collect = students.stream().map(Student::getSNo).collect(Collectors.toList());
        collect.forEach(System.out::println);

        //XS1001
        //XS1011
        //XS1002
        //XS1022
        //XS1003
    }

    @Test
    public void groupTest(){
        // 按照 地址Id 进行分组
        Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getAddressId));
        collect.forEach((k,v)->{
            v.forEach(t->System.out.println("k: "+k+" v: "+t));
        });

        //k: 1 v: Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
        //k: 1 v: Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
        //k: 1 v: Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
        //k: 2 v: Student(sNo=XS1011, name=河马, gender=男, addressId=2, score=1.4)
        //k: 2 v: Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
    }

    @Test
    public void group2Test(){
        Map<Integer, Double> collect = students.stream().collect(Collectors.groupingBy(Student::getAddressId, Collectors.summingDouble(Student::getScore)));
        collect.forEach((k,v)->System.out.println("k: "+k+" ,v: "+v));

        //k: 1 ,v: 11.4
        //k: 2 ,v: 6.300000000000001
    }

    @Test
    public void sortTest(){
        //按照某个熟悉排序
        students.sort((v1,v2)-> v2.getScore().compareTo(v1.getScore()));
        students.forEach(System.out::println);
    }

    @Test
    public void sortSTest(){
        //流处理不会改变原列表,需要接受返回值才能得到预期结果
        List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getScore).reversed()).collect(Collectors.toList());
        collect.forEach(System.out::println);

        //Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
        //Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
        //Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
        //Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
        //Student(sNo=XS1011, name=河马, gender=男, addressId=2, score=1.4)
    }

    @Test
    public void sort2STest(){
        //多列排序, score 降序, companyId升序
        List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getAddressId).reversed()
                .thenComparing(Comparator.comparing(Student::getGender).reversed()))
                .collect(Collectors.toList());
        collect.forEach(System.out::println);

        //Student(sNo=XS1011, name=河马, gender=男, addressId=2, score=1.4)
        //Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
        //Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
        //Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
        //Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
    }

    @Test
    public void reduceSTest(){
        //总分和
        Double reduce = students.stream().parallel().map(Student::getScore).reduce(0d, Double::sum);
        System.out.println(reduce);

        //17.700000000000003
    }

}

猜你喜欢

转载自blog.csdn.net/qq_34168515/article/details/105322318