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的方法:   java8新特性lambda和Stream新手springboot案例2020年新版   已经存在的 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操作分类
中间操作(Intermediate operations) 无状态(Stateless) unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
有状态(Stateful) distinct() sorted() sorted() limit() skip()
结束操作(Terminal operations) 非短路操作 forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
短路操作(short-circuiting) anyMatch() allMatch() noneMatch() findFirst() findAny()
                                     

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 测试 --- 跟着我一起做

 实体类

@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
    }

}

 

       

 

 

 

 

 

上一篇:域名和ip不能访问的原因


下一篇:java8 功能比较强大的两个终止操作 reduce和collect