Help us understand the problem. What is going on with this article?

java8特性

More than 1 year has passed since last update.

ラムダ式

ラムダ式はなぜ必要なのか?

従来の書き方
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("従来の書き方:この行為を実行します。");
    }
}).start();

問題点は?

  1. コードから意図が正しく伝わらない。
    Threadに渡したいのは、行為(メソッド)なのに、もの(インスタンス)を渡している。
  2. コードがシンプルでない、可読性がよくない。
    SYstem.out.println()以外は、冗長コードである。
ラムダ式
new Thread(() -> {
    System.out.println("別スレッドで行為1を実行します。");
}).start();

ラムダ式の利点

  1. コードから意図が伝わる。 Threadには行為(メソッド)を渡している。
  2. 冗長コードは排除された。

ラムダ式の変数は実質値参照である

public class Demo2 {
    public static void main(String[] args) {
        String outer = "outerString";

        // 値参照
        Runnable runnable = () -> System.out.println("外部変数 : " + outer) ;
        demo3Sub(runnable);
    }

    private static void demo3Sub(Runnable runnable) {
        runnable.run();
    }
}
外部変数 : outerString

変数outerに注目

  1. 変数outerをfinalに宣言しなくても、エラーにはならない。
  2. 変数outに値を代入するとエラーなる。実質値参照になる。

ラムダ式の書き方

// 引数なし
Runnable runnable1 = () -> System.out.println("引数なし");
runnable1.run();

// 複数ステートメント
Runnable runnable2 = () -> {
    System.out.println("引数なし");
    System.out.println("複数ステートメント");
};
runnable2.run();

// 引数一つ
Consumer<String> consumer = arg1 -> System.out.println("引数一つ : " + arg1);
consumer.accept("value1");

// 引数二つ & 型推理
BinaryOperator<String> binaryOperator1 = (arg1, arg2) -> "引数二つ連結:" + arg1 + arg2;
System.out.println(binaryOperator1.apply("param1", "param2"));

// 引数二つ & 型指定
BinaryOperator<Long> binaryOperator2 = (Long arg1, Long arg2) -> arg1 + arg2;
System.out.println(binaryOperator2.apply(10L, 20L));

jdk提供の@FunctionalInterface

IF 引数 戻り値
Predicate T boolean
Consumer T void
Function T R
Supplier None T
UnaryOperator T T
BinaryOperator (T, T) T

StreamAPI

集合に対するAPIを提供する。

  1. 操作の抽象度が高くなるらしい
  2. 並列実行に向いているらしい
  3. ラムダ式との併用でコードの可読性があがるらしい
  4. for文のようなテンプレートっぽいコードが減るらしい

中間操作と終端操作

中間操作(filter())
Stream.of("java", "spring", "c#")       
        .filter(    
                book -> {
                    System.out.println("4文字以上書籍名:" + book);
                    return book.length() >= 4;
                });

System.out.println("4文字以上書籍名:" + book);は実行されない。

終端操作(count())
Stream.of("java", "spring", "c#")
        .filter(
                book -> {
                    System.out.println("4文字超え書籍名:" + book);
                    return book.length() >= 4;
                })
        .count();
