一、前言
本文,前段是原理,后半段是案例,如果懒得看原理的朋友,可以直接跳到案例
敲黑板,跟我边做边学,直接到案例那一段,非常详细。
什么是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---关键字:“语法糖”,
- 用逗号分隔的参数列表
- -> 符号
- 和 语句块 组成
// 使用匿名内部类 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 标记
- 默认方法可以被覆写
我们基本不需要定义自己的函数式接口,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 } }