Consumer を一言でいうと
java.util.function.Consumer<T> は、
「T 型の値を 1 つ受け取って“何かする”けれど、結果(戻り値)は返さない関数」 を表す関数型インターフェースです。
数式っぽく書くと T -> void。
典型的には「ログを出す」「標準出力に表示する」「リストに追加する」のような“副作用のある処理”に使われます。
Consumer の中身をまず正確に押さえる
メソッドシグネチャと役割
Consumer<T> の定義の核はこの 2 つです。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t); // 抽象メソッド:ここに“やりたい処理”を書く
default Consumer<T> andThen(Consumer<? super T> after) {
// 先に this.accept、その後に after.accept を実行する合成 Consumer を返す
}
}
Java特に重要なのは accept です。
- 引数:
T t(処理対象の値) - 戻り値:
void(何も返さない)
つまり、「T を 1 つ受け取って、その場で何かして終わり」 という性格です。
JavaDoc にも「ほとんどの関数型インターフェースと違い、副作用で動作することが期待される」と書かれています。
ここが Consumer の本質 です。
一番シンプルな使用例:println を Consumer で書く
例:文字列を受け取って表示する Consumer
import java.util.function.Consumer;
public class ConsumerBasic {
public static void main(String[] args) {
Consumer<String> printer = s -> System.out.println("値: " + s);
printer.accept("Hello");
printer.accept("World");
}
}
Javaここでの対応関係を整理します。
- 型:
Consumer<String>
→ 「String -> voidの関数の型」 - ラムダ:
s -> System.out.println("値: " + s)
→acceptメソッドの中身 - 呼び出し:
printer.accept("Hello")
→"Hello"を受け取って、println するだけ(値は返さない)
「戻り値がない」という点が、Function<T,R>(何かを返す)との一番大きな違いです。
forEach と Consumer:よく出てくる組み合わせ
List.forEach の引数は Consumer
Iterable や Stream の forEach は、要素ごとに何か処理をしたいときに使います。
その「何か」が Consumer<T> です。
import java.util.List;
public class ForEachExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie");
// その場でラムダを書くパターン
names.forEach(name -> System.out.println(name));
// Consumer として変数に切り出すパターン
java.util.function.Consumer<String> printer =
s -> System.out.println("Name: " + s);
names.forEach(printer);
}
}
JavaforEach のシグネチャはざっくり言うと:
void forEach(Consumer<? super T> action)
Javaつまり、
「T を 1 つ受け取って何かする処理(Consumer)を渡してくれ」 というメソッドです。
names.forEach(name -> ...) のラムダは、その場で Consumer<String> として解釈されています。
andThen で Consumer をつなげる(ここが大事)
「この処理をした後に、もう一つ別の処理もしたい」
Consumer には、複数の処理を順番につなげるための andThen が用意されています。
default Consumer<T> andThen(Consumer<? super T> after)
Java意味は、「この Consumer の後に、after の処理を続けて実行する Consumer を返す」です。
具体例で見ましょう。
import java.util.function.Consumer;
public class ConsumerAndThenExample {
public static void main(String[] args) {
Consumer<String> printUpper = s -> System.out.println(s.toUpperCase());
Consumer<String> printLength = s -> System.out.println("len = " + s.length());
Consumer<String> combined =
printUpper.andThen(printLength);
combined.accept("hello");
}
}
Java出力イメージ:
HELLO
len = 5
ここで起きていることは、
combined.accept("hello")を呼ぶ- まず
printUpper.accept("hello")が実行される - 続けて
printLength.accept("hello")が実行される
という「処理の合成」です。
ラムダ式で 1 つにまとめてしまうこともできますが、
- 小さな処理単位(Consumer)をいくつか用意しておき
- それらを
andThenで組み合わせてパイプラインを作る
というスタイルが取れるようになると、コードの見通しがかなり良くなります。
Consumer の「副作用」という本質
なぜ戻り値がないのか
JavaDoc にはこう書かれています。
単一の入力引数を受け取って結果を返さないオペレーションを表します。
Consumer は他のほとんどの関数型インタフェースと異なり、副作用を介して動作することを期待されます。
「副作用」とは、そのメソッドの外側に影響を与えることです。例えば:
- 画面に表示する(
System.out.println) - ファイルに書き込む
- リストやマップに追加する
- 外側の変数を更新する
などです。
つまり Consumer は、
「結果を返すよりも、“外側に何か影響を与える”ことが目的の関数」
として設計されています。
逆に、データを変換して次に渡したいときは、
戻り値を持つ Function<T,R> を使う方が自然です。
他の関数型インターフェースとの違いを整理する
Function / Supplier / Predicate との比較
役割の違いをざっくり言葉で整理すると、次のような感じです。
Consumer<T>
T を受け取って何かする(ログ・出力・追加など)。戻り値なし。T -> void
Function<T,R>
T を受け取って R を返す(変換・マッピング)。T -> R
Supplier<T>
何も受け取らず T を返す(生成・取得)。() -> T
Predicate<T>
T を受け取って true/false を返す(条件判定)。T -> boolean
Consumer は「副作用で完結する処理」、Function は「新しい値を作る処理」として頭の中で分けておくと、
「どっちを使うべきか?」で迷いにくくなります。
原始型向け Consumer(IntConsumer など)
オートボクシングを避けたいときのバリエーション
Consumer<T> は参照型用なので、Consumer<Integer> を使うとint ↔ Integer のオートボクシングが発生します。
数値を大量にさばくような場面では、
プリミティブ専用のバリエーションを使うことで無駄なオーバーヘッドを減らせます。
代表的なもの:
IntConsumer:int -> voidLongConsumer:long -> voidDoubleConsumer:double -> void
例:
import java.util.function.IntConsumer;
public class IntConsumerExample {
public static void main(String[] args) {
IntConsumer printer = x -> System.out.println("x = " + x);
printer.accept(10); // x = 10
}
}
JavaIntStream.forEach などと組み合わせてよく使われます。
自分のメソッドの引数として Consumer を使う発想
「やり方だけ外から渡してもらう」メソッド
例えば、「リストの全要素に対して何か処理をする」ユーティリティメソッドを考えます。
import java.util.List;
import java.util.function.Consumer;
public class ForEachUtil {
public static <T> void forEach(List<T> list, Consumer<? super T> action) {
for (T t : list) {
action.accept(t);
}
}
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob");
forEach(names, s -> System.out.println("Hi, " + s));
}
}
Javaこのメソッドは、
- 「ループの枠組み」だけを自分で持ち
- 「各要素に対して何をするか」は Consumer として外から渡してもらう
という設計になっています。
このパターンを使えるようになると、
「アルゴリズム」と「中身の処理」を分離できて、コードの再利用性が一気に上がります。
まとめ:Consumer を自分の中でこう位置づける
Consumer<T> をあなたの言葉にすると、
「T を 1 つ受け取って、副作用だけ起こして終わる処理を表す ‘T -> void’ の関数型インターフェース」
です。
特に意識しておきたいのは、
- 抽象メソッドは
void accept(T t)(戻り値なし) forEachなど「要素ごとに何かしたい」場面で頻出andThenで複数の Consumer をつなげて、処理のパイプラインを作れる- 「値を返す変換」なら
Function、「条件判定」ならPredicateと使い分ける
あたりです。
