- JAVA
Java ラムダ式の使い方|関数型インターフェース全43種の一覧と実行結果
Javaのラムダ式とは、「抽象メソッドが1つだけのインターフェース(関数型インターフェース)」の実装を、(引数) -> 処理 の短い形で書ける記法です。 Java 8で導入され、Comparator での並べ替え、List.forEach、Stream APIなど、今のJavaのあらゆる場所で使われています。Java 8世代では同じことを匿名クラスで丁寧に書いていました。ラムダ式はそれを短く書ける記法です。
ラムダ式を使いこなすカギは、「どの関数型インターフェースを選ぶか」です。この記事では、java.util.function パッケージの全43種を「引数→戻り値」で整理した一覧表を用意しました(数はJDKを実際に数えて確認)。さらに、省略ルール、メソッド参照4種、実質的final、this の意味、合成メソッド、自作インターフェース、チェック例外の扱いまで、すべてJava 21(OpenJDK 21.0.10)の単体ファイル実行(java X.java)で動かした実行結果つきで紹介します。
Java ラムダ式と関数型インターフェースの早見表
まずは「引数」と「戻り値」から選びます。 迷ったらこの表の上から見てください。
| やりたいこと(引数 → 戻り値) | 使うインターフェース | 呼び出すメソッド | ラムダの例 |
|---|---|---|---|
| なし → 値を返す | Supplier<T> |
get() |
() -> "hello" |
| 1つ受け取り → 何も返さない | Consumer<T> |
accept(t) |
s -> System.out.println(s) |
| 1つ受け取り → 別の型を返す | Function<T, R> |
apply(t) |
s -> s.length() |
| 1つ受け取り → 同じ型を返す | UnaryOperator<T> |
apply(t) |
s -> s.trim() |
| 1つ受け取り → true/false | Predicate<T> |
test(t) |
s -> s.isEmpty() |
| 2つ受け取り → 何も返さない | BiConsumer<T, U> |
accept(t, u) |
(k, v) -> System.out.println(k + v) |
| 2つ受け取り → 別の型を返す | BiFunction<T, U, R> |
apply(t, u) |
(s, n) -> s.repeat(n) |
| 2つ(同じ型)→ 同じ型を返す | BinaryOperator<T> |
apply(t, u) |
(a, b) -> a + b |
| 2つ受け取り → true/false | BiPredicate<T, U> |
test(t, u) |
(a, b) -> a.equals(b) |
| int などを扱う | IntPredicate、ToIntFunction<T> など |
test、applyAsInt など |
n -> n % 2 == 0 |
| 何も受け取らず何も返さない | Runnable(java.lang) |
run() |
() -> doWork() |
| 書き方の要点 | 内容 |
|---|---|
| 省略 | 型は省略可、本体が1文なら {} と return を省略可、引数1つなら () も省略可 |
| メソッド参照 | Integer::parseInt、s::equals、String::length、ArrayList::new の4種 |
| 外の変数 | ローカル変数は実質的final(代入し直していない)なら使える |
this |
ラムダ内の this は外側のクラスを指す(匿名クラスとの違い) |
| 合成 | andThen、compose、and、or、negate、Predicate.not |
| 自作 | 抽象メソッド1つのインターフェースに @FunctionalInterface を付ける |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java ラムダ式の使い方|関数型インターフェース全43種の一覧と実行結果」
java.util.function の関数型インターフェース全43種一覧
java.util.function には、Java 21時点で43個のインターフェースがあります。 JDK内のクラスを列挙するプログラムで数を確認しました。
import java.lang.reflect.*;
import java.net.URI;
import java.nio.file.*;
import java.util.*;
public class ListFn {
public static void main(String[] args) throws Exception {
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
Path dir = fs.getPath("/modules/java.base/java/util/function");
List<String> names;
try (var s = Files.list(dir)) {
names = s.map(p -> p.getFileName().toString())
.filter(n -> n.endsWith(".class") && !n.contains("$"))
.map(n -> n.replace(".class", "")).sorted().toList();
}
for (String n : names) {
Class<?> c = Class.forName("java.util.function." + n);
for (Method m : c.getMethods()) {
if (Modifier.isAbstract(m.getModifiers())) {
System.out.println(n + " : " + m.getName() + " "
+ Arrays.toString(m.getGenericParameterTypes()) + " -> " + m.getGenericReturnType());
}
}
}
System.out.println("合計: " + names.size());
}
}
実行結果(抜粋):
BiConsumer : accept [T, U] -> void
BiFunction : apply [T, U] -> R
...
ToLongFunction : applyAsLong [T] -> long
UnaryOperator : apply [T] -> R
合計: 43
基本の9種(参照型を扱う)
| インターフェース | 抽象メソッド | 引数 → 戻り値 | 持っている便利メソッド |
|---|---|---|---|
Supplier<T> |
get |
なし → T |
なし |
Consumer<T> |
accept |
T → なし |
andThen |
BiConsumer<T, U> |
accept |
T, U → なし |
andThen |
Function<T, R> |
apply |
T → R |
andThen、compose、static identity |
BiFunction<T, U, R> |
apply |
T, U → R |
andThen |
UnaryOperator<T> |
apply |
T → T |
static identity(Function を継承) |
BinaryOperator<T> |
apply |
T, T → T |
static minBy、maxBy(BiFunction を継承) |
Predicate<T> |
test |
T → boolean |
and、or、negate、static isEqual、not |
BiPredicate<T, U> |
test |
T, U → boolean |
and、or、negate |
プリミティブ特化型(34種)
int・long・double・boolean をそのまま扱う版です。 Function<Integer, Integer> だと値が Integer に包まれる(ボクシング)ため、数値を大量に扱うときは特化型を選ぶと無駄がありません。命名は「Int = int を受け取る」「ToInt = int を返す」「Obj = 参照型と組み合わせる」と覚えると迷いません。
| 形 | int 版 | long 版 | double 版 | 引数 → 戻り値 |
|---|---|---|---|---|
| Supplier | IntSupplier |
LongSupplier |
DoubleSupplier |
なし → プリミティブ |
| Consumer | IntConsumer |
LongConsumer |
DoubleConsumer |
プリミティブ → なし |
| Predicate | IntPredicate |
LongPredicate |
DoublePredicate |
プリミティブ → boolean |
| Function | IntFunction<R> |
LongFunction<R> |
DoubleFunction<R> |
プリミティブ → R |
| UnaryOperator | IntUnaryOperator |
LongUnaryOperator |
DoubleUnaryOperator |
同じプリミティブ → 同じ |
| BinaryOperator | IntBinaryOperator |
LongBinaryOperator |
DoubleBinaryOperator |
同じ2つ → 同じ |
| ToXxxFunction | ToIntFunction<T> |
ToLongFunction<T> |
ToDoubleFunction<T> |
T → プリミティブ |
| ToXxxBiFunction | ToIntBiFunction<T, U> |
ToLongBiFunction<T, U> |
ToDoubleBiFunction<T, U> |
T, U → プリミティブ |
| ObjXxxConsumer | ObjIntConsumer<T> |
ObjLongConsumer<T> |
ObjDoubleConsumer<T> |
T, プリミティブ → なし |
残りの7種は型変換です。
| インターフェース | 抽象メソッド | 引数 → 戻り値 |
|---|---|---|
BooleanSupplier |
getAsBoolean |
なし → boolean |
IntToLongFunction |
applyAsLong |
int → long |
IntToDoubleFunction |
applyAsDouble |
int → double |
LongToIntFunction |
applyAsInt |
long → int |
LongToDoubleFunction |
applyAsDouble |
long → double |
DoubleToIntFunction |
applyAsInt |
double → int |
DoubleToLongFunction |
applyAsLong |
double → long |
基本9種+27種+7種=43種です。プリミティブ特化型のうち、合成メソッドを持つのは Consumer 系(andThen)、Predicate 系(and/or/negate)、UnaryOperator 系(andThen/compose/identity)だけです。
プリミティブ特化型を使った例です。
import java.util.function.*;
import java.util.stream.IntStream;
public class Primitive {
public static void main(String[] args) {
IntPredicate even = n -> n % 2 == 0; // int をそのまま受け取る
IntUnaryOperator square = n -> n * n;
ToIntFunction<String> length = String::length; // 参照型 → int
IntFunction<String> stars = "*"::repeat; // int → 参照型
ObjIntConsumer<StringBuilder> append = (sb, n) -> sb.append(n).append(',');
System.out.println(IntStream.rangeClosed(1, 6).filter(even).map(square).sum());
System.out.println(length.applyAsInt("function"));
System.out.println(stars.apply(5));
StringBuilder sb = new StringBuilder();
IntStream.range(0, 3).forEach(n -> append.accept(sb, n));
System.out.println(sb);
}
}
実行結果:
56
8
*****
0,1,2,
ラムダ式の書き方と省略ルール
ラムダ式は、省略なしの形から段階的に短くできます。 次の6つはどれも正しい書き方です。
import java.util.function.*;
public class LambdaSyntax {
public static void main(String[] args) {
// 1. 省略なし:型・かっこ・波かっこ・return をすべて書く
BinaryOperator<Integer> add1 = (Integer a, Integer b) -> { return a + b; };
// 2. 引数の型を省略(型推論)
BinaryOperator<Integer> add2 = (a, b) -> { return a + b; };
// 3. 本体が1文なら { } と return を省略
BinaryOperator<Integer> add3 = (a, b) -> a + b;
// 4. 引数が1つなら ( ) も省略できる
UnaryOperator<String> upper = s -> s.toUpperCase();
// 5. 引数なしは () が必須
Supplier<String> hello = () -> "hello";
// 6. Java 11以降:var で型を書くと注釈を付けられる
BiFunction<String, Integer, String> repeat = (var s, var n) -> s.repeat(n);
System.out.println(add1.apply(2, 3) + " " + add2.apply(2, 3) + " " + add3.apply(2, 3));
System.out.println(upper.apply("rjc"));
System.out.println(hello.get());
System.out.println(repeat.apply("ab", 3));
}
}
実行結果:
5 5 5
RJC
hello
ababab
| 省略できる? | ルール |
|---|---|
| 引数の型 | 省略できる。ただし全部書くか全部省略するかのどちらか(混在不可) |
引数の () |
引数がちょうど1つで型を書かないときだけ省略できる |
{} と return |
本体が式1つなら両方省略できる(片方だけの省略は不可) |
| 本体が複数行 | {} が必須。値を返すなら return も必須 |
メソッド参照4種の書き方
メソッド参照は、「引数をそのまま既存のメソッドに渡すだけ」のラムダをさらに短くする書き方です。 :: で表します。
| 種類 | 書き方 | 同じ意味のラムダ |
|---|---|---|
| static メソッド | Integer::parseInt |
s -> Integer.parseInt(s) |
| 特定のオブジェクトのメソッド | prefix::equals |
s -> prefix.equals(s) |
| 任意のオブジェクトのメソッド | String::length |
s -> s.length() |
| コンストラクタ | ArrayList::new |
() -> new ArrayList<>() |
import java.util.*;
import java.util.function.*;
public class MethodRefs {
record Item(String name, int stock) {
static boolean isEmpty(Item i) { return i.stock() == 0; }
}
public static void main(String[] args) {
// (1) static メソッド参照 クラス名::staticメソッド
Function<String, Integer> parse = Integer::parseInt; // s -> Integer.parseInt(s)
// (2) 特定オブジェクトのメソッド参照 変数::メソッド
String prefix = "item-";
Predicate<String> isPrefix = prefix::equals; // s -> prefix.equals(s)
// (3) 任意オブジェクトのメソッド参照 クラス名::インスタンスメソッド
Function<String, Integer> len = String::length; // s -> s.length()
BiPredicate<String, String> eq = String::equalsIgnoreCase; // (a, b) -> a.equalsIgnoreCase(b)
// (4) コンストラクタ参照 クラス名::new
Supplier<List<String>> newList = ArrayList::new; // () -> new ArrayList<>()
IntFunction<String[]> newArray = String[]::new; // n -> new String[n]
BiFunction<String, Integer, Item> newItem = Item::new; // (n, s) -> new Item(n, s)
System.out.println(parse.apply("42") + 1);
System.out.println(isPrefix.test("item-"));
System.out.println(len.apply("lambda"));
System.out.println(eq.test("Java", "JAVA"));
List<String> list = newList.get(); list.add("a");
System.out.println(list + " " + newArray.apply(3).length);
Item pen = newItem.apply("pen", 0);
System.out.println(pen + " empty=" + ((Predicate<Item>) Item::isEmpty).test(pen));
List.of("b", "a", "c").stream().sorted().forEach(System.out::println);
}
}
実行結果:
43
true
6
true
[a] 3
Item[name=pen, stock=0] empty=true
a
b
c
(3) の String::length は、最初の引数が「メソッドを呼ぶ相手」になるのがポイントです。BiPredicate<String, String> に String::equalsIgnoreCase を渡すと、(a, b) -> a.equalsIgnoreCase(b) として扱われます。
外の変数を使うときのルール(実質的final)
ラムダの中からは、外側のローカル変数を読めます。ただし、その変数が「実質的final(一度も代入し直していない)」である必要があります。
import java.util.function.*;
public class Capture {
public static void main(String[] args) {
int base = 10;
// base = 20; // ← この行を有効にするとコンパイルエラー
IntUnaryOperator plus = x -> x + base;
System.out.println(plus.applyAsInt(5));
}
}
実行結果:
15
base = 20; を有効にすると、次のコンパイルエラーになります(実際の出力)。
CaptureNg.java:8: error: local variables referenced from a lambda expression must be final or effectively final
IntUnaryOperator plus = x -> x + base;
^
1 error
| 外の値 | ラムダ内で読む | ラムダ内で書き換える |
|---|---|---|
| ローカル変数(実質的final) | できる | できない |
フィールド(this.count など) |
できる | できる |
ローカル変数が指すオブジェクトの中身(list.add など) |
できる | できる(変数そのものは再代入していないため) |
合計や件数は、外の変数を書き換えずStreamの sum() や count() で値を返す形にすると素直に書けます。
ラムダ式の this と匿名クラスの違い
ラムダ式の中の this は、ラムダを書いた外側のクラスのインスタンスを指します。 匿名クラスでは this が匿名クラス自身を指すため、ここが両者のいちばん大きな違いです。
public class ThisDemo {
private final String name = "外側のThisDemo";
@Override public String toString() { return name; }
void run() {
Runnable lambda = () -> System.out.println("ラムダの this : " + this);
Runnable anon = new Runnable() {
@Override public void run() {
System.out.println("匿名クラスの this : " + this.getClass().getName());
System.out.println("外側を指すには : " + ThisDemo.this);
}
};
lambda.run();
anon.run();
}
public static void main(String[] args) {
new ThisDemo().run();
}
}
実行結果:
ラムダの this : 外側のThisDemo
匿名クラスの this : ThisDemo$1
外側を指すには : 外側のThisDemo
| 比べる点 | ラムダ式 | 匿名クラス |
|---|---|---|
this |
外側のクラス | 匿名クラス自身(外側は 外側クラス名.this) |
| 使えるインターフェース | 抽象メソッド1つのものだけ | 抽象メソッドがいくつでも、抽象クラスも可 |
| 自分の状態(フィールド) | 持てない | 持てる |
| 向いている場面 | 短い処理を渡す | 複数メソッドの実装、状態を持つ実装 |
andThen・compose・negate でラムダを組み合わせる
関数型インターフェースの default メソッドを使うと、小さなラムダを部品にして組み合わせられます。
import java.util.*;
import java.util.function.*;
public class Compose {
public static void main(String[] args) {
Function<Integer, Integer> plus1 = x -> x + 1;
Function<Integer, Integer> times2 = x -> x * 2;
System.out.println("andThen : " + plus1.andThen(times2).apply(5)); // (5+1)*2
System.out.println("compose : " + plus1.compose(times2).apply(5)); // (5*2)+1
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> shortText = s -> s.length() <= 5;
List<String> words = List.of("", "pen", "notebook", " ", "desk");
System.out.println("and : " + words.stream().filter(notBlank.and(shortText)).toList());
System.out.println("or : " + words.stream().filter(notBlank.negate().or(shortText.negate())).toList());
System.out.println("not : " + words.stream().filter(Predicate.not(String::isEmpty)).toList());
BinaryOperator<String> shorter = BinaryOperator.minBy(Comparator.comparingInt(String::length));
System.out.println("minBy : " + shorter.apply("desk", "pen"));
}
}
実行結果:
andThen : 12
compose : 11
and : [pen, desk]
or : [, notebook, ]
not : [pen, notebook, , desk]
minBy : pen
andThen は「自分 → 引数」、compose は「引数 → 自分」の順に実行します。 読みやすさを優先するなら、処理の流れと同じ順に書ける andThen がおすすめです。Predicate.not(Java 11以降)は、メソッド参照を否定したいときに便利です。
@FunctionalInterface で関数型インターフェースを自作する
標準の43種に合う形がないとき(引数3つ、業務用の名前を付けたいときなど)は、自分で定義できます。 @FunctionalInterface を付けると、抽象メソッドが1つであることをコンパイラが確認してくれます。
import java.util.*;
public class CustomFi {
@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
@FunctionalInterface
interface PriceRule {
int apply(int price); // 抽象メソッドは1つだけ
default PriceRule then(PriceRule next) { // default メソッドはいくつあってもよい
return p -> next.apply(apply(p));
}
static PriceRule discount(int percent) { // static メソッドもOK
return p -> p * (100 - percent) / 100;
}
}
public static void main(String[] args) {
TriFunction<String, Integer, Integer, String> label =
(name, qty, price) -> name + " x" + qty + " = " + (qty * price) + "円";
System.out.println(label.apply("ノート", 3, 150));
PriceRule rule = PriceRule.discount(10).then(p -> p - 50).then(p -> Math.max(p, 0));
for (int price : List.of(1000, 300, 40)) {
System.out.println(price + " -> " + rule.apply(price));
}
}
}
実行結果:
ノート x3 = 450円
1000 -> 850
300 -> 220
40 -> 0
抽象メソッドを2つにすると、次のコンパイルエラーで知らせてくれます(実際の出力)。
BadFi.java:2: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
Rule is not a functional interface
multiple non-overriding abstract methods found in interface Rule
1 error
@FunctionalInterface は付けなくても動きますが、付けておくとメソッドの追加にすぐ気づけます。
チェック例外を投げるラムダの扱い方
Function や Consumer の抽象メソッドは throws を宣言していないため、IOException のようなチェック例外を投げるメソッドをそのまま呼ぶとコンパイルエラーになります。
import java.nio.file.*;
import java.util.List;
public class CheckedNg {
public static void main(String[] args) {
List<Path> paths = List.of(Path.of("a.txt"));
paths.stream().map(p -> Files.readString(p)).toList();
}
}
実際のエラー:
CheckedNg.java:7: error: unreported exception IOException; must be caught or declared to be thrown
paths.stream().map(p -> Files.readString(p)).toList();
^
1 error
throws 付きの関数型インターフェースを自作し、非チェック例外に包み直すラッパーを1つ用意すると、呼び出し側をすっきり書けます。
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.util.List;
import java.util.function.Function;
public class CheckedWrap {
// チェック例外を投げてよい関数型インターフェースを自作する
@FunctionalInterface
interface IOFunction<T, R> {
R apply(T t) throws IOException;
}
// IOException を UncheckedIOException に包んで Function に変換する
static <T, R> Function<T, R> unchecked(IOFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
public static void main(String[] args) throws IOException {
Path dir = Files.createTempDirectory("lambda");
Path a = Files.writeString(dir.resolve("a.txt"), "memo-A");
Path b = Files.writeString(dir.resolve("b.txt"), "memo-B");
List<String> texts = List.of(a, b).stream().map(unchecked(Files::readString)).toList();
System.out.println(texts);
try {
List.of(a, dir.resolve("none.txt")).stream().map(unchecked(Files::readString)).toList();
} catch (UncheckedIOException e) {
System.out.println("捕捉: " + e.getCause().getClass().getSimpleName());
}
}
}
実行結果:
[memo-A, memo-B]
捕捉: NoSuchFileException
Comparator.comparing で並べ替えを書く
並べ替えは、ラムダとメソッド参照がいちばん活躍する場面です。 Comparator.comparing 系を使うと「何で・どの順に」を宣言的に書けます。
import java.util.*;
public class ComparatorDemo {
record Task(String title, int priority, String due) {}
public static void main(String[] args) {
List<Task> tasks = new ArrayList<>(List.of(
new Task("資料作成", 2, "2026-10-03"),
new Task("備品発注", 1, "2026-10-05"),
new Task("議事録", 2, "2026-10-01"),
new Task("棚卸し", 3, null)));
// 優先度の昇順 → 同じなら期限の昇順(null は最後)
tasks.sort(Comparator.comparingInt(Task::priority)
.thenComparing(Task::due, Comparator.nullsLast(Comparator.naturalOrder())));
tasks.forEach(t -> System.out.println(t.priority() + " " + t.due() + " " + t.title()));
}
}
実行結果:
1 2026-10-05 備品発注
2 2026-10-01 議事録
2 2026-10-03 資料作成
3 null 棚卸し
降順にしたいときは Comparator.comparingInt(Task::priority).reversed() のように reversed() を付けます。
よくある質問
Q. ラムダ式はどのJavaバージョンから使えますか?
A. Java 8から使えます。 java.util.function の43種もJava 8で導入されました。Predicate.not はJava 11、引数に var を書けるのもJava 11からです。
Q. ラムダ式と匿名クラスはどちらを使えばよいですか?
A. 抽象メソッドが1つのインターフェースならラムダ式が基本です。 複数メソッドを実装する、状態をフィールドで持つ、this で自分自身を指したい、といった場合は匿名クラスが向いています。
Q. 43種も覚える必要がありますか?
A. まずは Supplier・Consumer・Function・Predicate の4つと、その Bi 版で十分です。 数値を扱うときに Int・ToInt などの特化型を思い出せれば、残りは早見表で探せます。
まとめ
- ラムダ式は関数型インターフェース(抽象メソッド1つ)の実装を短く書く記法。Java 8から使える
java.util.functionはJava 21で43種。基本9種+プリミティブ特化型34種で、「引数→戻り値」で選ぶ- 省略ルールは「型はまとめて省略」「1文なら
{}とreturnを省略」「引数1つなら()も省略」 - メソッド参照は4種(static、特定オブジェクト、任意オブジェクト、コンストラクタ)
- 外のローカル変数は実質的finalのみ、
thisは外側のクラスを指す andThen・compose・and・or・negateで小さなラムダを組み合わせられる- チェック例外は自作インターフェース+ラッパーで
UncheckedIOExceptionに包むと扱いやすい
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java ラムダ式の使い方|関数型インターフェース全43種の一覧と実行結果」
株式会社RJCでは、ラムダ式のような書き方の工夫を、コードレビューで「どちらが読みやすいか」を話し合いながら仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。