JDK8新特性:Lambda表达式概述

it2024-03-31  50

JDK8新特性:Lambda表达式

概述使用规则lambda表达语法Lambda表达式可用变量范围lambda表达式中this的概念方法引用和构造器引用 Stream APIStream的理解/特点使用stream的步骤创建Stream的方式(两种)Stream的集中常用方法Stream方法总结 汇聚(Reduce)Stream概述可变汇聚其他汇聚

概述
lambda表达式可以说是实现SAM的语法糖,使得Java编程更加高级,省力lambda表达式可以大大减少代码的冗余
使用规则

接口需要有且仅有一个需要实现的方法

使用方法,以创建新的线程为例

// 例1 // jdk8 之前 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("创建线程。。。"); } }); thread.start(); // jdk8 之后,使用lambda表达式 Thread thread = new Thread(() -> System.out.println("创建线程。。。")); thread.start(); // 例2 // jdk8 之前 List<String> list = Arrays.asList("b", "s", "f", "w", "a", "c", "y"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); // jdk8 之后,使用lambda表达式 List<String> list = Arrays.asList("b", "s", "f", "w", "a", "c", "y"); Collections.sort(list, (o1, o2) -> o1.compareTo(o2)); /*或者*/ Collections.sort(list, Comparable::compareTo);

lambda表达式配合stream API可以更加高效的实现业务代码

/*功能:将list中的所有元素小写*/ List<String> list = Arrays.asList("Hello", "ArE", "yOU", "DOing"); // jdk 8之前 List<String> lowercase = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { lowercase.add(list.get(i).toLowerCase()); } // jdk8 之后,使用lambda表达式 List<String> lowerList = list.stream().map(param -> param.toLowerCase()).collect(Collectors.toList()); // 或者 List<String> lowerList = list.stream().map(String::toLowerCase).collect(Collectors.toList());
lambda表达语法

一般语法

参数使用小括号使用箭头指向实现的代码体有返回值时最后直接return即🉑️。 (Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }

单参数语法

单个参数的情况下,可以省略参数的小括号 param1 -> { statment1; statment2; //............. return statmentM; }

单语句语法

如果方法体只有一条语句,可以省略大括号如果有返回值,还可以省略return关键字 param1 -> statment // 等同于 param1 -> return statment; // 等同于 param1 -> {return statment;}

方法引用写法

使用类名/对象实例 + "::"符号 + 方法名 的方式充当接口某方法的实现

Class or instance :: method
Lambda表达式可用变量范围

lambda表达式外部的变量,内部的变量和传入的参数变量

// outSider为外部变量,uuid是内部变量,param 是传递的变量 String outSider = "outSider"; List<String> list = Arrays.asList("How", "ArE", "yOU", "DOing"); List<String> lowerList = list.stream().map(param -> { String uuid = UUID.randomUUID().toString(); return outSider + " --- " + param.toLowerCase() + " --- " + uuid; }).collect(Collectors.toList());

⚠️注意:使用外部变量时,编译器会为外部变量自动加了final修饰,不能修改其值

因为加了final,如果希望修改,则需要使用对象引用的方式

AtomicReference<String> outSider = new AtomicReference<>("outSider");
lambda表达式中this的概念

lambda表达式中的this并不指向其产生的SAM对象,而是指向外部的对象

public class TestClazz { @Test public void test2() { String outSider = "outSider"; List<String> list = Arrays.asList("How", "ArE", "yOU", "DOing"); List<String> lowerList = list.stream().map(param -> { System.out.println("==>" + this.getClass().getName()); return param.toLowerCase(); }).collect(Collectors.toList()); } } 输出: ==>com.test.TestClazz ==>com.test.TestClazz ==>com.test.TestClazz ==>com.test.TestClazz
方法引用和构造器引用

方法引用

objectName::instanceMethod 对象实例::方法,将lambda表达式的参数作为方法的参数传入其中

ClassName::staticMethod 类名::静态方法,同样是将lambda表达式的参数作为方法的参数传入其中

ClassName::instanceMethod 类名::方法,将lambda表达式的第一个参数作为调用方,调用后面的instanceMethod,其余参数作为instanceMethod方法的参数

第一种:System.out::println 等同于 x->System.out.println(x)

第二种:Math::max 等同于 (x, y)->Math.max(x,y)

第三种:String::toLowerCase 等同于 x->x.toLowerCase()

构造器引用

用法:ClassName::new

将lambda表达式的参数作为构造器的参数,构造出新的对象

eg:BigDecimal::new 等同于 x->new BigDecimal(x)

Stream API

Stream的理解/特点

(1) Stream是元素的集合,类似于Iterator,使用这个集合可以进行多种Iterator无法进行的操作

(2) 支持顺序和并行的对原Stream进行汇聚的操作

public class TestClazz { @Test public void test3() { List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6); long count = nums.stream().filter(num -> num != null).count(); // Integer count = nums.stream().filter(Objects::nonNull).max(Comparable::compareTo).get(); System.out.println(count); } }

解释:图片就是对于Stream例子的一个解析,可以很清楚的看见:原本一条语句被三种颜色的框分割成了三个部分。红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。

使用stream的步骤
创建Stream;转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);对Stream进行聚合(Reduce)操作,获取想要的结果;
创建Stream的方式(两种)

通过使用Stream接口的静态方法:of(…)等 [注意:Java8里接口可以带静态方法]

of()方法 [两个重载的方法,一个接受一个参数,一个接受可变参数] // 声明 public static<T> Stream<T> of(T t); public static<T> Stream<T> of(T... values) // 使用 Stream<Integer> integerStream = Stream.of(1, 2, 3, 5); Stream<String> stringStream = Stream.of("taobao"); generate()方法,创建一个无限长度的stream,所以一般配合limit使用 // 声明 public static<T> Stream<T> generate(Supplier<T> s) { Objects.requireNonNull(s); return StreamSupport.stream( new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false); } // 使用 Stream.generate(new Supplier<Double>() { @Override public Double get() { return Math.random(); } }).limit(20L).forEach(System.out::println); // 或者 Stream.generate(() -> Math.random()).limit(20L).forEach(System.out::println); // 或者 Stream.generate(Math::random).limit(20L).forEach(System.out::println); // 以上三种表达方法一样的 iterate() 方法,也是生成无限长度的stream,但与generate方法不一样的是,其元素的生成是重复的利用给定的种子值(seed)调用指定的函数生成的,就是将seed代入到函数并得到结果,然后再将结果代入到函数中得出结果,如此往复。 // 声明 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); // 使用 Stream.iterate(2, param -> 2 * param).limit(10).forEach(param -> System.out.print(param + " ")); // 结果:2 4 8 16 32 64 128 256 512 1024

其他生成Stream的方法:

(1)生成空的Stream public static Stream empty()

(2)将两个Stream连接后返回 Stream.concat(Stream.empty(), Stream.generate(Math::random).limit(20L));

通过Collection接口的默认方法创建:stream()

Collection接口实现类的实例对象调用父接口的默认实现stream()方法即可返回对应的Stream对象 Stream<Integer> stream = Lists.newArrayList(1,2,3,4,6).stream();
Stream的集中常用方法

distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;(自定义类型是根据对象的hashCode和equals来去除重复元素的。)

filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素

map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;

flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;

// flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字 // flatMap给一段代码理解: Stream<List<Integer>> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) ); Stream<Integer> outputStream = inputStream. flatMap((childList) -> childList.stream());

peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;

limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

整体调用例子

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10); int sum = nums.stream().filter(Objects::nonNull).distinct().mapToInt(num -> num * 2).peek(System.out::println).skip(2).limit(4).sum(); System.out.println(“sum is:+ sum);

说明:

这段代码演示了上面介绍的所有转换方法(除了flatMap),简单解释一下这段代码的含义:给定一个Integer类型的List,获取其对应的Stream对象,然后进行过滤掉null,再去重,再每个元素乘以2,再每个元素被消费的时候打印自身,在跳过前两个元素,最后去前四个元素进行加和运算(解释一大堆,很像废话,因为基本看了方法名就知道要做什么了。这个就是声明式编程的一大好处!)。大家可以参考上面对于每个方法的解释,看看最终的输出是什么。

2 4 6 8 10 12 sum is:36

针对多次转换的说明:

可能会有这样的疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

Stream方法总结
方法名方法作用返回值类型方法种类count统计个数long终结forEach逐一处理void终结collect转化stream成为集合List/Map/…终结limit取用前几个Stream函数拼接skip跳过前几个Stream函数拼接map映射Stream函数拼接concat组合Stream函数拼接distinct去重Stream函数拼接peek添加额外处理函数Stream函数拼接sorted排序Stream函数拼接filter过滤Stream函数拼接

sorted 排序 Stream 函数拼接

Stream的3个注意事项:

Stream只能操作一次,因为操作一次就会返回一个新的Stream

Stream方法返回的是新的流

Stream不调用终结方法,中间的操作不会执行

汇聚(Reduce)Stream

概述

简介:汇聚操作(也称为折叠)是接收一个数据序列为输入,反复使用合并操作,把序列中的元素合并成一个汇总的结果。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。

例如

​ 查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。

分类

**可变汇聚:**把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder; **其他汇聚:**除去可变汇聚剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;

可变汇聚
可变汇聚只有一个方法:collect,它可以把Stream中需要的元素收集到一个容器中。比如,Collection // 重载方法1 <R, A> R collect(Collector<? super T, A, R> collector); // 使用范例 List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).collect(Collectors.toList());

参考链接:Collectors 参考地址

// 重载方法2 /* @param supplier 一个工厂函数,用来生成一个新的容器. * @param accumulator 一种添加新元素的、不干涉的、无状态的函数,用于将额外的元素合并到结果中 * @param combiner 用来把中间状态的多个结果容器合并成为一个(并发的时候会用到) */ <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); // 使用范例 List<Integer> nums = Lists.newArrayList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10); List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null). collect(() -> new ArrayList<Integer>(), (list, item) -> list.add(item), (list1, list2) -> list1.addAll(list2)); // 另一种写法 List<Integer> numsWithoutNull = nums.stream().filter(Objects::nonNull). collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

参考链接 Collector 参考地址

其他汇聚

reduce方法:最通用的方法,count和sum都可以通过它实现

三种重载实现

参数accumulator的accept()方法的实现需要符合结合律,即 (a op b) op c = a op (b op c)返回值是Optional类型的值,防止了NullPointerException,因为Optional在初始化赋值的时候进行了检验,如果是null,就抛出NPE。 Optional<T> reduce(BinaryOperator<T> accumulator); // 范例 List<Integer> ints = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get()); - 与上面基本一致,不同的是它允许用户提供一个循环计算的初始值,如果Stream为空,就直接返回该值。而且这个方法不会返回Optional,因为其不会出现null值 ```java T reduce(T identity, BinaryOperator<T> accumulator); // 范例 List<Integer> ints = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item)); <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

count方法:获取Stream中元素的个数

allMatch:是不是Stream中的所有元素都满足给定的匹配条件

anyMatch:Stream中是否存在任何一个元素满足匹配条件

findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional

noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件

max和min:使用给定的比较器(comparator),返回Stream中的最大|最小值

最新回复(0)