JAVA8的lambda及stream详解 小鱼儿 2022-12-30 10:44 116阅读 0赞 # JAVA8的lambda及stream详解 # ### 目录 ### * JAVA8的lambda及stream详解 * * 一、lambda学习 * * 1. 什么是lambda * 2.函数式接口介绍 * 3.lambda使用方式 * * 1)匿名函数 * 2)方法引用 * 二、stream学习 * * 1. 什么是stream * 2.流的构成 * 3.创建Stream * 4.数据操作 * 5.Stream API接口介绍 * 6. 实例操作 ## 一、lambda学习 ## ### 1. 什么是lambda ### lambda是JAVA8中提供的一个语法糖,它使用代码编写更加简洁、紧凑、灵活。Lambda允许把函数当做当参数来使用,是面向函数式编辑的思想。而这种参数式函数有两种用法: * **匿名函数**:用于实现抽象方法的一块代码段,而这个抽象方法是`函数式接口`中的抽象方法 * **已有函数的引用**:使用双冒号直接引用类或对象的已有方法 来一起看段代码: List<Integer> integers= Arrays.asList(1,3,5,4,2); integers.sort((a,b)->a-b); integers.forEach(System.out::println); //输出正序排列数字1 2 3 4 5 ### 2.函数式接口介绍 ### 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 > 函数式接口可以被隐式转换为 lambda 表达式,也就是所说lambda匿名函数的使用方式。 函数式接口具体有哪些特点呢? 1. 接口有且仅有一个抽象方法 2. JDK8接口中的静态方法和默认方法,都不算是抽象方法。 3. 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。 4. 使用FunctionalInterface注解的接口,但注解也不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响,只是加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。 看一下JAVA8中的定义的Comparator函数式接口源码 @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); default Comparator<T> reversed() { return Collections.reverseOrder(this); } default <U extends Comparable<? super U>> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor) { return thenComparing(comparing(keyExtractor)); } public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); } @SuppressWarnings("unchecked") public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; } ### 3.lambda使用方式 ### #### 1)匿名函数 #### 使用lambda特定的语法,编写接口实现代码段,代码段充当匿名函数做为参数使用。 > 基本语法:()->\{\},其中()为参数列表,\{\}用来描述方法体 为了使语法更加简捷、紧凑,lambda几种变种语法: 1. 语法格式一:无参,无返回 Runnable r=()-> System.out.println("接口内容"); r.run(); 1. 语法格式二:一参,无返回 Consumer<String> c=(x)-> System.out.println(x); c.accept("一参,无返回值"); Consumer<String> c1=x-> System.out.println(x); c1.accept("一参,无返回值,省去括号"); 1. 语法格式三:两参,有返回,多语句 Comparator<Integer> c=(x,y)->{ System.out.println("多语句测试"); return Integer.compare(x,y); }; System.out.println(c.compare(3,2)); 1. 语法格式四:两参,有返回 //省略大括号里和return Comparator<Integer> c=(x,y)->Integer.compare(x,y); System.out.println(c.compare(3,2)); -------------------- **语法糖的简写要旨:能省则省** * 省略匿名函数的传统语法结构 * 省略参数类型,JVM编译器会自动根据上下文推断参数类型 * 一个参数,省略lambda左侧的括号 * 一句方法体,省略lambda右侧的大括号和return -------------------- **四大内置核心函数式接口** 1. Consumer : 消费型接口 @FunctionalInterface public interface Consumer<T> { void accept(T t); } //使用方法 @Test public void consumerInvoker(String info,Consumer<String> consumer){ System.out.println("consumer接口调用方法体"); consumer.accept(info); } public void testConsumer() { consumerInvoker("consumerInvoker的参数",(x)-> System.out.println("consumer的接口实现,使用参数为:"+ x)); } //输出 consumer接口调用方法体 consumer的接口实现,使用参数为:consumerInvoker的参数 1. Supplier : 供给型接口 @FunctionalInterface public interface Supplier<T> { T get(); } @Test public void supplierTest(){ List<Integer> integers = supplierInvoker(5, () -> (int) (Math.random() * 10)); for (Integer integer : integers) { System.out.println(integer); } } public List<Integer> supplierInvoker(int num,Supplier<Integer> supplier){ ArrayList<Integer> integers = new ArrayList<>(); for (int i = 0; i < num; i++) { integers.add(supplier.get()); } return integers; } 1. Predicate : 断言型接口 @FunctionalInterface public interface Predicate<T> { boolean test(T t); } @Test public void predicateTest() { List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9,0); List<Integer> list1 = predicateInvoker(list, (x) -> x % 2 == 0); for (Integer integer : list1) { System.out.println(integer); } } public List<Integer> predicateInvoker(List<Integer> list, Predicate<Integer> pre){ ArrayList<Integer> integers = new ArrayList<>(); for (Integer integer : list) { if (pre.test(integer)) { integers.add(integer); } } return integers; } 1. Function: 函数型接口 @FunctionalInterface public interface Function<T, R> { R apply(T t); } @Test public void functionTest() { Double v = functionInvoker(10000d, (x) -> x * (1 + 0.03)); System.out.println("理财后我有"+ v +"元钱"); System.out.println("提现后我还有"+functionInvoker(v,(x)->x-10)+"元钱"); } public Double functionInvoker(Double money, Function<Double,Double> fun){ System.out.println("我有"+money+"元钱"); return fun.apply(money); } //输出 我有10000.0元钱 理财后我有10300.0元钱 我有10300.0元钱 提现后我还有10290.0元钱 -------------------- **四大内置核心函数式接口的扩展接口** 与四大内置核心函数接口同包(java.util.function)下的其它扩展接口 <table> <thead> <tr> <th>接口</th> <th>参数</th> <th>返回值</th> </tr> </thead> <tbody> <tr> <td>BiFunction<T,U,R></td> <td>T,U</td> <td>R</td> </tr> <tr> <td>UnaryOperator<T><br>(Function子接口)</td> <td>T</td> <td>T</td> </tr> <tr> <td>BinaryOperator<T><br>(BiFunction子接口)</td> <td>T,T</td> <td>T</td> </tr> <tr> <td>ToDoubleBiFunction<T, U><br>ToLongBiFunction<T, U><br>ToIntBiFunction<T, U></td> <td>T,U</td> <td>double<br>long<br>int</td> </tr> <tr> <td>ToDoubleFunction<T><br>……</td> <td>T</td> <td>double<br>……</td> </tr> <tr> <td>DoubleFunction<R><br>LongFunction<R><br>IntFunction<R></td> <td>double<br>long<br>int</td> <td>R</td> </tr> <tr> <td>BiConsumer<T, U></td> <td>T,U</td> <td>void</td> </tr> <tr> <td>ObjDoubleConsumer<T></td> <td>T,double</td> <td>void</td> </tr> <tr> <td>……</td> <td>……</td> <td>……</td> </tr> </tbody> </table> java.util.function包下总共43个常用函数式接口: `BiConsumer<T,U>` `BiFunction<T,U,R>` `BinaryOperator<T>` `BiPredicate<T,U>` `BooleanSupplier` `Consumer<T>` `DoubleBinaryOperator` `DoubleConsumer` `DoubleFunction<R>` `DoublePredicate` `DoubleSupplier` `DoubleToIntFunction` `DoubleToLongFunction` `DoubleUnaryOperator` `Function<T,R>` `IntBinaryOperator` `IntConsumer` `IntFunction<R>` `IntPredicate` `IntSupplier` `IntToDoubleFunction` `IntToLongFunction` `IntUnaryOperator` `LongBinaryOperator` `LongConsumer` `LongFunction<R>` `LongPredicate` `LongSupplier` `LongToDoubleFunction` `LongToIntFunction` `LongUnaryOperator` `ObjDoubleConsumer<T>` `ObjIntConsumer<T>` `ObjLongConsumer<T>` `Predicate<T>` `Supplier<T>` `ToDoubleBiFunction<T,U>` `ToDoubleFunction<T>` `ToIntBiFunction<T,U>` `ToIntFunction<T>` `ToLongBiFunction<T,U>` `ToLongFunction<T>` `UnaryOperator<T>` -------------------- #### 2)方法引用 #### 如果lambda表达式的匿名函数方法体中实现的功能,已经有现成的方法时,我们可以通过方法的名称引用已有方法。 <table> <thead> <tr> <th>方法引用</th> <th>等价的lambda表达式</th> </tr> </thead> <tbody> <tr> <td>String::valueOf</td> <td>x -> Sting.valueOf(x)</td> </tr> <tr> <td>Object::toString</td> <td>x -> x.toString()</td> </tr> <tr> <td>x::toString</td> <td>() -> x.toString()</td> </tr> <tr> <td>ArrayList::new</td> <td>() -> new ArrayList<>()</td> </tr> </tbody> </table> * 实例方法引用 //例1 Consumer<String> c=(x) -> System.out.println(x); Consumer<String> c1=System.out::println; //例2 final SysUser sysUser=new SysUser(); Supplier<String> s=() -> sysUser.getFullname(); Supplier<String> s1=sysUser::getFullname; * 静态方法引用 Function<List<Integer>,Integer> f=(x) -> Collections.max(x); Function<List<Integer>,Integer> f1=Collections::max; * 类的实例方法引用 //当lambda参数列表中第一个参数为是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用类的实例方法,ClassName::method BiPredicate<String,String> p=(x,y) -> x.equals(y); BiPredicate<String,String> p1=String::equals; * 构造方法引用 Supplier<SysUser> su=() -> new SysUser(); Supplier<SysUser> su1=SysUser::new; * 数组构造方法引用 IntFunction<Integer[]> intFunction=(x) -> new Integer[x]; Integer[] apply = intFunction.apply(3); IntFunction<Integer[]> intFunction1=Integer[]::new; Integer[] apply1 = intFunction1.apply(3); > 方法引用最明显的特点就是只有方法名,而没有参数,只能弄明白JVM是如何推导参数的,才能熟练使用方法引用。 > **适配方法一**:引用的方法与函数式接口的抽象方法,参数列表相同,适用类型:`实例方法引用` `静态方法引用` `构造方法引用` > **适配方法二**:参数列表中第一个参数充当实例方法的调用者,而第二个参数充当实例方法的参数,适用类型:`类的实例方法引用` > **适配方法三**:整数型参数充当数组的构造方法的初始长度值,适用类型:`数组构造方法引用` ## 二、stream学习 ## ### 1. 什么是stream ### stream是把要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。 你可以把它当做是是一个高级版本的Iterator,它对集合(Collection)对象功能进行了增强,它还提供并行模式进行数据操作,而无需编写多线程代码;同时它的短路操作可以在不用处理全部元素就可以返回结果,达到节省资源,提高性能的作用。 你也可以把Stream当做是一种可供流式操作的数据视图,类似数据库中视图的概念,它不改变源数据集合,如果对其有修改操作它会返回一个新的数据集合。 Stream API 借助 Lambda 表达式,极大的提高编程效率和程序可读性;它是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。 ### 2.流的构成 ### 当我们使用一个流的时候,通常包括三个基本步骤: 获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。 ### 3.创建Stream ### * Stream接口的静态工厂方法 * Collection对象和数组对象转换成Stream **静态工厂方法** * of Stream<String> stringStream=Stream.of("aaa","bbb","ccc","ddd"); stringStream.forEach(System.out::println); * generator Stream.generate(Math::random).limit(5).forEach(System.out::println); * iterate Stream.iterate(1,(x)->x+1).limit(5).forEach(System.out::println); **Collection对象和数组对象转换成Stream** * Collect对象转换成Stream List<String> arrayList=new ArrayList<>(); Stream<String> stream = arrayList.stream(); Stream<String> stringStream1 = arrayList.parallelStream();//并行stream * 数组对象转换成Stream String[] sArr = new String[10]; Stream<String> stream1 = Arrays.stream(sArr); ### 4.数据操作 ### 流的操作类型分为两种: * **中间操作Intermediate** :一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而中间操作又可以分为无状态操作和有状态操作 * 无状态操作 `unordered()` `filter()` `map()` `mapToInt()` `mapToLong()` `mapToDouble()` `flatMap()` `flatMapToInt()` `flatMapToLong()` `flatMapToDouble()` `peek()` * 有状态操作 `distinct()` `sorted()` `sorted()` `limit()` `skip()` > 无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果 * **终止操作Terminal** :一个流只能有一个 终止操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。终止操作的执行,才会真正开始流的遍历,并且会生成一个结果或产生数据操作效果,终止操作又可分为非短路操作和短路操作 * 非短路操作 `forEach()` `forEachOrdered()` `toArray()` `reduce()` `collect()` `max()` `min()` `count()` * 短路操作 `anyMatch()` `allMatch()` `noneMatch()` `findFirst()` `findAny()` > 短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。 在对于一个 Stream 进行N中间操作时,是不是进行了N次for 循环?其实不是这样的,转换操作都是懒操作,多个转换操作只会在 终止操作的时候才会融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 终止操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。 ### 5.Stream API接口介绍 ### <table> <thead> <tr> <th>接口名称</th> <th align="left">说明</th> </tr> </thead> <tbody> <tr> <td>filter</td> <td align="left">过滤流,过滤流中的元素,返回一个符合条件的Stream</td> </tr> <tr> <td>map</td> <td align="left">转换流,将一种类型的流转换为另外一种流。(mapToInt、mapToLong、mapToDouble 返回int、long、double基本类型对应的Stream)</td> </tr> <tr> <td>flatMap</td> <td align="left">简单的说,就是一个或多个流合并成一个新流。(flatMapToInt、flatMapToLong、flatMapToDouble 返回对应的IntStream、LongStream、DoubleStream流。)</td> </tr> <tr> <td>distinct</td> <td align="left">返回去重的Stream。</td> </tr> <tr> <td>sorted</td> <td align="left">返回一个排序的Stream。</td> </tr> <tr> <td>peek</td> <td align="left">主要用来查看流中元素的数据状态。</td> </tr> <tr> <td>limit</td> <td align="left">返回前n个元素数据组成的Stream。属于短路操作</td> </tr> <tr> <td>skip</td> <td align="left">返回第n个元素后面数据组成的Stream。</td> </tr> <tr> <td>forEach</td> <td align="left">循环操作Stream中数据。</td> </tr> <tr> <td>toArray</td> <td align="left">返回流中元素对应的数组对象。</td> </tr> <tr> <td>reduce</td> <td align="left">聚合操作,用来做统计。</td> </tr> <tr> <td>collect</td> <td align="left">聚合操作,封装目标数据。</td> </tr> <tr> <td>min、max、count</td> <td align="left">聚合操作,最小值,最大值,总数量。</td> </tr> <tr> <td>anyMatch</td> <td align="left">短路操作,有一个符合条件返回true。</td> </tr> <tr> <td>allMatch</td> <td align="left">所有数据都符合条件返回true。</td> </tr> <tr> <td>noneMatch</td> <td align="left">所有数据都不符合条件返回true。</td> </tr> <tr> <td>findFirst</td> <td align="left">短路操作,获取第一个元素。</td> </tr> <tr> <td>findAny</td> <td align="left">短路操作,获取任一元素。</td> </tr> <tr> <td>forEachOrdered</td> <td align="left">按元素顺序执行循环操作。</td> </tr> </tbody> </table> ### 6. 实例操作 ### 创建一个学生对象的List对象,然后使用stream方式对学生列表对象进行聚合、过滤、排序、映射等操作。 创建学生List List<Student> students=new ArrayList<Student>(){ { add(new Student(1,"张三","语文",88d)); add(new Student(2,"张三","数学",90d)); add(new Student(3,"张三","英语",82d)); add(new Student(4,"李四","语文",90d)); add(new Student(5,"李四","数学",80d)); add(new Student(6,"李四","英语",72d)); add(new Student(7,"王五","数学",68d)); add(new Student(8,"王五","语文",70d)); add(new Student(9,"王五","英语",92d)); }}; 获得学生名单 //获取学生名单 List<String> collect2 = students.stream().map(Student::getName) .distinct().collect(Collectors.toList()); collect2.forEach(System.out::println); //获取学生名单 Set<String> collect = students.stream().map(Student::getName) .collect(Collectors.toSet()); collect.stream().forEach(System.out::println); 获得张三的成绩单 //获得张三的成绩单 Map<String, Double> zsDate = students.stream() .filter((x) -> x.getName().equals("张三")) .collect(Collectors.toMap(Student::getCourse, Student::getScore)); zsDate.forEach((k,v)-> System.out.println(k+"成绩是:"+v)); 查找每个人的总成绩,并进行排名 //查找每个人的总成绩,并进行排名 Map<String, Double> collect = students.stream().collect(Collectors.groupingBy( Student::getName, Collectors.summingDouble(Student::getScore) )).entrySet().stream().sorted(Entry.<String, Double>comparingByValue().reversed()) .collect(Collectors.toMap(Entry::getKey , Entry::getValue,(x,y)->x,LinkedHashMap::new)); collect.forEach((k,v)-> System.out.println(k+"的总成绩为"+v)); 查找各科成绩最高的 //查找各科成绩最高的 Map<String, Double> collect = students.stream().collect(Collectors.groupingBy( Student::getCourse, Collectors.collectingAndThen( Collectors.maxBy(Comparator.comparingDouble(Student::getScore)), (x) -> x.get().getScore() ) )); collect.forEach((k,v)-> System.out.println(k+"的最高成绩为"+v)); 查询成绩为优异的学生 //查询成绩为优异的学生 List<String> collect = students.stream().collect(Collectors.groupingBy( Student::getName, Collectors.collectingAndThen(Collectors.toList(), x -> x.stream().noneMatch(y -> y.getScore() < 80)))) .entrySet().stream().filter(Map.Entry::getValue) .map(Map.Entry::getKey).collect(Collectors.toList()); collect.forEach(System.out::println);
相关 Java 8新特性:Lambda表达式、Stream API详解 1. Lambda表达式: Java 8引入了lambda表达式,这是一种更简洁的函数定义方式。例如: - 声明一个方法: ``` int a 一时失言乱红尘/ 2024年10月17日 01:12/ 0 赞/ 41 阅读
相关 Java 8新特性:Lambda表达式及Stream API详解 Java 8引入了新的特性,主要包括Lambda表达式和Stream API。以下是这两部分的详细解释: 1. Lambda表达式: Lambda表达式是Java 8简化匿名 系统管理员/ 2024年10月08日 08:51/ 0 赞/ 42 阅读
相关 Lambda&Stream (Java8新特性) Lambda&Stream 一.Lambda 1.为什么需要Lambda 2. Lambda简介 3. Lambda表达式的 超、凢脫俗/ 2024年03月17日 14:38/ 0 赞/ 71 阅读
相关 java8-lambda-stream 文章目录 参考文章 参考文章 [菜鸟教程 Java 8 Stream][Java 8 Stream] public static void mai r囧r小猫/ 2023年01月05日 03:55/ 0 赞/ 129 阅读
相关 JAVA8的lambda及stream详解 JAVA8的lambda及stream详解 目录 JAVA8的lambda及stream详解 一、lambda学习 1. 什么 小鱼儿/ 2022年12月30日 10:44/ 0 赞/ 117 阅读
相关 JAVA8 : lambda和Stream 详解 最近出了jdk10; 可是连 java8 都用的不熟练 ; 这边简单介绍一下 java 8 中;我觉得比较有意思的lambda 表达式和流操作。 前期准备 : ╰半夏微凉°/ 2022年05月28日 07:38/ 0 赞/ 168 阅读
相关 java8 lambda表达式和stream java8 lambda表达式 1. Lambda表达式的语法 > 基本语法: > (parameters) -> expression > 或 > ( 野性酷女/ 2022年04月11日 09:54/ 0 赞/ 210 阅读
相关 java8的Lambda详解 (jdk1.8)Lambda 表达式实质是一个可以实现某一接口的匿名内部类的中的方法 Lambda表达式组成部分 括号包起来的参数 (若参数仅有一个 可省略括号) 短命女/ 2022年03月17日 00:50/ 0 赞/ 170 阅读
相关 java8的lambda的stream简单使用 import java.util.Arrays; import java.util.Comparator; import java.util.List; 男娘i/ 2022年01月12日 00:03/ 0 赞/ 201 阅读
还没有评论,来说两句吧...