美文网首页
<<java 8 函数式编程>>学习 -

<<java 8 函数式编程>>学习 -

作者: _呆瓜_ | 来源:发表于2018-04-10 14:39 被阅读52次

    Java 8 对核心类库的改进主要包括集合类的 API 和新引入的流 (Stream)。流使程序员得以站在更高的抽象层次上对集合进行操作.

    1.从外部迭代到内部迭代

    使用 for 循环计算来自伦敦的艺术家人数:

    int count = 0;
    for (Artist artist : allArtists) {
        if (artist.isFrom("London")) {
            count++;
        }
    }
    

    改进一(外部迭代), 使用迭代器计算来自伦敦的艺术家人数:

    int count = 0;
    Iterator<Artist> iterator = allArtists.iterator(); 
    while(iterator.hasNext()) {
        Artist artist = iterator.next(); 
        if (artist.isFrom("London")) {
            count++; 
        }
    }
    

    改进二(内部迭代), 利用stream计算来自伦敦的艺术家人数:

    long count = allArtists.stream()
        .filter(artist -> artist.isFrom("London"))
        .count();
    

    Stream 是用函数式编程方式在集合类上进行复杂操作的工具.
    上述代码可被分解为两步更简单的操作:

    • 找出所有来自伦敦的艺术家;
    • 计算他们的人数.

    每种操作都对应 Stream 接口的一个方法. 为了找出来自伦敦的艺术家, 需要对 Stream 对象进行过滤:filter. 过滤在这里是指"只保留通过某项测试的对象". 测试由一个函数完成, 根据艺术家是否来自伦敦, 该函数返回true或者false. 由于Stream API的函数式编程风格, 我们并没有改变集合的内容, 而是描述出 Stream 里的内容. count() 方法计算给定 Stream 里包含多少个对象.

    2.实现机制

    上述整个过程被分解为两种更简单的操作: 过滤和计数, 看似有化简为繁之嫌--两次操作(filter和count)是否需要两次循环? 事实上类库设计精妙, 只需对艺术家列表迭代一次.

    通常, 在 Java 中调用一个方法,计算机会随即执行操作:比如, System.out.println ("Hello World"); 会在终端上输出一条信息. 这种方式叫做及早求值. 而Stream对象则则使用了另一个概念叫做惰性求值, 直到真正需要时才进行计算.

    //只过滤不计数
    allArtists.stream()
        .filter(artist -> artist.isFrom("London"));
    

    这行代码并未做什么实际性的工作, filter 只刻画出了 Stream, 但没有产生新的集合.
    判断一个操作是惰性求值还是及早求值很简单, 只需看它的返回值: 如果返回值是 Stream, 那么是惰性求值; 如果返回值是另一个值或为空, 那么就是及早求值.

    3.常用的流操作

    3.1 collect(toList())

    collect(toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作.

    List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
    assertEquals(Arrays.asList("a", "b", "c"), collected);
    

    3.2 map

    如果有一个函数可以将一种类型的值转换成另外一种类型, map 操作就可以使用该函数, 将一个流中的值转换成一个新的流.

    //使用 map 操作将字符串转换为大写形式
    List<String> collected = Stream.of("a", "b", "hello")
                                        .map(string -> string.toUpperCase())
                                        .collect(Collectors.toList());
    assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
    

    3.3 filter

    遍历数据并检查其中的元素时,可尝试使用 Stream 中提供的新方法 filter.

    List<String> beginningWithNumbers
                    = Stream.of("a", "1abc", "abc1")
                    .filter(value -> Character.isDigit(value.charAt(0)))
                    .collect(Collectors.toList());
    assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
    

    filter 接受一个函数作为参数, 该函数用 Lambda 表达式表示, 其返回值必须是 true 或者 false, 该 Lambda 表达式的函数接口正是之前介绍过的 Predicate.

    3.4 flatMap

    flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream.

    //假设有一个包含多个列表的流, 现在希望得到所有数字的序列.
    List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
                    .flatMap(numbers -> numbers.stream())
                    .collect(Collectors.toList());
    assertEquals(Arrays.asList(1, 2, 3, 4), together);
    

    3.5 max和min

    Stream上常用的操作之一是求最大值和最小值. Stream API中的max和min操作足以解决这一问题.

    //查找长度最小的元素
    List<String> tracks = Arrays.asList("Bakai", "Violets for Your Furs","Time Was");
    String shortestTrack = tracks.stream()
            .min(Comparator.comparing(track -> track.length()))
            .get();
    assertEquals(tracks.get(1), shortestTrack);
    

    3.6 reduce

    reduce 操作可以实现从一组值中生成一个值. 在上述例子中用到的 count、min 和 max 方 法, 因为常用而被纳入标准库中. 事实上, 这些方法都是 reduce 操作.
    下例展示这一过程, Lambda 表达式就是 reducer, 它执行求和操作, 有两个参数: 传入 Stream 中的当前元素和 acc. 将两个参数相加, acc 是累加器, 保存着当前的累加结果.

    //使用 reduce 求和
    int count = Stream.of(1, 2, 3)
                    .reduce(0, (acc, element) -> acc + element);
    assertEquals(6, count);
    

    4.重构遗留代码

    为了进一步阐释如何重构遗留代码, 本节将举例说明如何将一段使用循环进行集合操作的代码, 重构成基于 Stream 的操作.
    下面是跟专辑相关的一组基础类:

    package com.chyun.java8.lambda.base;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Stream;
    
    import static java.util.stream.Collectors.toList;
    
    /**
     * Domain class for a popular music artist.
     *
     * @author Richard Warburton
     */
    public final class Artist {
    
        private String name;
        private List<Artist> members;
        private String nationality;
    
        public Artist(String name, String nationality) {
            this(name, Collections.emptyList(), nationality);
        }
    
        public Artist(String name, List<Artist> members, String nationality) {
            Objects.requireNonNull(name);
            Objects.requireNonNull(members);
            Objects.requireNonNull(nationality);
            this.name = name;
            this.members = new ArrayList<>(members);
            this.nationality = nationality;
        }
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @return the members
         */
        public Stream<Artist> getMembers() {
            return members.stream();
        }
    
        /**
         * @return the nationality
         */
        public String getNationality() {
            return nationality;
        }
    
        public boolean isSolo() {
            return members.isEmpty();
        }
    
        public boolean isFrom(String nationality) {
            return this.nationality.equals(nationality);
        }
    
        @Override
        public String toString() {
            return getName();
        }
    
        public Artist copy() {
            List<Artist> members = getMembers().map(Artist::copy).collect(toList());
            return new Artist(name, members, nationality);
        }
    
    }
    package com.chyun.java8.lambda.base;
    
    import java.util.stream.Stream;
    
    import static java.util.stream.Stream.concat;
    
    public interface Performance {
    
        public String getName();
    
        public Stream<Artist> getMusicians();
    
        // TODO: test
        public default Stream<Artist> getAllMusicians() {
            return getMusicians().flatMap(artist -> {
                return concat(Stream.of(artist), artist.getMembers());
            });
        }
    
    }
    package com.chyun.java8.lambda.base;
    
    public final class Track {
    
        private final String name;
        private final int length;
    
        public Track(String name, int length) {
            this.name = name;
            this.length = length;
        }
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @return the length of the track in milliseconds.
         */
        public int getLength() {
            return length;
        }
    
        public Track copy() {
            return new Track(name, length);
        }
    
    }
    package com.chyun.java8.lambda.base;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Stream;
    
    import static java.util.Collections.unmodifiableList;
    import static java.util.stream.Collectors.toList;
    
    /**
     *
     * @author richard
     */
    public final class Album implements Performance {
    
        private String name;
        private List<Track> tracks;
        private List<Artist> musicians;
    
        public Album(String name, List<Track> tracks, List<Artist> musicians) {
            Objects.requireNonNull(name);
            Objects.requireNonNull(tracks);
            Objects.requireNonNull(musicians);
    
            this.name = name;
            this.tracks = new ArrayList<>(tracks);
            this.musicians = new ArrayList<>(musicians);
        }
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @return the tracks
         */
        public Stream<Track> getTracks() {
            return tracks.stream();
        }
    
        /**
         * Used in imperative code examples that need to iterate over a list
         */
        public List<Track> getTrackList() {
            return unmodifiableList(tracks);
        }
    
        /**
         * @return the musicians
         */
        public Stream<Artist> getMusicians() {
            return musicians.stream();
        }
    
        /**
         * Used in imperative code examples that need to iterate over a list
         */
        public List<Artist> getMusicianList() {
            return unmodifiableList(musicians);
        }
    
        public Artist getMainMusician() {
            return musicians.get(0);
        }
    
        public Album copy() {
            List<Track> tracks = getTracks().map(Track::copy).collect(toList());
            List<Artist> musicians = getMusicians().map(Artist::copy).collect(toList());
            return new Album(name, tracks, musicians);
        }
    
    }
    

    假定选定一组专辑, 找出其中所有长度大于 1 分钟的曲目名称.

    //遗留代码, 使用for循环
    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;
    }
    

    第一步要修改的是 for 循环. 首先使用 Stream 的 forEach 方法替换掉 for 循环.

    public Set<String> findLongTracks(List<Album> albums) {
        Set<String> trackNames = new HashSet<>();
        albums.stream()
            .forEach(album -> {
                album.getTracks()
                        .forEach(track -> {
                            if (track.getLength() > 60) {
                                String name = track.getName();
                                trackNames.add(name);
                            }
                        });
            });
        return trackNames; 
    }
    

    第二步, 将内部的 forEach 方法用Stream代替.

    public Set<String> findLongTracks(List<Album> albums) { 
        Set<String> trackNames = new HashSet<>(); 
        albums.stream()
            .forEach(album -> {
                album.getTracks()
                        .filter(track -> track.getLength() > 60)
                        .map(track -> track.getName())
                        .forEach(name -> trackNames.add(name));
            });
        return trackNames;
    }
    

    第三步,用flatMap替换第一个foreach.

    public Set<String> findLongTracks(List<Album> albums) {
        Set<String> trackNames = new HashSet<>();
        albums.stream()
                .flatMap(album -> album.getTracks())
                .filter(track -> track.getLength() > 60)
                .map(track -> track.getName())
                .forEach(name -> trackNames.add(name));
        return trackNames;
    }
    

    第四步, 用collect()方法替换最后的forEach.

    public Set<String> findLongTracks(List<Album> albums) {
        return albums.stream()
                .flatMap(album -> album.getTracks())
                .filter(track -> track.getLength() > 60)
                .map(track -> track.getName())
                .collect(Collectors.toSet());
    }
    

    5.练习

    a.编写一个求和函数, 计算流中所有数之和;

    public int addUp(Stream<Integer> numbers) {
        return numbers.reduce(0, (acc, x) -> acc + x);
    }
    

    b.编写一个函数, 接受艺术家列表作为参数, 返回一个字符串列表, 其中包含艺术家的姓名和国籍;

    public static List<String> getNamesAndOrigins(List<Artist> artists) {
        return artists.stream().flatMap(artist -> Stream.of(artist.getName(), artist.getNationality()))
                .collect(Collectors.toList());
    }
    

    c.修改如下代码,将外部迭代转换成内部迭代;

    int totalMembers = 0;
    for (Artist artist : artists) {
        Stream<Artist> members = artist.getMembers();
        totalMembers += members.count();
    }
    
    artists.stream().map(artist -> artist.getMembers().count()).reduce(0L, Long::sum).intValue();
    

    d.在一个字符串列表中, 找出包含最多小写字母的字符串.

    public static Optional<String> mostLowercaseString(List<String> strings) {
        return strings.stream().max(Comparator.comparing(string -> (int) string.chars().filter(Character::isLowerCase).count()));
    }
    

    e.只用 reduce 和 Lambda 表达式写出实现 Stream 上的 map 操作的代码, 如果不想返回 Stream, 可以返回一个 List.

    public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
        return stream.reduce(new ArrayList<O>(), (acc, x) -> {
            List<O> newAcc = new ArrayList<>(acc);
            newAcc.add(mapper.apply(x));
            return newAcc;
        }, (List<O> left, List<O> right) -> {
            List<O> newleft = new ArrayList<>(left);
            newleft.addAll(right);
            return newleft;
        });
    }
    

    此处关于reduce可以参考https://segmentfault.com/q/1010000004944450.
    f.只用 reduce 和 Lambda 表达式写出实现 Stream 上的 filter 操作的代码, 如果不想返回 Stream, 可以返回一个 List.

    public static <O> List<O> filter(Stream<O> stream, Predicate<O> mapper) {
        return stream.reduce(new ArrayList<O>(), (acc, x) -> {
            if (mapper.test(x)) {
                List<O> newAcc = new ArrayList<>(acc);
                newAcc.add(x);
                return newAcc;
            }
            return acc;
        }, (List<O> left, List<O> right) -> {
            List<O> newleft = new ArrayList<>(left);
            newleft.addAll(right);
            return newleft;
        });
    }
    

    相关文章

      网友评论

          本文标题:<<java 8 函数式编程>>学习 -

          本文链接:https://www.haomeiwen.com/subject/zegxhftx.html