書籍名:java
書籍名:spring
書籍名:c#
  1. 中間操作では、集合走査は行わない。
    • Builderパターンと似ている部分がある。(https://qiita.com/disc99/items/840cf9936687f97a482b)
    • 集合に対する属性を設定(中間操作)した後に、どう実行(終端操作)したほうが一番効率よい方法を決めれるから。
  2. 中間操作と終端操作は、戻り値で判別できる。 (本当に??)
    • Stream filter(Predicate<? super T> predicate); -> Streamを返すのは中間操作
    • long count(); -> voidかStream以外を返すのは終端操作

よく使うAPI

  • collect : streamからList,Map,Setを生成する
    R collect(Collector<? super T, A, R> collector);
List取得
List<String> collected = Stream.of("a", "b", "c")
        .collect(Collectors.toList());
// toSet(), toMap()
  • map : TをRに変換する Stream map(Function<? super T, ? extends R> mapper)
大文字変換
List<String> uppers = Stream.of("a", "b", "hello")
        .map(string -> string.toUpperCase())
        .collect(Collectors.toList());
System.out.println(uppers);

image.png

  • filter :フィルター
    Stream filter(Predicate<? super T> predicate);
数字から始まる文字列検索
List<String> beginningWithNumbers = Stream.of("1a1", "1abc", "abc1")
        .filter(value -> isDigit(value.charAt(0)))
        .collect(Collectors.toList());

image.png

  • flatMap : 複数streamを一つにする
    Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
一つのListにする
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
        .flatMap(numbers -> numbers.stream())
        .collect(Collectors.toList());

image.png

  • max and min : 最大、最小
    Optional max(Comparator<? super T> comparator)   Optional min(Comparator<? super T> comparator)
Integer max = Stream.of(1, 2, 5, 3, 4)
        .max(Comparator.comparing(num -> num.intValue()))
        .get();

Integer min = Stream.of(1, 2, 5, 3, 4)
        .min(Comparator.comparing(num -> num.intValue()))
        .get();
  • reduce : streamから一つの値を生成 T reduce(T identity, BinaryOperator accumulator);
合計を取得する
int summary = Stream.of(1, 2, 3, 4)
        .reduce(0, (acc, element) -> acc + element);

// 合計は、summaryStatistics()が妥当だけど。

image.png

StreamAPIによるリファクター

例:複数のアルバムから曲の長さが3分以上の曲名を取得する。

アルバム
import java.util.List;

public class Album {
    private String musician; // 歌手
    private List<Track> trackList;  // 曲リスト

    public Album(String musician, List<Track> trackList) {
        this.musician = musician;
        this.trackList = trackList;
    }

    public String getMusician() {
        return musician;
    }

    public List<Track> getTrackList() {
        return trackList;
    }
}
public class Track {
    private String name;
    private long length;

    public Track(String name, long length) {
        this.name = name;
        this.length = length;
    }

    public String getName() {
        return name;
    }

    public long getLength() {
        return length;
    }
}
アルバム、曲生成メソッド
// アルバム生成
public static List<Album> makeAlbumList(int albumSize, int trackSize) {
    List<Album> albumList = new ArrayList<>();
    for (int i = 0; i < albumSize; i++) {
        Album album = new Album("musician:" + RandomStringUtils.randomAlphabetic(10), makeTrackList(trackSize));
        albumList.add(album);
    }
    return albumList;
}
// 曲生成
private static List<Track> makeTrackList(int trackSize) {
    List<Track> trackList = new ArrayList<>();
    Random random = new Random(System.currentTimeMillis());
    for (int i = 0; i < trackSize; i++) {
        Track track = new Track("track : " + RandomStringUtils.randomAlphabetic(10), random.nextInt(5) + 1);
        trackList.add(track);
    }
    return trackList;
}
リファクタ対象である従来のコード
// アルバムリスト生成
List<Album> albumList = makeAlbumList(5,5);
// 検索結果
List<String> result = new ArrayList<>();
// 1. アルバムリストを走査
for (Album album : albumList) {
    // 2. 曲リストを走査
    for (Track track : album.getTrackList()) {
        // 3. 曲の長さ判定
        if (track.getLength() >= 3) {
            // 4. 曲名保存
            result.add(track.getName());
        }
    }
}

二重for文、さらにif文 -> リファクター対象を選ぶ際の「 不吉な匂い」ってやつですね。

一回目リファクタ
List<String> result1 = new ArrayList<>();
albumList.stream()                                  // 1. StreamAPI取得
    .forEach(                                       // 2. 曲リストを走査
        album -> {
            album.getTrackList().forEach(                // 3. 曲リストを走査
                track -> {
                    if (track.getLength() >= 3) {        // 4. 曲長さ判定
                        result1.add(track.getName());    // 5. 曲名保存
                    }
                }
            );
        }
    );

おや、StreamAPIを使ったけど、なんだか処理ステップ???可読性???の状態。

判定部分はfilter, 曲から曲名取得はmapが使えるね。

二回目リファクタ
List<String> result2 = new ArrayList<>();
albumList.stream()
    .forEach(album -> {
        album.getTrackList().stream()
            .filter(track -> track.getLength() >= 3)
            .map(track -> track.getName())
            .forEach(trackName -> result2.add(trackName))
    });

インデントから見ても2階層以上はあるように見えるね。
アルバム毎の曲リストはflatMapを使って、一つのStreamにすれば、foreachがなくなるのでは?

三回目リファクタ
List<String> result3 = new ArrayList<>();
albumList.stream()
    .flatMap(album -> album.getTrackList().stream())
    .filter(track -> track.getLength() >= 3)
    .map(track -> track.getName())
    .forEach(trackName -> result3.add(trackName));

最後のforEach、かつ結果リストに詰める処理がを綺麗にする。
collectの出番。

四回目リファクタ
List<String> result4 = albumList.stream()
    .flatMap(album -> album.getTrackList().stream())  // 1. 全ての曲
    .filter(track -> track.getLength() >= 3)          // 2. 曲長さで絞る
    .map(track -> track.getName())                    // 3. 名前だけほしい
    .collect(Collectors.toList());                    // 4. 結果はListにする

リファクター前と4回目リファクターで比較すると、大分綺麗になったようだ。

StreamAPI(ラムダ式の意義)

リファクタ前のコードは、「どう変えるか?」に注力している。

  1. アルバムを走査する
  2. 曲リストを走査する
  3. 長さを判定する
  4. 曲から曲名を取得する

リファクタ後のコードは、「何に変えるか?」に注力している。

  1. flatMap : 複数の曲リストを一つのリストに変える
  2. filter : 曲長さが3分以上のリストに変える
  3. map : 曲を曲名に変える
  4. collect : StreamをListに変える

ラムダ式を使うことの意義は、
1. 意図が正しく伝わる。
2. 副作用の少ないコードがかける。

  • リファクタ前のList<String> resultの状態が変わるので、副作用があると言える
  • ラムダ式で外部状態の変更はコンパイルエラーになる、実質final
    なので、自然と副作用の少ないコードが書ける。

Streamの順序

順序が保障される
List<Integer> expectedList = Arrays.asList(2, 1, 3, 4);
// 100回Stream操作
for (int i = 0; i < 100; i++) {
    List<Integer> actualList = expectedList.stream()
        .map(ele -> ele)
        .collect(Collectors.toList());
    assertEquals(expectedList, actualList);
}
順序が保障されない
List<Integer> expectedList = Arrays.asList(2, 1, 3, 4);
Set<Integer> expectedSet = new HashSet<>(Arrays.asList(2, 1, 3, 4));
// 100回Stream操作
for (int i = 0; i < 100; i++) {
    List<Integer> actualList = expectedSet.stream()
        .map(ele -> ele)
        .collect(Collectors.toList());
    assertEquals(expectedList, actualList);
}

Stream操作元が順序を保障するものであれば(例:List)順序が保障される。
Stream操作元が順序を保障しないものであれば(例:Set)順序が保障されない。

  • sorted() : 順序をつける
  • unordered() : 順序を消す

多くの処理(filter,map,reduce等)では順序あり(sorted)が処理効率がよいが、
一部の処理では順序なし(unordered())が効率よくなる。

ライブラリ

基本型(int, double, long)

基本型よりオブジェクトがメモリ使用量が大きいのは明白。
またジェネリックで基本型は使えないので、boxing,unboxingが必要となりリソースが使われる。

StreamAPIは基本型のint, double, longへ特殊な方法を提供して、効率よく処理できるようにしている。

  • mapToInt
  • mapToDouble
  • mapToLong

サンプルでパフォーマンス比較してみる。

基本型変換(autoboxing)
// 曲は10000000のアルバム
Album album = Refactor.makeAlbumList(1, 10000000).get(0); // 自作
long start;
start = System.currentTimeMillis();
Long summary1 =
    album.getTrackList().stream()
        .map(track -> track.getLength())
        .reduce(0L, (acc, element) -> acc + element);
System.out.println("総長さ  : " + summary1);
System.out.println("auto boxing 消費時間 : " + (System.currentTimeMillis() - start));
総長さ  : 16127232
auto boxing 消費時間 : 1582
toMapXxx使用
start = System.currentTimeMillis();
Long summary2 =
    album.getTrackList().stream()
    .mapToLong(track -> track.getLength())
    .reduce(0, (acc, element) -> acc + element);
System.out.println("総長さ  : " + summary2);
System.out.println("mapToLong消費時間 : " + (System.currentTimeMillis() - start));
総長さ  : 16127232
mapToLong消費時間 : 54
  • 30分の1になったね!

interfaceのdefalutメソッド

interfaceが実装を持つこと。

public interface Parent {
    public void message(String body);

    public default void hey() {
        System.out.println("Parent: hey!");
    }
}
親実装
public class ParentImpl implements Parent {
    @Override
    public void message(String body) {
        System.out.println("Parent:message");
    }
}
テスト
Parent parent = new ParentImpl();
parent.hey();
出力
Parent: hey!

さらに、

親を継承した子
public interface Child extends Parent {
    @Override
    default void hey() {
        System.out.println("Child: hey!");
    }
}
子実装
public class ChildImpl implements Child {
    @Override
    public void message(String body) {
        System.out.println("Child:message");
    }
}
テスト
Parent parent = new ParentImpl();
parent.hey();
Child child = new ChildImpl();
child.hey();
Parent: hey!
Child: hey!
  1. IFのdefalutメソッドは実装であり、override必須ではない
  2. defalutメソッドをoverrideすることはできる。

それでは、なぜdefalutが必要なのか?

java8の特性(StreamAPI等)により、既存jdkのライブラリを修正した。
たとえば、Collection#stream()。
サードパーティでCollectionを実装した場合、java8の環境ではコンパイルできなくなる。
すでにコンパイル済みのであっても実行環境で動かない。

java8のCollection実装
// 略

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

// 略

defalutの実装により、この問題が解消される。
java1から7までのコードがjava8でも動くように
互換性を保つために追加されたと言える。

defalutメソッドと多重継承

javaのインタフェースは多重継承できて、継承元で同じdefalutメソッド実装した場合、コンパイルエラーが発生する。
「class Musical Carriage inherits unrelated defaults for rock() from types Carriage and Jukebox」

解決策 : IF名.super.defalutメソッド
複数親の内、どれを自分のdefalutにするかを決めてあげる。

superキーワード
public interface Child extends Parent, AnotherParent {
    @Override
    default void hey() {
        Parent.super.hey();
    }
}

interfaceの静的メソッド

例:StreamAPI.of()
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

オブジェクト指向上、ある振る舞いがクラス、もしくはインターフェースに所属すべきだが、
今まではユーティルクラスへ入れたり(java7のObjects)した。

java8のinterfaceの静的メソッドの追加により、あるべき姿になった感じ?

Optional

nullの代わりに用意された新しいデータ型である。 NullObjectパターンっぽいやつかな?

  • Optional生成
of,empty,ofNullable
Optional op1 = Optional.of("value1");
Optional op2 = Optional.empty();
Optional op3 = Optional.ofNullable(null);
  • Optional値存在チェック
isPresent
assertTrue(op1.isPresent());
assertFalse(op2.isPresent());
assertFalse(op3.isPresent());
  • Optional値取得
get
assertEquals("value1", op1.get());

emptyに対してgetを行う場合、java.util.NoSuchElementExceptionが発生する。
emptyの場合、isPresentより、代替値を使うのお勧め。

  • Optionalのempty代替値
orElse,orElseGet,orElseThrow
Optional op4 = Optional.empty();
assertEquals("get else value", op4.orElse("get else value"));

Optional op5 = Optional.ofNullable(null);
assertEquals("get lambda value", op5.orElseGet(() -> "get lambda value"));

Optional op6 = Optional.ofNullable(null);
try {
    op5.orElseThrow(() -> new IOException("IO例外発生させる"));
} catch (Throwable throwable) {
    assertEquals("IO例外発生させる",throwable.getMessage());
}

orElse, orElseGet, orElseThrowは三項演算子っぽいものね。

メソッド参照

java8が提供する新しい文法で、メソッド呼び出しのために使用する。
文法 : Classname::methodName

  • 引数を提供する必要がなく、コンパイラーが探してくれる
  • ()はいらない。ここで実行するのではないので。
メソッド参照にする
Long summary =
    album.getTrackList().stream()
//      .map(track -> track.getLength())
        .map(Track::getLength)
        .reduce(0L, (acc, element) -> acc + element);
newする
// new String[]{}
String[]::new

Collector(結果収集)

Streamの最終的結果を収集する役割。
- 内部ではreduceメソッドである。
- カスタムCollector定義も可能。

集合を取得する(List, Set, TreeSetなどなど)

// アルバム
Album album = Refactor.makeAlbumList(1, 5).get(0);

// toList
List<String> trackNameList = album.getTrackList()
    .stream()
    .map(Track::getName)
    .collect(Collectors.toList());
System.out.println("Listへ : " + trackNameList);

// toSet
Set<String> trackNameSet = album.getTrackList()
    .stream()
    .map(Track::getName)
    .collect(Collectors.toSet());
System.out.println("Setへ : " + trackNameSet);

// toTreeSet
TreeSet<String> trackNameTreeSet = album.getTrackList()
    .stream()
    .map(Track::getName)
    .collect(Collectors.toCollection(TreeSet::new));
System.out.println("TreeSetへ : " + trackNameTreeSet);

一つの値を取得する

// アルバム
Album album = Refactor.makeAlbumList(1, 10).get(0);

// もっとも長い曲を取得する
Optional<Track> longest =
    album.getTrackList().stream()
        // Stream#maxを使えとIntelliJが言うけど
        .collect(Collectors.maxBy(Comparator.comparing(Track::getLength)));

System.out.println("最大長さ曲名: " + longest.get().getName());
System.out.println("長さ:" + longest.get().getLength());

// 平均取得
Double average =
    album.getTrackList().stream()
        .collect(Collectors.averagingLong(Track::getLength));
System.out.println("平均長さ:" + average);
結果
最大長さ曲名: track : rSAGmQLuXr
長さ:5
平均長さ:3.2

2分割する

// アルバム
Album album = Refactor.makeAlbumList(1, 10).get(0);

// 3分を基準に分割
Map<Boolean, List<Track>> partition =
    album.getTrackList().stream()
        .collect(Collectors.partitioningBy(track -> track.getLength() >= 3));

System.out.println("3分以上:");
partition.get(Boolean.TRUE).stream()
    .forEach(track -> System.out.println("曲名: " + track.getName() + " 長さ:" + track.getLength()));

System.out.println("3分未満:");
partition.get(Boolean.FALSE).stream()
    .forEach(track -> System.out.println("曲名: " + track.getName() + " 長さ:" + track.getLength()));
結果
3分以上:
曲名: track : BxQyeGMepu 長さ:4
曲名: track : IJlOMSUSgK 長さ:4
曲名: track : WHJxMIurcv 長さ:4
曲名: track : MfybYuUJqq 長さ:4
曲名: track : lDtgbWaKBw 長さ:3
3分未満:
曲名: track : mLjTdpGqlz 長さ:2
曲名: track : UTZhgFxcNR 長さ:2
曲名: track : rVMIzMDDTo 長さ:1
曲名: track : xVEvTXDKqe 長さ:1
曲名: track : pJBrUfiCva 長さ:1

文字列を取得する

Album album = Refactor.makeAlbumList(1, 10).get(0);
String result =
    album.getTrackList().stream()
        .map(Track::getName)
        .collect(Collectors.joining(", ", "[", "]"));
System.out.println(result);
結果
[track : qCGRMYQWRz, track : xmeEorPUSP, track : qUFzXrlicZ, track : iBiEXwufrQ, track : zxDIcXnCYr]

グルーピングする

// アルバム
Album album = Refactor.makeAlbumList(1, 10).get(0);
Map<Long, List<Track>> grouping =
    album.getTrackList().stream()
        .collect(Collectors.groupingBy(Track::getLength)); // 長さでグルーピングする

// 長さ毎の曲数を取得する
Map<Long, Integer> numberOfTrack = new HashMap<>();
for (Map.Entry<Long, List<Track>> entry : grouping.entrySet()) {
    numberOfTrack.put(entry.getKey(), entry.getValue().size());
}
System.out.println(numberOfTrack);
結果
{1=1, 2=2, 4=3, 5=4}

Collector組み合わせ

上記グルーピングの場合、まず長さでグルーピング、その後for文でグルーピングしている。
これはCollector組み合わせの組み合わせで解決できる。

Collectorの組み合わせ
// アルバム
Album album = Refactor.makeAlbumList(1, 10).get(0);
Map<Long, Long> grouping =
    album.getTrackList().stream()
        .collect(
            Collectors.groupingBy(
                Track::getLength,         // 長さでgrouping
                Collectors.counting()     // groupingした結果に対して、countする
            )); // 長さでグルーピングする
System.out.println(grouping);
結果
{1=1, 2=2, 4=3, 5=4}

組み合わせ用はほかにも色々(mappingとか)あるが、割愛。

Streamの平列処理

簡単に言えば、複数コアに仕事させるってこと。
下記メソッドで簡単にパラレル実行可能になる。

  • parallel()
  • parallelStream()

parallelStream

総長さ取得
// アルバム
Album album = Refactor.makeAlbumList(1, 10000000).get(0);

long start = System.currentTimeMillis();
long summary =
    album.getTrackList().stream()
        .mapToLong(Track::getLength)
        .sum();

System.out.println("シングル処理時間: " + (System.currentTimeMillis() - start));
System.out.println("総長さ: " + summary);

start = System.currentTimeMillis();
long summaryWithParallel =
    album.getTrackList().parallelStream()
        .mapToLong(Track::getLength)
        .sum();

System.out.println("パラ処理時間: " + (System.currentTimeMillis() - start));
System.out.println("総長さ: " + summaryWithParallel);
シングル処理時間: 109
総長さ: 29336172
パラ処理時間: 62
総長さ: 29336172

平列処理に影響する要素

  1. データの大きさ
    fork/joinの処理があるため、小さいデータの場合、逆に遅くなる。
  2. 元データの種類 分割しやすいデータほど処理が早い
    • ArrayList, Array, IntStream.Rangeは分割し易いので、良い
    • HashSet,TreeSet等は分割に向いてないケースもあるので、普通
    • LinkedList,BufferedReader.lines等は分割が難しいので、悪い
  3. boxing 基本型が性能よい。
  4. コア数 多いほうがいいね、逆にシングルコアだとパラの意味がないね
  5. ユニット処理時間 ユニット処理が長いほど、平列でよい性能が期待される。

Arraysに新規追加した機能

parallelSetAll : 配列を更新する

配列更新
int[] values = new int[10];

int val = 10;
Arrays.parallelSetAll(values, i -> i);

System.out.println(
    Arrays.stream(values)
        .mapToObj(String::valueOf)
        .collect(Collectors.joining(", ", "[", "]"))
);
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

parallelSort : ソートする

ソート
Integer[] sums = {1, 2, 3, 4};

Arrays.parallelPrefix(sums, Integer::sum);

System.out.println(
    Arrays.stream(sums)
        .mapToInt(Integer::intValue)
        .mapToObj(String::valueOf)
        .collect(Collectors.joining(", ", "[", "]"))
);
[1, 2, 3, 4]

parallelPrefix:BinaryOperatorのセット

Integer[] sums = {1, 2, 3, 4};

Arrays.parallelPrefix(sums, Integer::sum);

System.out.println(
    Arrays.stream(sums)
        .mapToInt(Integer::intValue)
        .mapToObj(String::valueOf)
        .collect(Collectors.joining(", ", "[", "]"))
);
[1, 3, 6, 10]

ラムダ式のデバックと単体テスト

デバッグ

アルバムから長さ3分以上の曲名を取得する
// アルバムリスト生成
Album album = makeAlbumList(1, 5).get(0);

List<String> trackNameList1 = album.getTrackList().stream()
    .filter(track -> track.getLength() >= 3)
    .map(Track::getName)
    .collect(Collectors.<String>toList());

filter,mapは中間操作であるため、stream操作は行わない。
終端操作であるcollectをコメントアウトして、forEachで確認してみよう!

まずは、filterした結果を標準出力してみる
album.getTrackList().stream()
    .filter(track -> track.getLength() >= 3)
    .forEach(track -> System.out.println("曲名 : " + track.getName() + " 長さ : " + track.getLength()));
//      .map(Track::getName)
//      .collect(Collectors.<String>toList());
出力
曲名 : track : pQPTCFWDly 長さ : 4
曲名 : track : qXpziYuSHi 長さ : 4
曲名 : track : TFXgoRODAm 長さ : 3
mapした結果を標準出力してみる
album.getTrackList().stream()
    .filter(track -> track.getLength() >= 3)
    .map(Track::getName)
    .forEach(trackName -> System.out.println("曲名 : " + trackName));
//      .collect(Collectors.<String>toList());
出力
曲名 : track : pQPTCFWDly
曲名 : track : qXpziYuSHi
曲名 : track : TFXgoRODAm

デバックのための、実装をコメントアウトしたり、なんとかくいけてない!

peek()の出番

peek()を確認したデバックした箇所に入れる
List<String> trackNameList2 = album.getTrackList().stream()
    .filter(track -> track.getLength() >= 3)
    .peek(track -> System.out.println("曲名 : " + track.getName() + " 長さ : " + track.getLength()))
    .map(Track::getName)
    .peek(trackName -> System.out.println("曲名 : " + trackName))
    .collect(Collectors.<String>toList());
曲名 : track : npECfTvxoy 長さ : 5
曲名 : track : npECfTvxoy
曲名 : track : RDKLzmREzw 長さ : 4
曲名 : track : RDKLzmREzw
曲名 : track : DGZiRdurZQ 長さ : 3
曲名 : track : DGZiRdurZQ
peekを使って、log出力
Logger logger = Logger.getLogger(Demo6.class);
List<String> trackNameList3 = album.getTrackList().stream()
    .filter(track -> track.getLength() >= 3)
    .peek(track -> logger.debug("曲名 : " + track.getName() + " 長さ : " + track.getLength())) // logger
    .map(Track::getName)
    .peek(trackName -> logger.debug("曲名 : " + trackName))                                   // logger
    .collect(Collectors.<String>toList());

ラムダ式とmock(with mockito)

List<String> list = mock(List.class);

// 既存の書き方
when(list.size()).thenAnswer(new Answer<Object>() {
    @Override
    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
        System.out.println("既存:値を返却する前の処理");
        return 3;
    }
});

// ラムダ式
when(list.size()).thenAnswer(invocationOnMock -> {
    System.out.println("ラムダ:値を返却する前の処理");
    return 3;
});
assertEquals(3, list.size());
出力
既存:値を返却する前の処理
ラムダ:値を返却する前の処理

ラムダ式を使えるのは、thenAnswerの引数の、AnswerがFunctional interfaceであるからだ。

Answer.java
public interface Answer<T> {
    T answer(InvocationOnMock var1) throws Throwable;
}

参考書籍:Java-Lambdas-Functional-Programming-Masses

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした