Consumer と Supplier
表示のしかたが違うだけで、また同じループを書いている
一覧を 1 件ずつ処理する場面で、ある画面では標準出力に出し、別の場所ではログに書き、また別の場所では別のリストに足す。やっていることが違うのは 1 行だけで、回して 1 件ずつ渡す部分は毎回同じです。
ここで渡したいのは「受け取って、何かして、終わり」という処理です。返す値はありません。この形を表す型が java.util.function.Consumer<T> です。
Java
import java.util.List;
import java.util.function.Consumer;
public class Demo {
public static void main(String[] args) {
Consumer<String> show = s -> System.out.println("- " + s);
show.accept("java"); // - java
List.of("list", "map", "set").forEach(show);
}
}動かす命令は accept の 1 つで、戻り値は void です。List.forEach の引数がちょうどこの型なので、作った Consumer をそのまま渡せます。ラムダの中で return に値を書くとコンパイルエラーになります。値を返したくなったら、それは Function の役目です。
使うかどうか分からない値を、先に作ってしまう
逆に「引数は取らないが、値を 1 つ返す」型が Supplier<T> です。命令の名前は get です。
Supplier がありがたいのは、値そのものではなく 値の作り方 を渡せるところです。既定値を用意する処理を値として渡すと、実際には使われない場合でも毎回作られてしまいます。作り方だけ渡しておけば、必要になった瞬間に 1 回だけ作れます。
Java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Demo2 {
public static void main(String[] args) {
Supplier<List<String>> newBox = () -> new ArrayList<>();
List<String> a = newBox.get();
List<String> b = newBox.get();
a.add("java");
System.out.println(a); // [java]
System.out.println(b); // []
}
}get を呼ぶたびに新しい ArrayList ができるので、a に足しても b には影響しません。Supplier は値ではなく工場だ、と考えると腑に落ちます。
変数を作っただけでは、何も起きない
一番多い詰まり方は、呼び出しを書き忘れることです。
Java
List<String> ng = newBox; // コンパイルエラー
List<String> ok = newBox.get(); // これで値になるnewBox は「作り方」であって「作られた値」ではないので、型が合いません。Consumer も同じで、show を用意しただけでは何も表示されず、show.accept("java") と呼んで初めて走ります。
名前で覚えるなら、
Consumeは受け取って終わり、Supplyは持ってくる、です。矢印の向きだけ思い出せば取り違えません。
課題では Supplier<String> を変数として作り、get の戻り値を返します。文字列をそのまま return しても結果は同じですが、それでは Supplier を通したことにならないので注意してください。
要件
- ファイル先頭で
import java.util.function.Supplier;を書くこと - メソッド内で
Supplier<String>をラムダ式() -> "hi"で生成すること - 生成した
Supplierのget()を呼び出して、その戻り値をreturnすること (固定文字列を直接返すのは NG)
入出力例
forwardSupplier() → "hi"