算是之前写代码的时候学到的两个比较有意思的特性,也是Java8新增的两个功能,其实说到底就是减少了一些nullptr的判断和for循环的遍历,让代码变得更加优雅,可读性更高。
主要就是如上两个特性,总结的过程中也学了一些以前不知道的特性。
先从Optional来吧,他其实是对原有的数据类型进行了一层包装,将nullptr判断的部分操作进行了一定的封装。
可以先来看一些常规的操作
public static void main(String[] args) { String r1 = getStrOpt().orElse("default string"); String r2 = getStrOpt().orElseGet(() -> String.valueOf(Math.random())); String r3 = getStrOpt().orElseThrow(IllegalStateException::new); System.out.println(r1); System.out.println(r2); System.out.println(r3); } public static Optional<String> getStrOpt() { return Math.random() > 0.5 ? Optional.of("optional str") : Optional.empty(); }
可以看到可以通过如下三种方式来从Optional中获取值,并为没有值的情况选择一个默认的返回情况,可以是一个default值,也可以是通过调用某个函数返回的值,以及抛出一个异常
Optional.orElse Optional.orElseGet Optional.orElseThrow
但是其实它并不能化简如下的这种操作,遇到这种情况时其实和判断!=null并没有什么太多的区别。
public static void main(String[] args) { String s = getStr(); if(s != null){ System.out.println(s); } Optional<String> sOpt = getStrOpt(); if(sOpt.isPresent()){ System.out.println(sOpt.get()); } } public static String getStr() { return Math.random() > 0.5 ? "str" : null; } public static Optional<String> getStrOpt() { return Math.random() > 0.5 ? Optional.of("optional str") : Optional.empty(); }
可以看到要进行的操作是差不多的,并不比null判断要方便多少(或许他的isPresent让人看的更有逼格?)
那Optional的好处在于哪呢?他真正方便的地方可能在于一些链式的操作。
假设下面 flatMap
、 getUSB
、 getVersion
处理
和 返回
的都是个Optional/<T> 对象,就可以用如下链式操作
String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .flatMap(USB::getVersion) .orElse("UNKNOWN");
否则需要进行多次的!=null的判断比较,才能进行下一步的操作。
对于一个empty的Optional,flatMap的调用之后的结果也是一个空的Optional,从而避免了nullptr的异常。
假如调用函数处理的不是Optional对象,则应该调用Optional.map来进行处理
Optional<String> a = getStrOpt().map(String::toUpperCase);
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
总之呢,个人感觉Optional确实解决了一部分空指针的烦恼,但也需要你的项目代码里有较多的Optional代码的支持,否则就会出现之前 isPresent
判断并没有什么优化的尴尬情况,反而让人觉得多套了一层这个Optional的麻烦。
流式操作, stream
这是一个在 Collection
接口的方法,可以将集合以一种很优雅且性能相当高的方式进行操作。
long count = words.stream() .filter(w -> w.length() > 12) .count()
可以很容易的看出这是从words中筛选长度大于12的字符串,并计算它的个数。
可以通过两种方式创建流
Collection.stream()
− 为集合创建串行流。
Collection.parallelStream()
− 为集合创建并行流。
其实当数据量不大时,用stream完全没有问题,当数据量大到需要优化时,可以采用并行流的方式。
但是并行流由于是并行的关系,所以对于遍历顺序之类的操作是无法指定的,否则也失去了并行的优势。
或者通过Stream的一些静态方法
Stream.of("a","b","c") Stream.generate(() -> "kingkk") Stream.iterate(BigInterger.ZERO, n -> n.add(BigInter.ONE)) Stream.empty()
通过这种函数调用之类的方式,可以生成一个无限流,也就类似与Python中的yield生成器。
流式操作还有几个特点
流的操作主要分为 中间操作 和 终结操作 ,他们之间的区别主要在于
其实直接看代码也很好区分一个stream上的操作是终结操作还是中间操作
Optional<T> findAny(); Stream<T> filter(Predicate<? super T> predicate);
可以看到中间操作的返回类型都是Stream,而终结操作的返回类型是其他的结果,也就不能在进行流的链式操作了。
有很多,我就列几个常用的吧
map(lamda)
就是map,别问我是啥 flatMap(lamda)
将数据类型 铺平
之后进行map操作,类似于 [[a,b,c], [d,e] ] -> [func(a), func(b), func(c), func(d), func(e) ]
filter(lamda)
传入一个判断函数,过滤只符合条件的数据 distinct()
类似Mysql的distinct,去除重复的数据 imit(num)
截断流使其最多只包含指定数量的数据 skip(num)
返回一个新的流,并跳过原始流中的前 N 个元素。 sorted([lamda])
对流进行排序,也可以传入一个函数,按指定方式排序 peek(lamda)
产生另一个流,通常用于调试之类的操作,由于它不是终结操作,流并不会终止 concat(stream)
连接流 forEach(lamda) forEachOrdered(lamda) max() min() findFirst() findAny() anyMatch(lamda) allMatch(lamda) noneMatch(lamda) reduce(lamda)
有时候对流进行操作之后只是想获得过滤之后的集合,就可以用
toArray
默认会返回一个Object[], 想指定类型的话可以使用stream.toArray(String[]::new)
但 Collectors
提供了一个更为便捷的方式
stream.collect(Collectors.toList()); stream.collect(Collectors.toSet()); stream.collect(Collectors.toMap(Person::getName, Person::getAge)) stream.collect(Collectors.toCollection(TreeSet::new));
通过如上方式都可以很简单将筛选后的流转换成Set、List、或者指定数据类型等形式。
甚至可以讲所有的结果以字符串的形式连接起来
stream.collect(Collectors.joining()); stream.collect(Collectors.joining(", "));
或者收集一个可以同时获取最大值、最小值、平均值的数据类型
IntSummarzingInt summary = stream.collect(Collectors.summarizingInt(String::length)); // 这里的Int也可以是Double|Long summary.getAverage(); summary.getMax(); summary.getMin();
对toMap有更多的定制化的方式
// 当value的值是元素本身时 people.collect(Collectors.toMap(Person::getId, Function.identity())); // 当存在key值冲突时 locales.collect( Collectors.toMap( Locale::getDisplayLanguage, l -> l.getDisplayLanguage(l), (a, b) -> a ) ); // 并指定特定的数据结构 locales.collect( Collectors.toMap( Locale::getDisplayLanguage, l -> l.getDisplayLanguage(l), (a, b) -> a, TreeMap::new ) );
还提供了一个mysql中group by的功能
Map<Integer, List<Person>> map = people.collect( Collectors.groupingBy(Person::getAge) );
如果group by之后的结果希望是一个Set
people.collect( Collectors.groupingBy(Person::getAge, Collectors.toSet()) );
第二个当然不止可以传入 Collectors.toSet
也可以是一些
Collectorss.counting() Collectors.maxBy(...) Collectors.minBy(...) Collectors.mapping(...)
之类其他下游收集器操作
常用的操作就到这吧,其他的一些需要时再查阅可能会更方便一些。