ラムダ式
ラムダ式はなぜ必要なのか?
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("従来の書き方:この行為を実行します。");
}
}).start();
問題点は?
- コードから意図が正しく伝わらない。
Threadに渡したいのは、行為(メソッド)なのに、もの(インスタンス)を渡している。 - コードがシンプルでない、可読性がよくない。
SYstem.out.println()以外は、冗長コードである。
new Thread(() -> {
System.out.println("別スレッドで行為1を実行します。");
}).start();
ラムダ式の利点
- コードから意図が伝わる。
Threadには行為(メソッド)を渡している。 - 冗長コードは排除された。
ラムダ式の変数は実質値参照である
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に注目
- 変数
outer
をfinalに宣言しなくても、エラーにはならない。 - 変数
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を提供する。
- 操作の抽象度が高くなるらしい
- 並列実行に向いているらしい
- ラムダ式との併用でコードの可読性があがるらしい
- for文のようなテンプレートっぽいコードが減るらしい
中間操作と終端操作
Stream.of("java", "spring", "c#")
.filter(
book -> {
System.out.println("4文字以上書籍名:" + book);
return book.length() >= 4;
});
System.out.println("4文字以上書籍名:" + book);
は実行されない。
Stream.of("java", "spring", "c#")
.filter(
book -> {
System.out.println("4文字超え書籍名:" + book);
return book.length() >= 4;
})
.count();
書籍名:java
書籍名:spring
書籍名:c#
- 中間操作では、集合走査は行わない。
- Builderパターンと似ている部分がある。(https://qiita.com/disc99/items/840cf9936687f97a482b)
- 集合に対する属性を設定(中間操作)した後に、どう実行(終端操作)したほうが一番効率よい方法を決めれるから。
-
中間操作と終端操作は、戻り値で判別できる。 (本当に??)
- 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<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);
-
filter :フィルター
Stream filter(Predicate super T> predicate);
List<String> beginningWithNumbers = Stream.of("1a1", "1abc", "abc1")
.filter(value -> isDigit(value.charAt(0)))
.collect(Collectors.toList());
-
flatMap : 複数streamを一つにする
Stream flatMap(Function super T, ? extends Stream extends R>> mapper);
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(Collectors.toList());
-
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()が妥当だけど。
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(ラムダ式の意義)
リファクタ前のコードは、__「どう変えるか?」__に注力している。
- アルバムを走査する
- 曲リストを走査する
- 長さを判定する
- 曲から曲名を取得する
リファクタ後のコードは、__「何に変えるか?」__に注力している。
- flatMap : 複数の曲リストを一つのリストに変える
- filter : 曲長さが3分以上のリストに変える
- map : 曲を曲名に変える
- collect : StreamをListに変える
ラムダ式を使うことの意義は、
- 意図が正しく伝わる。
- 副作用の少ないコードがかける。
- リファクタ前の
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
サンプルでパフォーマンス比較してみる。
// 曲は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
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!
- IFのdefalutメソッドは実装であり、override必須ではない
- defalutメソッドをoverrideすることはできる。
それでは、なぜdefalutが必要なのか?
java8の特性(StreamAPI等)により、既存jdkのライブラリを修正した。
たとえば、Collection#stream()。
サードパーティでCollectionを実装した場合、java8の環境ではコンパイルできなくなる。
すでにコンパイル済みのであっても実行環境で動かない。
// 略
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にするかを決めてあげる。
public interface Child extends Parent, AnotherParent {
@Override
default void hey() {
Parent.super.hey();
}
}
interfaceの静的メソッド
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
オブジェクト指向上、ある振る舞いがクラス、もしくはインターフェースに所属すべきだが、
今まではユーティルクラスへ入れたり(java7のObjects)した。
java8のinterfaceの静的メソッドの追加により、あるべき姿になった感じ?
Optional
nullの代わりに用意された新しいデータ型である。 NullObjectパターンっぽいやつかな?
- Optional生成
Optional op1 = Optional.of("value1");
Optional op2 = Optional.empty();
Optional op3 = Optional.ofNullable(null);
- Optional値存在チェック
assertTrue(op1.isPresent());
assertFalse(op2.isPresent());
assertFalse(op3.isPresent());
- Optional値取得
assertEquals("value1", op1.get());
emptyに対してgetを行う場合、__java.util.NoSuchElementException__が発生する。
emptyの場合、isPresentより、代替値を使うのお勧め。
- Optionalのempty代替値
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 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組み合わせの組み合わせで解決できる。
// アルバム
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
平列処理に影響する要素
- データの大きさ
fork/joinの処理があるため、小さいデータの場合、逆に遅くなる。 - 元データの種類
分割しやすいデータほど処理が早い- ArrayList, Array, IntStream.Rangeは分割し易いので、良い
- HashSet,TreeSet等は分割に向いてないケースもあるので、普通
- LinkedList,BufferedReader.lines等は分割が難しいので、悪い
- boxing
基本型が性能よい。 - コア数
多いほうがいいね、逆にシングルコアだとパラの意味がないね - ユニット処理時間
ユニット処理が長いほど、平列でよい性能が期待される。
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]
ラムダ式のデバックと単体テスト
デバッグ
// アルバムリスト生成
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で確認してみよう!
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
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()の出番
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
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であるからだ。
public interface Answer<T> {
T answer(InvocationOnMock var1) throws Throwable;
}
参考書籍:Java-Lambdas-Functional-Programming-Masses