添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
谈吐大方的毛衣  ·  markdown-image | ...·  1 月前    · 
豪气的电影票  ·  TendiffPure: a ...·  7 月前    · 
卖萌的紫菜汤  ·  中华文史网·  10 月前    · 

前言

算是之前写代码的时候学到的两个比较有意思的特性,也是Java8新增的两个功能,其实说到底就是减少了一些nullptr的判断和for循环的遍历,让代码变得更加优雅,可读性更高。

  • Optional
  • Stream
  • 主要就是如上两个特性,总结的过程中也学了一些以前不知道的特性。

    Optional

    先从Optional来吧,他其实是对原有的数据类型进行了一层包装,将nullptr判断的部分操作进行了一定的封装。

    可以先来看一些常规的操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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并没有什么太多的区别。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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> 对象,就可以用如下链式操作

    1
    2
    3
    4
    String version = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .flatMap(USB::getVersion)
    .orElse("UNKNOWN");

    否则需要进行多次的!=null的判断比较,才能进行下一步的操作。

    对于一个empty的Optional,flatMap的调用之后的结果也是一个空的Optional,从而避免了nullptr的异常。

    假如调用函数处理的不是Optional对象,则应该调用Optional.map来进行处理

    1
    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

    流式操作, stream 这是一个在 Collection 接口的方法,可以将集合以一种很优雅且性能相当高的方式进行操作。

    1
    2
    3
    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上的操作是终结操作还是中间操作

    1
    2
    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() 返回第一个元素(类型为Optional),如果不存在,则返回一个空的Optional
  • findAny() 返回任何一个元素(类型为Optional),和findFirst的区别在于它不要求顺序
  • anyMatch(lamda) 是否存在一个匹配
  • allMatch(lamda) 是否全部匹配
  • noneMatch(lamda) 是否有不匹配的
  • reduce(lamda) 学过map/reduce应该就知道是干啥的
  • Collector

    有时候对流进行操作之后只是想获得过滤之后的集合,就可以用

  • toArray 默认会返回一个Object[], 想指定类型的话可以使用stream.toArray(String[]::new)
  • Collectors 提供了一个更为便捷的方式

    1
    2
    3
    4
    stream.collect(Collectors.toList());
    stream.collect(Collectors.toSet());
    stream.collect(Collectors.toMap(Person::getName, Person::getAge))
    stream.collect(Collectors.toCollection(TreeSet::new));

    通过如上方式都可以很简单将筛选后的流转换成Set、List、或者指定数据类型等形式。

    甚至可以讲所有的结果以字符串的形式连接起来

    1
    2
    stream.collect(Collectors.joining());
    stream.collect(Collectors.joining(", "));

    或者收集一个可以同时获取最大值、最小值、平均值的数据类型

    1
    2
    3
    4
    IntSummarzingInt summary = stream.collect(Collectors.summarizingInt(String::length)); // 这里的Int也可以是Double|Long
    summary.getAverage();
    summary.getMax();
    summary.getMin();

    对toMap有更多的定制化的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 当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的功能

    1
    2
    3
    Map<Integer, List<Person>> map = people.collect(
    Collectors.groupingBy(Person::getAge)
    );

    如果group by之后的结果希望是一个Set

    1
    2
    3
    people.collect(
    Collectors.groupingBy(Person::getAge, Collectors.toSet())
    );

    第二个当然不止可以传入 Collectors.toSet 也可以是一些

  • Collectorss.counting()
  • Collectors.maxBy(...)
  • Collectors.minBy(...)
  • Collectors.mapping(...)
  • 之类其他下游收集器操作

    常用的操作就到这吧,其他的一些需要时再查阅可能会更方便一些。

    最后

    怎么越来越像一名菜鸡Java开发的了。。。