関数型インタフェースとラムダ式の要点
内容の確認日: 2026-10-04
この分野では、ラムダ式とメソッド参照の書き方に加えて、その土台になるネストしたクラス (内部クラス・static なネストしたクラス・無名クラス) と、java.util.function パッケージの関数型インタフェースが問われます。次の分野の Stream API はラムダ式を前提にしているので、ここでしっかり固めておきましょう。
出題項目は「内部クラス (内部クラス, static な内部クラス, 無名クラス) の使用」「関数型インタフェースの定義とインスタンス化」「ラムダ式とメソッド参照の使用」「java.util.function パッケージの主要なインタフェース (Predicate, Consumer, Function, Supplier) の使用」の 4 つです。
ネストしたクラス
クラスの中に宣言したクラスには、次の種類があります。
・内部クラス (static でないメンバークラス): 外側のクラスのインスタンスに結び付く。outer.new Inner() のように、外側のインスタンスを使って生成する。外側の private なメンバーも使える
・static なネストしたクラス: 外側のインスタンスと結び付かない。new Outer.Nested() で生成でき、外側の static なメンバーだけを直接使える
・ローカルクラス: メソッドの中で宣言したクラス
・無名クラス: new インタフェース名() { ... } のように、名前を付けずに宣言と生成を同時に行うクラス
内部クラスの中で、外側のクラスのフィールドが同じ名前で隠れている場合は、外側のクラス名.this.フィールド名 で外側のものを指定できます。
public class Main { private int value = 10; private static int count = 1; class Inner { int value = 20; void show() { int value = 30; System.out.println(value + " " + this.value + " " + Main.this.value); } } static class Nested { void show() { System.out.println("count = " + count); } } public static void main(String[] args) { Main outer = new Main(); Main.Inner inner = outer.new Inner(); inner.show(); new Main.Nested().show(); }}java Main 30 20 10 count = 1
無名クラスやラムダ式の中から使えるローカル変数は、final か実質的に final (宣言後に一度も代入し直していない) な変数だけです。後から代入し直すと、ラムダ式の中で使っている箇所がコンパイルエラーになります。
public class Main { public static void main(String[] args) { int base = 10; Runnable r = () -> System.out.println(base); base = 20; r.run(); }}javac Main.java
コンパイルエラー (4 行目): base は 5 行目で代入し直しているので実質的に final ではなく、ラムダ式の中で使えない
関数型インタフェース
関数型インタフェースは、抽象メソッドをちょうど 1 つだけ持つインタフェースです。ラムダ式やメソッド参照は、関数型インタフェースの型の値として使います。default メソッドと static メソッドはいくつあっても構いません。また、equals のように Object クラスにある public メソッドと同じシグネチャの抽象メソッドは、数に含めません。
@FunctionalInterface を付けると、条件を満たしているかをコンパイラが確かめ、抽象メソッドが 0 個や 2 個以上ならコンパイルエラーにします。付けなくても条件を満たしていれば関数型インタフェースとして使えます。型パラメータを持つ関数型インタフェースも定義できます。
@FunctionalInterfaceinterface Converter<T, R> { R convert(T value); default Converter<T, String> describe() { return v -> "[" + convert(v) + "]"; } boolean equals(Object o);}public class Main { public static void main(String[] args) { Converter<String, Integer> length = String::length; System.out.println(length.convert("Gold")); System.out.println(length.describe().convert("Java")); }}java Main 4 [4]
ラムダ式の書き方とスコープ
ラムダ式は (引数) -> 処理 の形で書きます。主な省略のルールは次のとおりです。
・引数の型は省略できる。書く場合はすべての引数に書く (一部だけは不可)。Java SE 11 からは (var a, var b) のように var も使える
・引数が 1 つで型を書かない場合だけ、( ) を省略できる
・処理が 1 つの式なら { } と return を省略できる。{ } を書いたら、値を返すには return が必要
ラムダ式は新しいスコープを作らず、囲んでいるメソッドと同じスコープにあるとみなされます。そのため、ラムダ式の引数に、メソッドのローカル変数と同じ名前は使えません。また、ラムダ式の中の this は、ラムダ式を囲んでいるクラスのインスタンスを指します (無名クラスの中の this は、無名クラス自身のインスタンスです)。
import java.util.function.Function;public class Main { public static void main(String[] args) { String s = "x"; Function<String, Integer> f = s -> s.length(); System.out.println(f.apply("abc")); }}javac Main.java
コンパイルエラー (6 行目): ラムダ式の引数 s が、同じスコープのローカル変数 s と重複している
メソッド参照
処理が既存のメソッドを呼ぶだけなら、ラムダ式の代わりにメソッド参照 (クラス名::メソッド名 など) を使えます。次の 4 種類があります。
・static メソッドの参照: Integer::parseInt (s -> Integer.parseInt(s) と同じ)
・特定のオブジェクトのメソッドの参照: System.out::println (x -> System.out.println(x) と同じ)
・任意のオブジェクトのメソッドの参照: String::toUpperCase (s -> s.toUpperCase())。String::startsWith は (s, prefix) -> s.startsWith(prefix) のように、第 1 引数がメソッドを呼ぶ対象になる
・コンストラクタの参照: ArrayList::new (() -> new ArrayList<>() と同じ)
import java.util.ArrayList;import java.util.List;import java.util.function.BiPredicate;import java.util.function.Function;import java.util.function.Supplier;public class Main { public static void main(String[] args) { Function<String, Integer> parse = Integer::parseInt; BiPredicate<String, String> starts = String::startsWith; Supplier<List<String>> maker = ArrayList::new; Function<String, String> upper = String::toUpperCase; List<String> list = maker.get(); list.add(upper.apply("java")); System.out.println(parse.apply("41") + 1); System.out.println(starts.test("Gold", "Go")); System.out.println(list); }}java Main 42 true [JAVA]
式::メソッド名 の形のメソッド参照では、:: の左側の式はメソッド参照を作った時点で評価されます。その後で変数に別のオブジェクトを代入しても、メソッド参照が使うのは最初のオブジェクトです (ラムダ式と違い、変数が実質的に final である必要もありません)。
import java.util.function.Supplier;public class Main { public static void main(String[] args) { StringBuilder sb = new StringBuilder("ab"); Supplier<Integer> length = sb::length; sb = new StringBuilder("abcde"); System.out.println(length.get()); }}java Main 2
java.util.function の主なインタフェース
主なインタフェースと、その抽象メソッドの名前は次のとおりです。メソッド名はよく問われるので、正確に覚えましょう。
・Supplier<T>: 引数なしで値を返す。T get()
・Consumer<T>: 値を受け取り、何も返さない。void accept(T t)
・Predicate<T>: 値を受け取り、boolean を返す。boolean test(T t)
・Function<T, R>: 値を変換する。R apply(T t)
・UnaryOperator<T>: 同じ型に変換する (Function<T, T> のサブインタフェース)
・BinaryOperator<T>: 同じ型の 2 つの値から同じ型の値を作る (BiFunction<T, T, T> のサブインタフェース)
・BiConsumer・BiPredicate・BiFunction: 引数が 2 つの版
プリミティブ型に特化したインタフェースは、メソッド名が変わるものがあります。IntPredicate は test、IntFunction<R> は apply のままですが、ToIntFunction<T> は applyAsInt、IntSupplier は getAsInt です。
インタフェースには、関数を組み合わせる default メソッドがあります。Function の f.andThen(g) は f の後に g、f.compose(g) は g の後に f を適用します。Predicate は and・or・negate で条件を組み合わせられます。
import java.util.Comparator;import java.util.function.BinaryOperator;import java.util.function.Function;import java.util.function.Predicate;import java.util.function.ToIntFunction;public class Main { public static void main(String[] args) { Function<Integer, Integer> plus2 = n -> n + 2; Function<Integer, Integer> times3 = n -> n * 3; System.out.println(plus2.andThen(times3).apply(1)); System.out.println(plus2.compose(times3).apply(1)); Predicate<String> empty = String::isEmpty; Predicate<String> shortWord = s -> s.length() <= 3; Predicate<String> p = empty.negate().and(shortWord); System.out.println(p.test("") + " " + p.test("abc") + " " + p.test("abcd")); BinaryOperator<Integer> min = BinaryOperator.minBy(Comparator.naturalOrder()); System.out.println(min.apply(5, 3)); ToIntFunction<String> length = String::length; System.out.println(length.applyAsInt("lambda")); }}java Main 9 5 false true false 3 6
plus2.andThen(times3) は (1 + 2) * 3 = 9、plus2.compose(times3) は 1 * 3 + 2 = 5 です。
試験でよくある引っかけ
内部クラスの生成
staticでない内部クラスは、outer.new Inner()のように外側のインスタンスが必要です。staticなネストしたクラスはnew Outer.Nested()で作れます。実質的に
finalラムダ式・無名クラスから使うローカル変数は、後から代入し直すとコンパイルエラーです。代入がラムダ式より後にあっても同じです。
関数型インタフェースの抽象メソッドの数
default・staticメソッドと、Objectのpublicメソッドと同じ抽象メソッドは数えません。残りがちょうど 1 つなら関数型インタフェースです。ラムダ式の引数名
囲んでいるメソッドのローカル変数と同じ名前の引数は、コンパイルエラーです。
andThenとcomposeの順番f.andThen(g)はf→g、f.compose(g)はg→fの順に適用します。プリミティブ型の関数型インタフェース
ToIntFunctionはapplyAsInt、IntSupplierはgetAsIntです。applyやgetではありません。
問題で確認する
この分野の問題を、解説つきで読めます (登録不要)。
出典・参考
- Oracle University 日本「Java SE 17 Programmer II (1Z0-826-JPN) 試験」
- The Java Tutorials「Lambda Expressions」
- The Java Tutorials「Method References」
- Java SE 17 API ドキュメント「java.util.function」
コード例は、試験の対象バージョンで実際にコンパイル・実行して結果を確認しています。