原文链接:
https://juejin.im/post/6861849472499417096
Java 8 所做的改变,在许多方面比Java 历史上任何一次改变都更加深远,这些改变会让你的编程更加容易
例子:
传统写法:
List<Person> personList = Arrays.asList(new Person(21,50),new Person(22,55),new Person(23,60));Collections.sort(personList, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getWeight().compareTo(o2.getWeight()); }});复制代码
Java 8写法:
personList.sort(Comparator.comparing(Person::getWeight));复制代码
熟悉 Linux 操作的同学对这个指令应该不默认:
cat testFile | tr "[A-Z]" "[a-z]" | sort | tail -3
这种操作便是基于流式操作,cat 会把文件转换创建成一个流,然后tr会转换流中字符,sort会对流中的行进行排序,tail -3则会输出流的最后三行。这种就像是流水线操作,经过每个中转站,将处理完的结果转入下一个处理中心,最后得到最终结果。
Java 8 的第一个编程思想就是流处理,流式一系列数据项,一次只生成一项,程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能就是另一个程序的输入流。
已知一个集合中有以下几种花:
List<Flower> flowerList = Arrays.asList(new Flower("red", 6), new Flower("yellow", 7), new Flower("pink", 8));复制代码
这个时候如果我想要红花,那么传统写法是这样子的:
List<Flower> resList = new ArrayList<>();for (Flower flower : flowerList) { if (StringUtils.equals("red", flower.getColor())) { resList.add(flower); }}复制代码
那么如果我想要8块钱以下的花,那么写法就是这样的:
List<Flower> resList = new ArrayList<>();for (Flower flower : flowerList) { if (flower.getPrice() < 8) { resList.add(flower); }}复制代码
其实代码写法大部分都是一样的,只是判断的条件不一样,那么我们进行第一版优化:
我们将判断方法抽取出来:
public static boolean isRed(Flower flower) { return StringUtils.equals("red", flower.getColor());}public static boolean isLowPrice(Flower flower) { return flower.getPrice() < 8;}复制代码
借助函数式接口Predicate,将我们自定义的方法传递进去:
public static List<Flower> filterFlower(List<Flower> flowers, Predicate<Flower> p) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if (p.test(flower)) { resList.add(flower); } } return resList;}复制代码
使用:
filterFlower(flowerList,Flower::isRed);filterFlower(flowerList,Flower::isLowPrice);复制代码
我们也可以借助 Lambda 流来传递函数,就可以不用事先写好判断函数了:
filterFlower(flowerList, (Flower f) -> StringUtils.equals("red", f.getColor()));filterFlower(flowerList, (Flower f) -> f.getPrice() < 8);复制代码
在 Java 8 之前我们可以实现一个接口然后被强制重写这个接口的方法,那么隐含着很多问题:如果动物类新增一个fly()方法,那么其实dog这个类是不需要fly这个方法的,但是如果不重写就会编译报错。因此在 Java 8 之后也设计了默认方法这一种方式巧妙的解决了这种问题。
interface Animal { void eat(); void fly();}class bird implements Animal { @Override public void eat() {} @Override public void fly() { System.out.println("bird fly"); }}class dog implements Animal { @Override public void eat() {}}复制代码
Java 8 之后可以这样写:
interface Animal { void eat(); default void fly() { System.out.println("animal fly"); }}class bird implements Animal { @Override public void eat() {} @Override public void fly() { System.out.println("bird fly"); }}class dog implements Animal { @Override public void eat() {}}复制代码
以上便是 Java 8 的部分特性,那么接下来就让我们来了解 Java 8的使用
开发中,我们需要应对不断的需求,怎么样才能做到自适应可扩展就是我们要关注的地方。
需求1:筛选出红色的花
public static List<Flower> filterFlower(List<Flower> flowers) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if (StringUtils.equals("red", flower.getColor())) { resList.add(flower); } }}复制代码
需求2:筛选出绿色的话
聪明的你肯定想到了我们可以通过传递一个颜色参数来过滤花朵,而不用每一次都修改主要代码。
public static List<Flower> filterFlowerByColor(List<Flower> flowers, String color) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if (StringUtils.equals(color, flower.getColor())) { resList.add(flower); } }}复制代码
需求3:筛选出价格小于8块钱的花
这样子我们只能再写一个方法来实现这个需求,为了防止后续价格的变化,聪明的我们提前将价格设置成可变参数。
public static List<Flower> filterFlowerByPrice(List<Flower> flowers, Integer price) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if (flower.getPrice() < price) { resList.add(flower); } }}复制代码
为了保持代码的整洁,我们被迫重写了一个方法来实现上述的需求:
public static List<Flower> filterFlower(List<Flower> flowers, String color, Integer price, Boolean flag) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if ((flag && flower.getPrice() < price) || (!flag && StringUtils.equals(color, flower.getColor()))) { resList.add(flower); } } return resList;}复制代码
通过flag来控制要筛选价格类型的花还是颜色类型的花,但是这种写法实在是不美观。
那么,我们既然都能把花的属性作为参数进行传递,那么我们能不能我们能不能把过滤花的这种行为也作为一个参数进行传递,想着想着,你就动起了手:
首先定义一个过滤行为的接口:
interface FilterPrecidate { boolean test(Flower flower);}复制代码
然后自定义两个行为过滤类继承这个接口:
class RedColorFilterPredicate implements FilterPrecidate { @Override public boolean test(Flower flower) { return StringUtils.equals("red", flower.getColor()); }}class LowPriceFilterPredicate implements FilterPrecidate { @Override public boolean test(Flower flower) { return flower.getPrice() < 8; }}复制代码
然后重写我们的过滤方法,通过将行为作为参数传递:
public static List<Flower> filterFlower(List<Flower> flowers, FilterPrecidate filter) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if (filter.test(flower)) { resList.add(flower); } } return resList;}/***** 使用 *****/filterFlower(flowerList,new RedColorFilterPredicate());filterFlower(flowerList,new LowPriceFilterPredicate());复制代码
这样子我们的代码已经很明了,但是我们再观察一下上面的方法,filterFlower()这个方法只能传递对象作为参数,而FilterPrecidate对象的核心方法也只有test(),如果我们有新的行为就需要新建一个类继承FilterPrecidate接口实现test()方法。那么我们有没有办法直接将test()这一个行为作为参数传递,答案是有的:Lombda.
filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8);复制代码
我们甚至可以将多种行为作为作为一个参数传递:
filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8 && StringUtils.equals("red", flower.getColor()));复制代码
可以看到,行为参数化是一个很有用的模式,它能够轻松地使用不断变化的需求,这种模式可以把一个行为封装起来,并通过传递和使用创建的行为将方法的行为参数化。
它可以替代匿名类
如果我们将一个鲜花的集合按照价格进行排序,我们会这样做:
Collections.sort(flowerList, new Comparator<Flower>() { @Override public int compare(Flower o1, Flower o2) { return o1.getPrice().compareTo(o2.getPrice()); }});复制代码
那么通过行为参数化我们可以这样写:
Collections.sort(flowerList,(o1, o2) -> o1.getPrice().compareTo(o2.getPrice()));复制代码
也可以这样写:
Collections.sort(flowerList, Comparator.comparing(Flower::getPrice));复制代码
甚至可以这样写:
flowerList.sort(Comparator.comparing(Flower::getPrice));复制代码
对比一下传统写法,你是不是已经开始爱上这种方式的写法了
Lambda可以理解为是一种简洁的匿名函数的表示方式:它没有名称,但它有参数列表,函数主体,返回类型,还可以有一个可以抛出的异常。
Lambda表达式鼓励采用行为参数化的风格。利用Lambda表达式我们可以自定义一个Comparator对象
函数式接口就是只定义一个抽象方法的接口,并使用@FunctionalInterface标记。
例如:
Lambda 表达式可以允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的示例(Lambda表达式就是函数式接口一个具体实现的示例)。
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("这是传统的写法"); } };Runnable r = () -> System.out.println("这是使用 Lambda 的写法");复制代码
Predicate
这个接口中定义了一个test()的抽象方法,它接受泛型 T 对象,并返回一个 boolean。你如果需要 表示一个涉及类型 T 的布尔表达式时,就可以使用这个接口。
public static List<Flower> filterFlower(List<Flower> flowers, Predicate<Flower> p) { List<Flower> resList = new ArrayList<>(); for (Flower flower : flowers) { if (p.test(flower)) { resList.add(flower); } } return resList;}/***** 使用方式 *****/filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8);复制代码
Consumer
这个接口定义了一个accept()的抽象方法,它接受泛型 T 对象,没有返回(void)。你如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。
List<Integer> nums = Arrays.asList(1,2,3,4);nums.forEach(integer -> System.out.println(integer));复制代码
Function
这个接口定义了一个apply()的抽象方法,它接受泛型 T 对象,并返回一个泛型 R 的对象。你如果需要定义一个Lambda,将输入对象的信息映射输出,就可以使用这个接口。
(String s) -> s.length()复制代码
Supplier
这个接口定义了一个get()的抽象方法,它没有传入参数,会返回一个泛型 T 的对象,如果你需要定义一个 Lambda,输出自定义的对象,就可以使用这个接口。
Callable<Integer> call = () -> 1 ;复制代码
以这个为例子:
filter(flowerList, (Flower flower) -> flower.getPrice() > 8);
filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8);
我们可以继续将这个代码简化为:
filterFlower(flowerList, f -> f.getPrice() > 8);
Lambda 表达式不仅能够使用主体里面的参数,也能够使用自由变量(在外层作用域中定义的变量)。
int tmpNum = 1;Runnable r = () -> System.out.println(tmpNum);复制代码
注意点:Lambda 表达式对于全局变量和静态变量可以没有限制的使用,但是对于局部变量必须显示声明为 final
因为实例变量是存储在堆中,而局部变量是存储在栈中,属于线程私有的。而 Lambda 是在一个线程中使用的,访问局部变量只是在访问这个变量的副本,而不是访问原始值。
方法引用就是让你根据已有的方法实现来创建 Lambda表达式。可以看做是单一方法的 Lambda 的语法糖。
例子:
List<Flower> flowerList = Arrays.asList(new Flower("red", 6), new Flower("yellow", 7), new Flower("pink", 8));复制代码
List<Integer> nums = Arrays.asList(1, 2, 3, 4);复制代码
比较器复合
我们有一组鲜花集合如下:
List<Flower> flowerList = Arrays.asList(new Flower("red", 6), new Flower("yellow", 7), new Flower("pink", 8), new Flower("white", 8));复制代码
按鲜花的价格进行排序:
flowerList.sort(Comparator.comparing(Flower::getPrice));复制代码
这样子默认是使用升序进行排列的,那么我们如果想进行降序:使用 reversed()
flowerList.sort(Comparator.comparing(Flower::getPrice).reversed());复制代码
这里的粉花和白花的价格一样,那我们在价格排序完后再按照颜色排序那应该怎么做:使用 thenComparing()
flowerList.sort(Comparator.comparing(Flower::getPrice).thenComparing(Flower::getColor));复制代码
谓词复合
用于Predicate接口
Predicate<Flower> redFlower = (t) -> StringUtils.equals("red",t.getColor());Predicate<Flower> notRedFlower = redFlower.negate();复制代码
Predicate<Flower> redFlower = (t) -> StringUtils.equals("red", t.getColor());Predicate<Flower> lowPriceFlower = (t) -> t.getPrice() < 8;Predicate<Flower> redAndLowPriceFlower = redFlower.and(lowPriceFlower);复制代码
Predicate<Flower> redFlower = (t) -> StringUtils.equals("red", t.getColor());Predicate<Flower> lowPriceFlower = (t) -> t.getPrice() < 8;Predicate<Flower> redOrLowPriceFlower = redFlower.or(lowPriceFlower);复制代码
函数复合
用于Function接口
Function<Integer, Integer> addRes = a1 -> a1 + 1;Function<Integer, Integer> mulRes = a1 -> a1 * 2;Function<Integer, Integer> andThenResult = addRes.andThen(mulRes);Integer apply = andThenResult.apply(1); // 结果为 4 ==> (1 + 1) * 2复制代码
Function<Integer, Integer> addRes = a1 -> a1 + 1;Function<Integer, Integer> mulRes = a1 -> a1 * 2;Function<Integer, Integer> composeResult = addRes.compose(mulRes);Integer apply = composeResult.apply(1); // 结果为 3 ==> (1 * 2) + 1复制代码
两者的区别就是操作的顺序不一样
集合式 Java 中使用最多的API。流是 Java API 的新成员,它允许以声明式方式处理数据集合,可以看作是遍历数据集的高级迭代器。而且,刘海可以透明地并行处理,这样就可以无需多写任何多线程代码了。
现在有一组花的集合如下:
List<Flower> flowerList = Arrays.asList(new Flower("red", 10), new Flower("yellow", 7), new Flower("pink", 8), new Flower("white", 8), new Flower("black", 12));复制代码
需求:获取10块钱以下并且按照价格排序的花的颜色
传统写法:
List<Flower> lowPriceFlowers = new ArrayList<>();for (Flower flower : flowerList) { if (flower.getPrice() < 10) { lowPriceFlowers.add(flower); }}Collections.sort(lowPriceFlowers, new Comparator<Flower>() { @Override public int compare(Flower o1, Flower o2) { return o1.getPrice().compareTo(o2.getPrice()); }});List<String> lowPriceFlowerColor = new ArrayList<>();for (Flower priceFlower : lowPriceFlowers) { lowPriceFlowerNames.add(priceFlower.getColor());}复制代码
为了完成这个需求不仅代码量大,还多定义了lowPriceFlowers 这个临时变量,真的是糟糕透了! Java 8 之后,代码才应该有它该有的样子:
List<String> colorList = flowerList.stream().filter(t->t.getPrice()<10).sorted(Comparator.comparing(Flower::getPrice)).map(Flower::getColor).collect(Collectors.toList());复制代码
通过filter筛选出10元以下的花,然后通过sorted按照花的价格进行排序,再通过map映射出花的颜色,最后通过collect将流归约成一个集合。filter 处理的结果传给了 sorted 方法,再传给 map 方法,最后传给 collect 方法。
甚至我们还可以利用多核架构并行执行这段代码,只需要把stream()换成parallelStream()
flowerList.parallelStream().filter(t->t.getPrice()<10).sorted(Comparator.comparing(Flower::getPrice)).map(Flower::getColor).collect(Collectors.toList());复制代码
因为 filter 、sorted 、map 和 collect 等操作是与具体线程模型无关的高层次构件,所以它们的内部实现可以是单线程的,也可能透明地充分利用你的多核架构!在实践中,这意味着你用不着为了让某些数据处理任务并行而去操心线程和锁。
集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构,它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中。流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。从另一个角度来说,流就像是一个延迟创建的集合:只有在消费者要求的时候才会计算值。
只能遍历一次:和迭代器类似,流只能遍历一次。遍历完之后,这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍。
List<String> color = Arrays.asList("red", "yellow", "pink");Stream<String> s = title.stream();s.forEach(System.out::println); //在这里 流已经被消费了s.forEach(System.out::println); //如果这里再消费流则会报错!复制代码
流可以拆成三大操作:
获取流 -> 中间操作 -> 终端操作
List<String> colorList = flowerList.stream() //获取流 .filter(t->t.getPrice()<10) //中间操作 .limit(3) //中间操作 .map(Flower::getColor) //中间操作 .collect(Collectors.toList());//终端操作复制代码
1)使用流
筛选:filter
List<String> colorList = flowerList.stream().filter(t->t.getPrice()<10).collect(Collectors.toList());复制代码
筛选去重:distinct
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);复制代码
筛选阶段:limit
List<String> colorList = flowerList.stream().filter(t->t.getPrice()<10).limit(3).collect(Collectors.toList());复制代码
筛选跳跃:skip
List<String> colorList = flowerList.stream().filter(t->t.getPrice()<10).skip(2).collect(Collectors.toList());复制代码
(2)映射
不支持 map() 方法,它会接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> colors = flowerList.stream().map(Flower::getColor).collect(Collectors.toList());复制代码
它是创建一个新的集合,而不是修改原有的集合
(3)流的扁平化
将一个单词的集合,拆分成各个字母的集合:
[Hello,World] ===> [H, e, l, o, W, r, d]
首先我们尝试使用map看能不能解决问题:
List<String> words = Arrays.asList("Hello","World");words.stream().map(t->t.split("")).distinct().collect(Collectors.toList());/***** 结果 *****[[Ljava.lang.String;@2cdf8d8a, [Ljava.lang.String;@30946e09] **/复制代码
可以看到,这样处理后的结果是一个数组的集合,并不是我们想要的结果,这是因为map返回的流实际上是Stream<String[]>类型的。但是我们想要的是Stream<String>来表示一个字符流。
既然需要Stream<String>的字符流,那我们使用Arrays.stream()来处理试一下:
words.stream().map(t -> t.split("")).map(Arrays::stream).distinct.collect(Collectors.toList());/***** 结果 *****[java.util.stream.ReferencePipeline$Head@1698c449, java.util.stream.ReferencePipeline$Head@5ef04b5] **/复制代码
这是返回了一个Stream<String>的集合,貌似只要将这个集合处理合并一下就可以解决问题了。所以flatMap()出现了。
words.stream().map(t->t.split("")).flatMap(t -> Arrays.stream(t)).distinct().collect(Collectors.toList());/***** 结果 *****[H, e, l, o, W, r, d] **/复制代码
果然,已经成功解决了问题,flatMap方法就是让你把一个流中的每个值都转成另一个流,然后把所有的流连接起来成为一个流。
(4)匹配
boolean res = flowerList.stream().anyMatch(t -> t.getPrice() < 8);复制代码
boolean res = flowerList.stream().allMatch(t -> t.getPrice() < 8);复制代码
boolean res = flowerList.stream().noneMatch(t -> t.getPrice() < 8);复制代码
(5)查找
flowerList.stream().filter(t->t.getPrice()<8).findAny();复制代码
flowerList.stream().filter(t->t.getPrice()<8).findFirst();复制代码
(6)归约
reduce 接收两个参数,一个是初始值,一个是将集合中所有元素结合的操作
reduce也支持一个参数,将集合中所有元素结合的操作,不过返回的是一个 Option ,Option 下面会讲到
List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9);
元素求和
传统写法:
int res = 0;for (Integer num : nums) { res += num;}复制代码
改进后:
// 两个参数版int res = nums.stream().reduce(0,(a, b) -> a + b);int res = nums.stream().reduce(0,Integer::sum);// 一个参数版Optional<Integer> o = nums.stream().reduce(Integer::sum);复制代码
最大值和最小值
传统写法:
int max = 0;int min = Integer.MAX_VALUE;for (Integer num : nums) { if (num > max) { max = num; } if (num < min) { min = num; }}复制代码
改进后:
// 两个参数版int max = nums.stream().reduce(0,Integer::max);int min = nums.stream().reduce(Integer.MAX_VALUE,Integer::min);// 一个参数版Optional<Integer> maxOption = nums.stream().reduce(Integer::max);Optional<Integer> minOption = nums.stream().reduce(Integer::min);复制代码
(7)小练习(出于网上)
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。 (2) 交易员都在哪些不同的城市工作过? (3) 查找所有来自于剑桥的交易员,并按姓名排序。 (4) 返回所有交易员的姓名字符串,按字母顺序排序。 (5) 有没有交易员是在米兰工作的? (6) 打印生活在剑桥的交易员的所有交易额。 (7) 所有交易中,最高的交易额是多少? (8) 找到交易额最小的交易
答案:
Stream<String> stream = Stream.of("hello","world");Stream<String> emptyStream = Stream.empty();复制代码
int[] numbers = {2, 3, 5, 7, 11, 13};int sum = Arrays.stream(numbers).sum();复制代码
long uniqueWords = 0;try(Stream<String> lines =Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();}catch(IOException e){}// 使用 Files.lines 得到一个流,其中的每个元素都是给定文件中的一行。然后,你可以对 line 调用 split 方法将行拆分成单词复制代码
如今有一组花的集合如下:
List<Flower> flowerList = Arrays.asList(new Flower("red", 10), new Flower("yellow", 7), new Flower("pink", 8), new Flower("yellow", 8), new Flower("red", 12));复制代码
这个时候我想按照花的颜色进行分类,获取一个Map<String, List<Flower>>
传统写法:
Map<String, List<Flower>> listMap = new HashMap<>();for (Flower flower : flowerList) { if (null == listMap.get(flower.getColor())) { List<Flower> flowers = new ArrayList<>(); listMap.put(flower.getColor(), flowerList); } listMap.get(flower.getColor()).add(flower);}复制代码
相信以上代码是比较常见的,那么当我们学习了 Java 8之后有没有什么比较好的写法呢:
Map<String,List<Flower>> map = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor));复制代码
一行代码解决,Java 8 真的是秀啊!
函数式变成的一个主要优势就是,我们只要告诉它 “做什么”,而不用关心“怎么做”。就像是上一个例子中,我们需要的是按颜色分组,所以我们只要跟收集器说 按照颜色分组就行collect(Collectors.groupingBy(Flower::getColor))。我们上面也比较经常用到的是collect(Collectors.toList(),它的作用就是将我们需要的结果收集成一个集合。
用来计算总数:
Long c1 = flowerList.stream().collect(Collectors.counting());//也可以直接用 count() 方法来计数Long c2 = flowerList.stream().count();复制代码
用来查找最大值和最小值:
Optional<Flower> max = flowerList.stream().collect(Collectors.maxBy(Comparator.comparing(Flower::getPrice)));Optional<Flower> min = flowerList.stream().collect(Collectors.minBy(Comparator.comparing(Flower::getPrice)));复制代码
用来求和:
Integer sum = flowerList.stream().collect(Collectors.summingInt(Flower::getPrice));复制代码
用来求平均数:
Double avg = flowerList.stream().collect(Collectors.averagingInt(Flower::getPrice));复制代码
用来连接字符串:
String color = flowerList.stream().map(Flower::getColor).collect(Collectors.joining(", "));复制代码
如今有一组花的集合如下:
List<Flower> flowerList = Arrays.asList(new Flower("red", 10), new Flower("yellow", 7), new Flower("pink", 8), new Flower("yellow", 8), new Flower("red", 12));/***** 结果 *****{red=[Flower(color=red, price=10), Flower(color=red, price=12)], pink=[Flower(color=pink, price=8)], yellow=[Flower(color=yellow, price=7), Flower(color=yellow, price=8)]} **/复制代码
按照颜色分组:Map<String,List<Flower>>
Map<String,List<Flower>> color = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor));/***** 结果 *****{red=[Flower(color=red, price=10), Flower(color=red, price=12)], pink=[Flower(color=pink, price=8)], yellow=[Flower(color=yellow, price=7), Flower(color=yellow, price=8)]} **/复制代码
统计每种颜色的数量:Map<String, Long>
Map<String, Long> longMap = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor, Collectors.counting()));/***** 结果 *****{red=2, pink=1, yellow=2} **/复制代码
也可以支持多级分组
先按颜色分组,再按价格分组:Map<String, Map<String, List<Flower>>>
Map<String, Map<String, List<Flower>>> collect = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor, Collectors.groupingBy(t -> { if (t.getPrice() < 8) { return "LOW_PRICE"; } else { return "HIGHT_PRICE"; }})));/***** 结果 *****{red={HIGHT_PRICE=[Flower(color=red, price=10), Flower(color=red, price=12)]}, pink={HIGHT_PRICE=[Flower(color=pink, price=8)]}, yellow={HIGHT_PRICE=[Flower(color=yellow, price=8)], LOW_PRICE=[Flower(color=yellow, price=7)]}} **/复制代码
先按颜色分组,再找每个颜色中最贵的花:Map<String, Flower>
Map<String, Flower> f = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Flower::getPrice)), Optional::get)));/***** 结果 *****{red=Flower(color=red, price=12), pink=Flower(color=pink, price=8), yellow=Flower(color=yellow, price=8)} **/复制代码
这个工厂方法接受两个参数——要转换的收集器以及转换函数,并返回另一个收集器。这个收集器相当于旧收集器的一个包装, collect 操作的最后一步就是将返回值用转换函数做一个映射。在这里,被包起来的收集器就是用 maxBy 建立的那个,而转换函数 Optional::get 则把返回的 Optional 中的值提取出来。
Collectors 的常用方法
开发中最经常遇到的异常莫过于NullPointException了吧。因为这就是我们为了方便甚至不可避免的像 null 引用这样的构造所付出的代价。Java 8之后仿佛出现了转机,那就是用Optional来代替null。
上面这段代码乍看之下应该没啥问题,平时开发的时候也很有可能会情不自禁的写出类似这种的代码。但是问题也就来了,真的是每个人都有手机吗,如果new Person().getPhone()获取不到手机,那么调用getType()是不是就会出现熟悉的NullPointException异常了。
为了避免空指针异常,Java 8出现的Optional为我们很好的避免了。
private String getPhoneType(Person person) { if (person != null) { Phone phone = person.getPhone(); if (phone != null) { return phone.getType(); } } return "";}复制代码
每次引用都做一次判空操作,效果想必也不赖,也可以避免空指针异常。当时每一次判空都得添加一个 if 判断,真实让人头大。
从图中可以看出 Optional相当于是一个容器,里面可以装 T 类型的对象。当变量不存在的时候,缺失的值就会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。这就是Optional.empty()和null的区别,如果引用一个 null,那结果肯定是会触发NullPointException异常,但是引用Optional.empty()则没事。
上述代码可修改为:
private String getPhoneType(Person person) { return Optional.ofNullable(person).map(Person::getPhone).map(Phone::getType).orElse("");}复制代码
一行代码搞定,干净利落。
创建一个空的Optional
Optional<Person> personOpt = Optional.empty()
创建一个非空的Optional
Optional<Person> personOpt = Optional.of(person)
Optional.of()不接受空值。如果 person 是一个空值则会抛出 NullPointException 异常,而不是等你试图访问 person 的属性才抛出异常。
创建一个可接受空值的Optional
Optional<Person> personOpt = Optional.ofNullable(Person)
如果 person 是 null ,那么得到的 Optional 对象就是个空对象。
Optional 中的 map()方法和流中的map()相似,都是从Optional对象中提取和转换值。
Optional<String> name = Optional.ofNullable(person).map(Person::getName);复制代码
获取到的是一个Optional对象是为了防止获取到一个 null,我们可以通过Optional.get()来获取值。
我们可以使用get()方法来获取 Optional 的值,也可以使用orElse()来定义一个默认值,遭遇到空的Optional值的时候,默认值会作为该方法的调用返回值。以下是Optional的常用方法:
最简单但又是最不安全的方法,如果变量存在,则直接返回封装的变量值,反之则抛出NullpointException异常。
允许自己定义一个默认值在Optional为空的时候返回。
是orElse()方法的延迟调用版,在Optional对象不含值的时候执行调用。
和get()方法类似,在Optional对象为空的时候会抛出一个异常,但是这个异常我们可以自定义。
在Optional对象存在的执行的方法,反之不操作。也接受一个空参数的,如果
在 Java 8之前,我们对日期和时间的支持只能依赖 java.util.Date类,这个类无法表示日期,只能以毫秒的精度表示时间。而且它的表现方式也不是那么直观,在Java1.0的Date这个类中,年份的起始是 1900 年,月份的起始是 0 开始,如果我们这个时候想要构造一个 2020年7月18号的日期,我们就得这样做:
Date date = new Date(120, 6, 18);System.out.println(date); // Sat Jul 18 00:00:00 CST 2020复制代码
这种的构造方式简直是糟糕透了不是吗,对于不了解Date 的来说太不友好了。在java1.1 后出现了Calender这个类,而Date中大部分方法都被废弃了,但是Calender这个类中也有类似的问题和设计缺陷,而且两个日期类的出现,我们有时候也难以选择使用哪一个。
创建一个 LocalDate 对象
LocalDate nowDate = LocalDate.of(2020,7,18); //2020-07-18int year = nowDate.getYear(); //2020Month month = nowDate.getMonth(); //07int day = nowDate.getDayOfMonth(); //18DayOfWeek dayOfWeek = nowDate.getDayOfWeek(); //SATURDAYint days = nowDate.lengthOfMonth(); //31LocalDate nowdate = LocalDate.now(); //获取当前时间>2020-07-18复制代码
也可以使用 TemporalField 读取 LocalDate 的值
LocalDate nowDate = LocalDate.of(2020,7,18); //2020-07-18int year = nowDate.get(ChronoField.YEAR); //2020int month = nowDate.get(ChronoField.MONTH_OF_YEAR); //07int day = nowDate.get(ChronoField.DAY_OF_MONTH); //18复制代码
创建一个 LocalTime 对象
LocalTime nowTime = LocalTime.of(19, 34, 32); //19:34:32int hour = nowTime.getHour(); //19int minute = nowTime.getMinute(); //34int second = nowTime.getSecond(); //32复制代码
同样也可以使用 TemporalField 读取 LocalTime 的值
LocalTime nowTime = LocalTime.of(19, 34, 32); //19:34:32int hour = nowTime.get(ChronoField.HOUR_OF_DAY); //19int minute = nowTime.get(ChronoField.MINUTE_OF_HOUR); //34int second = nowTime.get(ChronoField.SECOND_OF_MINUTE); //32复制代码
LocalDateTime 相当于合并了日期和时间,以下是创建的几种方式:
LocalDate nowDate = LocalDate.of(2020,7,18); //2020-07-18LocalTime nowTime = LocalTime.of(19, 45, 20); //19:34:32LocalDateTime dt1 = LocalDateTime.of(2020, Month.JULY, 18, 19, 45, 20);LocalDateTime dt2 = LocalDateTime.of(nowDate, nowTime);LocalDateTime dt3 = nowDate.atTime(19, 45, 20);LocalDateTime dt4 = nowDate.atTime(nowTime);LocalDateTime dt5 = nowTime.atDate(nowDate);LocalDate date1 = dt1.toLocalDate(); //2020-07-18LocalTime time1 = dt1.toLocalTime(); //19:45:20复制代码
时间点的日期 时间类的通用方法:
这两个类是用来表示两个时间内的间隔的
Duration d1 = Duration.between(time1, time2);Duration d1 = Duration.between(dateTime1, dateTime2);Duration threeMinutes = Duration.ofMinutes(3);Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);Period tenDays = Period.ofDays(10);Period threeWeeks = Period.ofWeeks(3);Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);复制代码
日期 - 时间类中表示时间间隔的通用方法: