Java8函數之旅 (三) --幾道關於流的練習題


為什么要有練習題?

   所謂學而不思則罔,思而不學則殆,在系列第一篇就表明我認為寫博客,既是分享,也是自己的鞏固,我深信"紙上得來終覺淺,絕知此事要躬行"的道理,因此之后的幾篇博客都會在知識點后面附上幾道練習題,不會單獨開一篇來說練習題。
   大部分題練習題來自於Richard Warburton《Java8 函數式編程》,練習題的難度不會很難,大部分都十分的基礎(可能偶爾會有一兩道進階題),並且我在后面也會附上可供參考的思路與代碼,但是我認為想要學習的人(包括我)應當首先對問題沉下心來思考,然后再查看參考答案,當然如果看博客的你沒有這么多時間我的建議是記錄下來,閑暇時光在腦袋里想一想也是好的,我認為魯迅的說的時間就像海綿,擠擠就有了是十分有道理的,其實大家都沒有自己想象的那么忙。

基礎題

Question 1 (常用流操作)

  • 編寫一個求和函數,計算流中所有數的和。例int addUp(Stream numbers)
  • 編寫一個函數,參數為藝術家集合,返回一個字符串集合,其中包含了藝術家的姓名與國籍。(藝術家類名為Artist,里面有獲得姓名與國籍的get方法getName()與getNationality())
  • 編寫一個函數,參數為專輯集合,返回一個由 最多包含3首歌曲的專輯 組成的集合。(專輯類名為Album,里面包含了一個返回本專輯所有歌曲的集合的方法getTrackList())

請耐心思考一會,看看能不能自己寫出來,下面給一些參考的思路,具體的方法確實是有很多種的。


Question 1 的參考思路

  • 編寫一個求和函數,計算流中所有數的和。
    這道練習題要求計算流中所有的數的和,參數為流,返回他們的和,這其實就是要求我們重復實現類庫中的sum()方法,那系列第二篇提到的reduce操作里面已經提到了,類庫中的sum,max,min等一系列操作都是用reduce完成的,因此在這里我們也可以用reduce進行操作。參考代碼如下
    public static int addUp(Stream<Integer> numbers){
        reutrn numbres.reduce(0,(x,y) -> x+y);
    }
  • 編寫一個函數,參數為藝術家集合,返回一個字符串集合,其中包含了藝術家的姓名與國籍。
    這道題參數為藝術家集合,返回值為字符串,那么顯然這是涉及到映射map的操作,但是要將一個藝術家元素映射成兩個字符串(分別是這個藝術家的姓名與這個藝術家的國籍),那么就要涉及到flatMap了,flatMap方法可以用Stream替換值,然后在將多個Stream鏈接成一個Stream,對於本題來說,使用藝術家姓名與藝術家國籍的流替換掉藝術家,再將這些流鏈接到一起即可。
    更具體一點的說法,假設有2個藝術家,分別是來自中國的張三與來自美國的jack,那么直接使用map映射返回的結果是2個流,格式大約為這樣:[張三,中國],[jack,美國],使用flatMap就可以將這兩個流合並,於是格式就變成了[張三,中國,jack,美國],符合題目中的返回一個字符串集合。
    參考代碼如下
    public static List<String> getArtistNamesAndNations(List<Artist> artists){
        return artists.stream()
                .flatMap(artist -> Stream.of(artist.getName(),artist.getNationality()))
                .collect(toList());
    }

注:這里最后一行要說明一下,事實上標准的寫法應該是collect(Collectors.toList()),但是因為這個方法十分常用,所以在類開頭導入了靜態方法
import static java.util.stream.Collectors.toList
這是來自於java5的特性,可以方便靜態屬性與方法的調用。

  • 編寫一個函數,參數為專輯集合,返回一個由 最多包含3首歌曲的專輯 組成的集合。(專輯類名為Album,里面包含了一個返回本專輯所有歌曲的集合的方法getTrackList())
    這道題很明顯參數為專輯,返回值也是專輯,中間多了一個條件,因此使用過濾器即可。參考代碼如下
    public static List<Album> getAlbumsWithMostThreeTracks(List<Album> albums){
        return albums.stream()
                     .filter(album -> album.getTrackList().size <= 3)
                     .collect(toList());
    }

Question 2 (關於迭代)

  • 修改下面的代碼,將外部迭代轉換成內部迭代
  • 為了保證不理解錯誤,說明一下,藝術家有的是個人,有的是團體或者樂隊,getMembers()方法是獲得該藝術家團體的所有人數,如果是個人藝術家,人數自然就是一個人。
  • 並且注意下面這段代碼的最后一行有效行,members使用了count()方法來計數,因此該方法的返回值是一個stream流,想要獲得數量要用count()方法。
    int totalMembers = 0;
    for(Artist artist: artists){
        Stream<Artist> members = artist.getMembers();
        totalMembers += members.count();
    }

附:getMembers方法

    public Stream<Artist> getMembers() {
        return members.stream();
    }

Question 2 的參考思路

不拘泥於具體實現方式,首先了解清楚這段代碼想做什么,有幾步。這段代碼想做的就是統計出所有藝術家的人數,因為有的藝術家是團體,有的藝術家是個人,個人的藝術家就算一個人,團體的話就統計出該團體的所有人數。那么相應的步驟就顯而易見了。
第一步統計出每個藝術家個人或者團體的人數,第二步將他們求和。對於流操作第一步是一個惰性求值,首先將藝術家映射成他們的團體人數,第二步是一個及早求值,對這些人數進行求和。
參考代碼如下

