zoukankan      html  css  js  c++  java
  • 2、流

    流Stream

    1、外部迭代到内部迭代。
    求总数的代码:
    int count = 0;
    for(Artist artist : allArtists){
    if(artist.isForm("Lodon")){
    count++;
    }
    }
    这样的操作存在几个问题:
    1、每次迭代集合元素都需要写很多样板代码
    2、将for循环改成并行很麻烦
    3、for循环过大不一定能很好传达程序员意图
    forEach是一个语法糖,实际操作如下:
    int count = 0;
    Iterator<Artist> iterator = allArtist.iterator();
    while(iterator.hasNext()){
    Artist artist = iterator.next();
    if(artist.isFrom("Lodon")){
    count++;
    }
    }
    内部迭代操作:
    long count = allArtist.stream()
    .filter(artist -> artist.isFrom("London"))
    .count();
    2、实现机制
    在上面的例子,过程分解为两个简单的操作:过滤和计数。
    两次操作是否需要两次循环?实际上,只需要迭代一次。
    java中通常调用一个方法,计算机会立即执行操作。Stream里面的一些方法却有些不同,虽然是普通的java方法,但返回的Stream对象却不是一个新集合,而是创建新集合的配方。
    allArtist.stream()
    .filter(artist -> artist.isForm("London"))
    这代码其实并没有做什么实质工作,只是刻画出了stream,但没有产生新的集合。类似filter这样只描述filter,最终不产生新集合的方法叫做惰性求值方法。类似count这样最终从Stream产生值的方法叫做及早求值方法。
    3、常用的流操作
    collect(toList()):由Stream里面的值生成一个列表。
    of方法适用一组初始值生成新的strean
    public void tranList(){
    Stream<String> stream = Stream.of("a","b","ab","c");
    List<String> list = stream.collect(Collectors.toList());
    }

    map
    如果有一个函数可以将一种类型的值转换成另一种类型,map操作就可以使用该函数,将一个流中的值转换成另一个流。
    public void testMap(){
    /** 原操作 **/
    List<String> collected = new ArrayList<>();
    for(String string : Arrays.asList("a","b","hello")){
    String upperCaseString = string.toUpperCase();
    collected.add(upperCaseString);
    }

    /** 现操作 **/
    List<String> collected1 = Stream.of("a", "b", "ab")
    .map(string -> string.toUpperCase())
    .collect(Collectors.toList());
    }
    传给例子中的传给map的Lambda表达式只接受一个String类型的参数,返回一个新的String。参数和返回值不必属于同一种类型,但是Lambda表达式必须是Function接口的一个实例。接受T,返回R

    filter
    遍历并检查其中的元素时,可以尝试使用stream中提供的新方法filter
    public void testFilter(){
    List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1")
    .filter(value -> Character.isDigit(value.charAt(0)))
    .collect(Collectors.toList());
    }
    和map一样,filter接受一个函数作为参数,函数用Lambda表示。函数返回boolean,返回true的函数被保留。该Lambda表达式就是Predicate

    flatMap
    flatMap方法可以为Stream替换值,然后将多个Stream连接成一个Stream
    public void testFlatMap(){
    List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
    .flatMap(numbers -> numbers.stream())
    .collect(Collectors.toList());
    }
    调用stream方法,将每个列表转换成stream对象,其余部分由flatMap方法处理。
    flatMap方法的相关函数接口和map方法的一样,都是Function接口,只是方法的返回值限定为stream类罢了。

    max和min
    Stream上常用的操作之一是求最大值和最小值。Stream API中的max和min操作足以解决这一问题。
    public void testMinAndMax(){
    List<Track> tracks = Arrays.asList(new Track("Bakai", 524),
    new Track("Violets for Your Furs", 378),
    new Track("Time Was", 451));
    Track shortTestTrack = tracks.stream()
    .min(Comparator.comparing(track -> track.getName().length()))
    .get();
    }

    通用模式
    max和min方法都属于更通用的一种编程模式。要看到这种编程模式,最简单的方法是使用for循环重写上面例子中的代码。
    public void usuallyModel(){
    List<Track> tracks = Arrays.asList(new Track("Bakai", 524),
    new Track("Violets for Your Furs", 378),
    new Track("Time Was", 451));
    Track sortTestTrack = tracks.get(0);
    for(Track track: tracks){
    if(track.getName().length() < sortTestTrack.getName().length()){
    sortTestTrack = track;
    }
    }
    }
    先使用第一个元素初始化一个变量,然后遍历列表,如果找到更适合的,则更新外面的变量,最后找到的就是最适合的变量
    Stream API中的reduce可以达到。

    reduce
    reduce操作可以实现从一组值中生成一个值。上面例子中的count、min、max,因为过于常用被纳入标准库。其实,这些方法都是reduce操作。
    public void testReduce(){
    int count = Stream.of(1, 2, 3)
    .reduce(0, (acc, element) -> acc + element);
    }
    Lambda表达式的返回值是最新的acc,是上一轮acc的值和当前元素相加的结果。reduce类型是BinaryOperator类型。
    将reduce代码展开,就是下面的代码:
    BinaryOperator<Integer> accumulator = (acc, element) -> acc + element;
    int count1 = accumulator.apply(
    accumulator.apply(accumulator.apply(0, 1),
    2),
    3);
    原来的命令编程写法:
    int acc = 0;
    for(Integer element : Arrays.asList(1, 2, 3)){
    acc = acc + element;
    }

    整合操作
    Stream接口的方法如此之多,有时会让人难以选择,不知道使用什么更好。
    例子:
    需求:找出某张专辑上所有乐队的国籍。艺术家列表里既有个人,也有乐队。利用一点领域知识,假定一般乐队名以定冠词The开头。
    步骤:
    1、找出专辑上的所有表演者
    2、分辨出那些表演者是乐队
    3、找出每个乐队的国籍
    4、将找出的国籍放入一个集合
    performers.stream()
    .filter(performer -> performer.getName().startWith("The"))
    .map(performer - performer.getCountry())
    .collect(Collection.toSet());
    一个思考,操作的时候,你真的需要暴露整个List或Set这样的集合对象吗?可能一个Stream工厂才是更好的选择。通过Stream暴露集合的最大优点在于,它很好的封装了内部的数据结构。仅暴露一个Stream接口,用户在实际操作中无论如何使用,都不会影响内部的List或Set。

    4、重构遗留代码
    如何将一段使用循环进行集合操作的代码,重构成基于Stream的操作。
    需求:
    选定一组专辑,找出其中所有长度大于1分钟的曲目名称。
    遗留代码:
    public Set<String> findLongTracks(List<Album> albums){
    Set<String> trackNames = new HashSet<>();
    for(Album album : albums){
    for(Track track :album.getTrackList()){
    if(track.getLength() > 60){
    String name = track.getName();
    trackNames.add(name);
    }
    }
    }
    return trackNames;
    }
    代码分析:
    1、声明外部装载容器
    2、遍历专辑
    3、遍历专辑中的曲目
    4、找出专辑中曲目长度大于60的
    5、将长度大于60的装进外部集合
    Stream API实现:
    1、专辑得到流Stream
    2、流操作结束得到一个集合。reduce操作。
    3、reduce操作分解:
    输入空集合A,流B 得到 新集合A
    子reduce操作:
    filter () 筛选流
    map 映射替换得到名字
    合并集合
    代码实现:
    /**
    Set<String> names = new HashSet<String>();
    albums.stream()
    .reduce(names,album -> {
    album.getTrackList().stream()
    .filter(track -> track.getLength() > 60)
    .map(trace -> trace.getName())
    .reduce(names,name -> names.add(name))
    })
    .collect(Collection.toSet());
    前面的代码是错误的,因为我忽略了一个问题,匿名函数里面的外部成员必须是final
    **/
    albums.stream()
    .flatMap(album -> album.getTracks())
    .stream()
    .filter(track -> track.getLength() > 60)
    .map(trace -> trace.getName())
    .collect(Collection.toSet());
    5、多次调用流操作。
    用户也可以选择每一步强制对函数求值,而不是将所有的方法调用链接在一起,但是最好不要如此操作。
    List<Artist> musiciams = album.getMusicians().collect(toList());
    List<Artist> bands = musicans.stream()
    .filter(artist -> artist.getName().startsWith("The"))
    .collect(toList());
    Set<String> origins = binds.stream()
    .map(artist -> artist.getNationality())
    .collect(toSet());
    符合Stream使用习惯的链式调用:
    Set<String> origins = alubm.getMusicans()
    .filter(artist -> artist.getName().startsWith("The"))
    .map(artist -> artist.getNationality())
    .collect(toSet());
    多次调用相比链式调用的缺点:
    1、代码可读性茶,隐藏真正业务逻辑
    2、效率差,每一步都要对流及早求值,生成新的集合
    3、代码充斥一堆垃圾变量
    3、难于自动并行化处理

    6、高阶函数
    本章中不断出现被函数式编程程序员称为高阶函数的操作。高阶函数式指能接受另一个函数作为参数,或返回一个函数的函数。
    map是一个高阶函数。实际上,本章介绍的Stream接口中几乎所有的函数都是高阶函数。之前的排序例子中用到了comparing函数,它接受一个函数作为参数,获取对应值,同时返回一个Comparator。Comparator可能被误认为是一个对象,但它有且只有一个抽象方法,所以实际上时一个函数接口。

    7、正确使用Lambda表达式
    开始介绍Lambda表达式时,能输出一些信息的回调函数为示例。回调函数式一个合法的Lambda表达式,但并不能真正帮助用户写出更简单、更抽象的代码,因为它仍然在只会计算机执行一个操作。
    本章的介绍能让用户写出更简单的代码,因为这些概念描述了数据上的操作,明确了要达成什么转化,而不是说明如何转化,这样子写出的代码,潜在的缺陷更少。
    没有副作用的函数不会改变程序或外界的状态。书中的第一个Lambda表达式是由副作用的,它向控制台输出了信息————一个可观测到的副作用。无论何时,将Lambda表达式传给Stream上的高阶函数,都应该尽量避免副作用。唯一的例外是forEach方法,它是一个终结方法。

  • 相关阅读:
    Linux命令基础
    ubuntu16.04修改ssh的端口
    ubuntu16.04没有办法使用CRT,或者SSH工具的解决办法
    如何启动、关闭和设置ubuntu防火墙
    ubuntu远程桌面软件vnc。
    CAD安装激活失败的原因
    如何调出电脑的任务管理器
    如何查看Windows10连接的WiFi密码
    如何知道和你在一个局域网的电脑个数?
    如何快速连上另一台电脑已共享的打印机
  • 原文地址:https://www.cnblogs.com/aigeileshei/p/7169167.html
Copyright © 2011-2022 走看看