接口需要有且仅有一个需要实现的方法
使用方法,以创建新的线程为例
// 例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());一般语法
参数使用小括号使用箭头指向实现的代码体有返回值时最后直接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 :: methodlambda表达式外部的变量,内部的变量和传入的参数变量
// 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并不指向其产生的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)
(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接口的静态方法: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();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对应的集合,然后对每个元素执行所有的函数。
sorted 排序 Stream 函数拼接
Stream的3个注意事项:
Stream只能操作一次,因为操作一次就会返回一个新的Stream
Stream方法返回的是新的流
Stream不调用终结方法,中间的操作不会执行
简介:汇聚操作(也称为折叠)是接收一个数据序列为输入,反复使用合并操作,把序列中的元素合并成一个汇总的结果。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。
例如
查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。
分类
**可变汇聚:**把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder; **其他汇聚:**除去可变汇聚剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;
参考链接: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中的最大|最小值