public static int countAllArtistMembers(List<Aritst> artists){
    return artists.stream()
                  .map(artist -> artist.getMembers().count())
                  .reduce(0,Integer::sum);
}

當然目前還沒有介紹一些屬於高階流的例如收集器等,事實上下面這段使用數值流的代碼看起來更舒服一些,閱讀性也更強

public static int countAllArtistMembers(List<Aritst> artists){
    return artists.stream()
                  .mapToInt(artist -> artist.getMembers().count())//統計人數,轉換成數值流
                  .sum();//對人數求和

Question 3 (判斷求值方式)

-依據以下兩個方法的方法簽名,判斷下面兩個方法是屬於惰性求值還是及早求值
1.boolean anyMatch(Predicate<? super T> predicate);
2.Stream limit(long maxSize);
注:第一題的Predicate是java8的函數接口,看不明白並沒有關系,后面會進行介紹,題目與之也沒有什么聯系。


Question 3 的參考思路

之前系列博客上一篇將惰性求值與及早求值的區別已經介紹過,這兩者最大的區別的就是惰性求值返回的值是Stream,而及早求值是直接返回具體的數據,那么結果就顯而易見了
第一個方法返回了具體的boolean數值,第二個方法返回的則是Steam流,因此第一個方法屬於及早求值,第二個方法屬於惰性求值。


Question 4 (字符串練習題兩道)

  • 計算一個字符串中小寫字母的個數
    注:可以參考Java8 string中新引入的chars方法

  • 在一個字符串集合中,找出包含最多小寫字母的字符串。

對於還沒有熟悉流操作的同學來說,第一反應思路是for循環,if語句之類的,其實這很正常,那么不妨試着先寫出你第一反應程序的偽代碼,接着再使用流操作對它進行重構,接着再想一想重構的過程中,提取出真正的幾步需求,也就是我要干什么,總共有幾步,分解成流操作可以怎么樣,是過濾器?還是收集器?又還是映射?還是比較器?,慢慢的這樣思考,相信以后工作中碰到相應的問題第一反應就會是流操作啦:)


Question 4 的參考思路

  • 計算一個字符串中小寫字母的個數
    首先引入一段java8 String類的chars方法的api
    /**
     * Returns a stream of {@code int} zero-extending the {@code char} values
     * from this sequence.  Any char which maps to a <a
     * href="{@docRoot}/java/lang/Character.html#unicode">surrogate code
     * point</a> is passed through uninterpreted.
     *
     * <p>If the sequence is mutated while the stream is being read, the
     * result is undefined.
     *
     * @return an IntStream of char values from this sequence
     * @since 1.8
     */
    public default IntStream chars() 

簡單點說呢,這個方法返回一個由string里字符組成的數值流,數值流,當然也是屬於流的一種了。
那么這個問題的思路是這樣的,第一步獲得所有字母的流,這點通過chars方法已經得到了,第二步選出所有的小寫字母,這點的話使用過濾器filter也可以輕松做到(也就是第一反應的if),第三部統計個數,這個就不用說了吧,count()方法搞定。
下面是參考代碼

    public class Question4 {
        public static int countStringLowercaseLetters(String string){
            return string.chars()//獲得字母流
                         .filter(Character::isLowerCase)//篩選出所有小寫字母
                         .count;//統計數量
        }
    }

這里注意到參考代碼里給了一個類,那是因為后面一道題要用到這個方法,為了減少代碼量,這里給一個類,方便到時候引用。

  • 在一個字符串集合中,找出包含最多小寫字母的字符串。
    同樣的理清思路,第一步統計出每一個字符串小寫字母的數量(這一步上一題已經做完了)並將他們映射成數值流,第二步選出這些數值中最大的。
    這里依舊給兩個參考代碼,一個是使用了Comparator比較器,返回的是optional對象,使用get()方法獲得里面的值,另一個是直接映射成數值流(個人推薦第二種啦)

  • 使用比較器

    public static Optional<String> mostLowcaseLetters(List<String> strings){
        return strings.stream()
                      .max(Comparator.comparing(Question4::countStringLowercaseLetters));
    } 
  • 使用數值流
    public static int mostLowcaseLetters(List<String> strings){
        return strings.stream()
                      .mapToInt(Question4::countStringLowercaseLetters))
                      .max();
    }

思考題

  • 試着只用reduce和Lambda表達式寫出實現Stream上的filter操作的代碼
  • 試着只用reduce和Lambda表達式寫出實現Stream上的map操作的代碼

注:使用collect收集器會更容易一些,不過目前本系列博客還沒有介紹。當然只是用reduce與lambda也是完全可以做到的,可以試着看一看java8 filter與map的源碼。

思考題答案放在下一篇博客里,當然實現的方式有很多,這里所有的答案都只是一個參考,僅提供一種思路。

本篇小結

  本篇是屬於上一篇的一個延伸,由幾道練習題講解流的一些實際操作與思考過程,在這里還是要重復說一下上面的重構建議,可能大家(包括我一開始)剛開始想當然就是for與if之類,確實很容易這樣,我的建議就是試着用流操作去重構自己的原先代碼,同時從具體的實現細節中脫離出來,去關注究竟需要做什么,這樣你會發現解決一個問題並沒有想象的那么難。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM