Function を一言でいうと
java.util.function.Function<T, R> は、
「T 型の値を 1 つ受け取って、R 型の値を 1 つ返す“変換処理”を表す関数型インターフェース」
です。
数式で書くと f: T → R。
コードで書くと「`T を受け取って R を返すメソッド」を、オブジェクトとして表現したもの」です。
String → Integer(文字列を数値に変換)User → String(ユーザから表示名を取り出す)Integer → Integer(数値を 2 倍にする)
といった「変換」や「マッピング」を、ラムダ式で扱いたいときの基本中の基本の型です。
Function の中身をまずしっかり掴む
シグネチャ(メソッド定義)を読む
Function<T, R> はざっくりこう定義されています。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t); // 抽象メソッド(これが本体)
// ほかに andThen, compose, identity など default メソッドがある
}
Java一番大事なのはこの 1 行です。
R apply(T t);
Javaこれを日本語にすると、
「T を 1 つ受け取って、R を 1 つ返すメソッド」
です。
つまり、Function<T, R> は「T -> R の関数の“型”」だと理解してください。
最もシンプルな使用例
例として、「Integer を 2 倍して Integer を返す関数」を書いてみます。
import java.util.function.Function;
public class FunctionBasic {
public static void main(String[] args) {
Function<Integer, Integer> doubleFunc = x -> x * 2;
Integer result = doubleFunc.apply(10); // 20
System.out.println(result);
}
}
Javaここでの対応関係を整理します。
- 型:
Function<Integer, Integer>
→T = Integer,R = Integerの「Integer -> Integer」関数 - ラムダ:
x -> x * 2
→applyメソッドの中身をラムダで書いたもの - 呼び出し:
doubleFunc.apply(10)
→ 「10 を渡して結果を返してもらう」
ラムダを見たら必ず、「どの関数型インターフェースに代入されているか(ここでは Function)」とセットで考える癖を付けてください。
典型的な変換例:String → Integer、User → String
例1:文字列を整数に変換する Function
import java.util.function.Function;
public class StringToInt {
public static void main(String[] args) {
Function<String, Integer> parseInt =
s -> Integer.parseInt(s);
System.out.println(parseInt.apply("123")); // 123
}
}
JavaFunction<String, Integer> は「String -> Integer の関数」です。apply("123") とすると、その変換結果が返ってきます。
メソッド参照を使ってもっと短く書くと、こうも書けます。
Function<String, Integer> parseInt = Integer::parseInt;
Java「Integer.parseInt(String) という既存メソッドを、そのまま Function として扱う」という書き方です。
例2:ユーザーオブジェクトから名前を取り出す Function
import java.util.function.Function;
class User {
final String name;
final int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class UserNameMapper {
public static void main(String[] args) {
Function<User, String> toName =
u -> u.name;
User alice = new User("Alice", 20);
String name = toName.apply(alice); // "Alice"
System.out.println(name);
}
}
JavaFunction<User, String> は「User -> String」。
つまり「User から String を取り出す」変換関数です。
こういう「オブジェクトから ある情報だけを取り出す」処理は、Stream の map などで頻出します。
Stream.map と Function の関係(ここはしっかり)
map は「要素を別の形に変換するための Function を受け取る」
Stream の map のシグネチャはこうです。
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
Java長くて怖そうですが、真ん中だけ見るとこうです。
「Function<T, R> を渡すと、Stream<R> を返す」
つまり、「元の要素 T を R に変換する関数」を渡すメソッドです。
具体例で見ます。
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MapExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie");
Function<String, Integer> lengthFunc = s -> s.length();
List<Integer> lengths =
names.stream()
.map(lengthFunc) // String -> Integer の Function
.collect(Collectors.toList());
System.out.println(lengths); // [5, 3, 7]
}
}
Javamap(lengthFunc) の部分で、
lengthFuncはFunction<String, Integer>namesはStream<String>- 結果は
Stream<Integer>
という対応になっているのが分かると思います。
直接ラムダで書くと、こうも書けます。
names.stream()
.map(s -> s.length())
.collect(Collectors.toList());
Javaこの s -> s.length() は、
「Function<String, Integer> として解釈されるラムダ式」です。
andThen / compose による「関数の合成」(超重要)
Function の強みの 1 つが、「関数どうしを合成できる」という点です。andThen と compose という default メソッドが用意されています。
andThen:まずこれをして、そのあとあれをする
f.andThen(g) は、「先に f を適用して、その結果に対して g を適用する」という合成関数を作ります。
式で書くと x -> g(f(x)) です。
例:「文字列を数値に変換してから 2 倍にする」
import java.util.function.Function;
public class AndThenExample {
public static void main(String[] args) {
Function<String, Integer> parseInt = Integer::parseInt; // String -> Integer
Function<Integer, Integer> doubleFunc = x -> x * 2; // Integer -> Integer
Function<String, Integer> parseAndDouble =
parseInt.andThen(doubleFunc); // String -> Integer
System.out.println(parseAndDouble.apply("10")); // 20
}
}
Javaここで parseAndDouble は、「String → Integer の新しい Function」です。
中身は「Integer.parseInt してから x * 2 する」処理になっています。
流れを言葉にすると、
「parseInt を適用 → 結果を doubleFunc に渡す」
です。
compose:g をしてから f をする(順番が逆)
f.compose(g) は、「先に g を適用して、その結果に対して f を適用する」合成です。
式で書くと x -> f(g(x))。
さっきの例を compose で書いてみます。
Function<String, Integer> parseInt = Integer::parseInt;
Function<Integer, Integer> doubleFunc = x -> x * 2;
Function<String, Integer> parseAndDouble =
doubleFunc.compose(parseInt); // String -> Integer
Javaこれも「文字列を数値にしてから 2 倍にする」関数です。
andThen と compose の違いは、「どっちを先に適用するか」です。
f.andThen(g)→g(f(x))(f のあと g)f.compose(g)→f(g(x))(g のあと f)
最初は混乱しやすいので、
「andThen は“and then …(そして次に)”と読むので、“自分のあとに” g が来る」
「compose は“g を先に適用して、そのあと自分(f)を適用する」
と自然言語ベースで覚えてしまうのもアリです。
条件付きで使える場面を意識する
関数の合成をちゃんと使うと、
「変換処理を小さなステップに分けて書き、それを組み合わせて新しい処理を作る」
というスタイルが取れます。
例えば、
User -> String(名前を取り出す)String -> Integer(名前の長さを求める)
を別々の Function として定義し、andThen で「User -> Integer」の関数を作る、といった使い方です。
Function.identity():入力そのまま返す関数
「なにもしない変換」が欲しいとき
Function.identity() は、「渡された値をそのまま返す関数」 を返します。
型は Function<T, T> です。
import java.util.function.Function;
public class IdentityExample {
public static void main(String[] args) {
Function<String, String> id = Function.identity();
System.out.println(id.apply("hello")); // "hello"
}
}
Java「何も変換したくないけど、Function<T, T> が必要」という場面で使います。
例えば、Collectors.toMap などで「キーは何かで変換、値はそのまま使う」
といったときに出てきます。
原始型用の Function(IntFunction など)
オートボクシングを避けるためのバリエーション
Function<T, R> は参照型用なので、Function<Integer, Integer> のように書くと、int との間でオートボクシングが発生します。
大量の数値を扱う場面では、プリミティブ専用の関数型インターフェースを使う方が効率的です。
よく出てくるものとしては:
IntFunction<R>:int -> RLongFunction<R>:long -> RDoubleFunction<R>:double -> R
例えば、「int を文字列に変換する」関数ならこうです。
import java.util.function.IntFunction;
public class IntFunctionExample {
public static void main(String[] args) {
IntFunction<String> toString =
x -> "値: " + x;
System.out.println(toString.apply(10)); // "値: 10"
}
}
JavaStream の IntStream.mapToObj などで出てきます。
Function を自作メソッドの引数として使う発想
「処理の中身」を外から注入できる
例えば、「リストの全要素に何らかの変換をかける」ユーティリティを自分で書きたいとします。
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class MyMapper {
public static <T, R> List<R> mapList(List<T> list, Function<? super T, ? extends R> mapper) {
List<R> result = new ArrayList<>();
for (T t : list) {
R r = mapper.apply(t);
result.add(r);
}
return result;
}
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob");
List<Integer> lengths = mapList(names, s -> s.length());
System.out.println(lengths); // [5, 3]
}
}
Javaここで mapList は、「変換の枠組みだけ」を担当しています。
List<T>を受け取る- 各要素に
Function<T, R>を適用する List<R>を返す
“どう変換するか”は外から受け取る。
“どうループして結果を集めるか”だけを mapList が担当する。
このように、Function を引数に取ると、「アルゴリズム」と「具体的な処理内容」をキレイに分離できます。
Stream API の多くは、まさにこういう思想で設計されています。
まとめ:Function を自分の中でこう位置づける
Function<T, R> を一文でまとめると、
「T を 1 つ受け取って R を返す“変換”を表す、Java の基本的な関数型インターフェース」
です。
特に押さえておきたいポイントは、
- シグネチャは
R apply(T t)→ 「T -> R」 mapなど「変換」を行うメソッドで多用されるandThen/composeで関数どうしを合成できる(処理を小さく分けて組み合わせられる)Function.identity()で「そのまま返す」関数が手に入る- 原始型用に
IntFunctionなどのバリエーションがある - 自作メソッドの引数に使うと、「枠組み」と「処理の中身」を分離できる
