倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

java.util.function パッケージ 【追記・修正あり】

拙者、あんまり正式リリースされてない API って扱わない保守的な人間なんですが、ふと Java8 で導入されるラムダ式ってのがどんな感じになるのか気になったのでドキュメントを探してみました。 ラムダ式に対応したドキュメントや JavaDoc は普通の「JDK〓 8 Early Access Releases」にはまだ含まれてないようで、「Java〓 Platform, Standard Edition 8 Early Access with Lambda Support」 からダウンロードが必要。

ちなみに、今回は java.util.function パッケージに含まれるインターフェースを見ていく記事で、実際にラムダ式を用いた関数オブジェクトの生成などコードは書いてません。 また、当然のことながら最終的に仕様が変更になってるかも知れませんのでご注意を。

参考

追記
ea-b120 で試してみたらチラホラと変更があったので所々修正しました(2014年1月1日)。

  • メソッドの追加・削除・変更
    • Function に andThen(), identity() を追加
    • BiFunction に andThen() を追加
    • UnaryOperator に identity() を追加
    • BinaryOperator に maxBy(), minBy() を追加
    • Predicate に isEqual() を追加、xor() を削除
    • BiPredicate から xor() を削除
    • Consumer の chain() を andThen() に変更
    • BiConsumer の chain() を andThen() に変更
  • インターフェースの追加
    • IntToLongFuncion
    • IntToDoubleFunction
    • LongToIntFuncion
    • LongToDoubleFunction
    • DoubleToIntFuncion
    • DoubleToLongFunction

andThen() メソッドは Function, BiFunction, Consumer, BiConsumer に追加されてますが、これらは先に自分を作用させる合成を行う、という意味で統一されてるようです。 また、プリミティブ型を返り値に持つ関数インターフェースの抽象メソッドが applyAsInt() だったりするところを apply() と書いてたところを修正しました。

Overview

java.util.function パッケージに定義されているインターフェースを大雑把に分類するとこんな感じ:

各インターフェースには抽象メソッドが1つずつ定義されています。 また、「Bi」が付いているのはその抽象メソッドの引数が「2つ」になってます。 引数、返り値がともに Object のもの(プリミティブ型でないもの)を表にまとめると以下の通り:

インターフェース 抽象メソッド 引数型 → 返り値型
Function
BiFunction
apply(...)  T   → R
T × U → R
UnaryOperator
BinaryOperator
apply(...)  T   → T
T × T → T
Predicate
BiPredicate
test(...)  T   → boolean
T × U → boolean
Supplier get()  ()   → T
Consumer
BiConsumer
accept(...)  T   → ()
T × U → ()

Supplier は引数なし(よって BiSupplier というインターフェースはない)、Consumer は返り値が void です。 これらの他に、引数もしくは返り値がプリミティブ型のインターフェースがあれこれ定義されてますが、現在のところ全プリミティブ型には対応してなくて

  • int
  • long
  • double
  • boolean (Supplier のみ)

にのみ対応してるようです。

継承関係は Function 系と Operator 系との間にのみにあり*1、Operator は引数と返り値が同じ型の Function という関係になってます。 また、プリミティブ型が異なれば(int と long というように)やはり継承関係はありません。

java.util.function パッケージに定義されているクラスは Function 系, Predicate 系に対するユーティリティ・クラス Functions, Predicates があります。

では、それぞれのインターフェースをもう少し詳しく見ていきましょう。

Function / BiFunction (関数)

Function / BiFunction は関数を表すインターフェースです。 定義されている抽象メソッドは apply() です。

Function (1引数)

  • XxxFunction
    • Function
    • IntFunction
    • LongFunction
    • DoubleFunction
  • ToXxxFunction
    • ToIntFunction
    • ToLongFunction
    • ToDoubleFunction
  • XxxToYyyFunction
    • IntToLongFuncion
    • IntToDoubleFunction
    • LongToIntFuncion
    • LongToDoubleFunction
    • DoubleToIntFuncion
    • DoubleToLongFunction

型宣言は以下の通り:

package java.util.function;

@FunctionalInterface
public interface Function<T,R>{

    R apply(T t);

    default <V> Function<T,V> andThen(Function<? super R,? extends V> after);
    default <V> Function<V,R> compose(Function<? super V,? extends T> before);

    static <T> Function<T,T> identity()
}

andThen(), compose() メソッドは関数の合成を行うためのメソッドです。 andThen() メソッドはこの Function オブジェクトを先に適用してその結果に引数の関数(Function オブジェクト)を作用させる、という Function オブジェクトを返します。 compose() メソッドはそれとは適用順序が逆の合成関数を返します。 identity() メソッド (static) は恒等関数(何もしない関数、引数をそのまま返す関数)に対応する Function オブジェクトを返します。

インターフェース 抽象メソッド 引数型 → 返り値型
Function<T, R> R apply(T) T → R
IntFunction<R>
LongFunction<R>
DoubleFunction<R>
apply(int)
apply(long)
apply(double)
int → R
long → R
double → R
ToIntFunction<T>
ToLongFunction<T>
ToDoubleFunction<T>
applyAsInt(T)
applyAsLong(T)
applyAsDouble(T)
T → int
T → long
T → double
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
applyAsLong(int)
applyAsDouble(int)
applyAsInt(long)
applyAsDouble(long)
applyAsInt(double)
applyAsLong(double)
int → long
int → double
long → int
long → double
double → int
double → long

抽象メソッドが applyAsXxx() の場合、返り値が xxx 型だということに注意。 引数と返り値の型が同じプリミティブ型の関数を使いたい場合は、下記の XxxUnaryOperator を使って下さい。 Function インターフェース以外には andThen(), compose(), identity() メソッドはないようです。 プリミティブ型の関数にまで合成関数を返すメソッドを定義してたら、それこそ組み合わせ論的にメソッド数が爆発してしまいそうだし。 なんでまた、こんな仕様にしようとしたんだか。

BiFunction (2引数)
BiFunction は引数が2つの関数です。

  • BiFunction
  • ToIntBiFunction
  • ToLongBiFunction
  • ToDoubleBiFunction
package java.util.function;

@FunctionalInterface
public interface BiFunction<T,U,R>{

    R apply(T t, U u);

    default <V> BiFunction<T,U,V> andThen(Function<? super R,? extends V> after);
}

andThen() メソッドは関数の合成を行うためのメソッドです。

インターフェース 抽象メソッド 引数型 → 返り値型
BiFunction<T, U, R>
ToIntBiFunction<T, U>
ToLongBiFunction<T, U>
ToDoubleBiFunction<T, U>
apply(T, U)
applyAsInt(T, U)
applyAsLong(T, U)
applyAsDouble(T, U)
T × U → R
T × U → int
T × U → long
T × U → double

UnaryOperator / BinaryOperator (演算子

UnaryOperator は1項演算子*2、BinaryOperator は2項演算子*3です。 UnaryOperator は型パラメータが全て同じの Function、BinaryOperator は型パラメータが全て同じの BiFunction です。 抽象メソッドは Function / BiFunction と同じく apply() です。

UnaryOperator (1引数)

  • UnaryOperator
  • IntUnaryOperator
  • LongUnaryOperator
  • DoubleUnaryOperator
package java.util.function;

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T,T>{

    static <T> UnaryOperator<T> identity();
}

identity() メソッド (static) は恒等演算(何もしない演算、引数をそのまま返す演算)を行う UnaryOperator オブジェクトを返します。 UnaryOperator は Function のサブタイプなので andThen(), compose() メソッドも使えます。

インターフェース 抽象メソッド 引数型 → 返り値型
UnaryOperator apply(T) T → T
IntUnaryOperator applyAsInt(int) int → int
LongUnaryOperator applyAsLong(long) long → long
DoubleUnaryOperator applyAsDouble(double) double → double

BinaryOperator (2引数)

  • BinaryOperator
  • IntBinaryOperator
  • LongBinaryOperator
  • DoubleBinaryOperator
package java.util.function;

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T>{

    static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator);
    static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator);
}

maxBy(), minBy() メソッド (static) は、それぞれ、引数の2つの値のうち大きい方、小さい方を返す二項演算子を返します。 引数は比較を行う Comparator です。 BiFunction から継承した andThen() メソッドもあります。

BinaryOperator apply(T, T) T × T → T
IntBinaryOperator applyAsInt(int, int) int × int → int
LongBinaryOperator applyAsLong(long, long) long × long → long
DoubleBinaryOperator applyAsDouble(double, double) double × double → double

Predicate / BiPredicate (述語)

Prediate / BiPredicate は述語を表すインターフェースです。 「述語」とは主語等にあたる変数の値が確定すれば真偽が確定するテストと言えばいいんでしょうかね。 気持ち的には「ToBooleanFunction / ToBooleanBiFunction」と言えます。 ただし、Function / BiFunction インターフェースとの関係(継承関係など)はありません。 定義されている抽象メソッドは test() です。

Predicate (1引数)

  • Predicate
  • IntPredicate
  • LongPredicate
  • DoublePredicate
package java.util.function;

@FunctionalInterface
public interface Predicate<T>{

    boolean test(T t);

    default Predicate<T> negate();
    default Predicate<T> and(Predicate<? super T> p);
    default Predicate<T> or(Predicate<? super T> p);

    static <T> Predicate<T> isEqual(Object targetRef);
}

negate(), and(), or() メソッドは、この Predicate オブジェクトと引数の Predicate オブジェクトの評価結果に対して論理演算を行う Predicate オブジェクトを生成します。 and(), or() はショートカット演算子 &&, || のように、不必要な評価は行われません。 isEqual() メソッド (static) は引数のオブジェクトと同じ(Object#equals() によって)オブジェクトに対して test() を呼ばれたときに true を返すような Predicate オブジェクトを返します。

インターフェース 抽象メソッド 引数型 → 返り値型
Predicate test(T) T → boolean
IntPredicate test(int) int → boolean
LongPredicate test(long) long → boolean
DoublePredicate test(double) double → boolean

BiPredicate (2引数)

  • BiPredicate
package java.util.function;

@FunctionalInterface
public interface BiPredicate<T,U>{

    boolean test(T t, U u);

    default BiPredicate<T,U> negate();
    default BiPredicate<T,U> and(BiPredicate<? super T,? super U> p);
    default BiPredicate<T,U> or(BiPredicate<? super T,? super U> p);
}

negate(), and(), or() メソッドは、この BiPredicate オブジェクトと引数の BiPredicate オブジェクトの評価結果に対して論理演算を行う BiPredicate オブジェクトを生成します。 and(), or() はショートカット演算子 &&, || のように、不必要な評価は行われません。

インターフェース 抽象メソッド 引数型 → 返り値型
BiPredicate test(T, U) T × U → boolean

Supplier

Supplier は何らかの値を供給 (supply) するインターフェースです。 引数のない Function と思えばいいでしょう。 当然のことながら、2引数の BiSupplier なんてのはありません。 プリミティブ型のサポートとして、int, long, double 値以外にも、boolean 値を返す BooleanSupplier というのも定義されています。 抽象メソッドは get() です。

  • Supplier
  • BooleanSupplier
  • IntSupplier
  • LongSupplier
  • DoubleSupplier
package java.util.function;

@FunctionalInterface
public interface Supplier<T>{
    T get();
}
インターフェース 抽象メソッド 引数型 → 返り値型
Supplier get() () → T
BooleanSupplier getAsBoolean() () → boolean
IntSupplier getAsInt() () → int
LongSupplier getAsLong() () → long
DoubleSupplier getAsDouble() () → double

Consumer / BiConsumer

Consumer インターフェースは値を受け取って処理をするが、返り値を返さないインターフェースです。 返り値のない(返り値が void の) Function と思えばいいでしょう。 ただし、返り値がないということはメソッド内でなんらかの副作用を伴う処理をしていることになるので、関数型プログラミングを行うときは注意が必要ってことにんるんでしょうね。 定義されている抽象メソッドは accept() です。
Consumer (1引数)

  • Consumer
  • IntConsumer
  • LongConsumer
  • DoubleConsumer
package java.util.function;

@FunctionalInterface
public interface Consumer<T>{

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after);
}

chain() メソッドは、この Consumer オブジェクトと引数の Consumer オブジェクトの処理を連続して行う Consumer オブジェクトを生成します。 処理の順序はこの Consumer オブジェクトの方が先に行われます。

インターフェース 抽象メソッド 引数型 → 返り値型
Consumer accept(T) T → ()
IntConsumer accept(int) int → ()
LongConsumer accept(long) long → ()
DoubleConsumer accept(double) double → ()

BiConsumer (2引数)

  • BiConsumer
  • ObjIntConsumer
  • ObjLongConsumer
  • ObjDoubleConsumer
package java.util.function;

@FunctionalInterface
public interface BiConsumer<T,U>{

    void accept(T t, U u);

    default BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after);
}

chain() メソッドは、この BiConsumer オブジェクトと引数の BiConsumer オブジェクトの処理を連続して行う BiConsumer オブジェクトを生成します。 処理の順序はこの BiConsumer オブジェクトの方が先に行われます。

インターフェース 抽象メソッド 引数型 → 返り値型
BiConsumer accept(T, U) T × U → ()
ObjIntConsumer accept(T, int) T × int → ()
ObjLongConsumer accept(T, long) T × long → ()
ObjDoubleConsumer accept(T, double) T × double → ()

@FunctionalInterface

上記の各インターフェースには共通するスーパーインターフェースはありませんが、関数型であることを表すマーカーインターフェース @FunctionalInterface アノテーションが付与されています。 定義はこんなの:

package java.lang;

@Documented
@Retention(value=RUNTIME)
@Target(value=TYPE)
public @interface FunctionalInterface{}

ちなみに、サブタイプには継承されないようなので、(Function インターフェースのサブインターフェースである) UnaryOperator インターフェースなどにも付与されてます。

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

実践 F# 関数型プログラミング入門

実践 F# 関数型プログラミング入門

*1:継承関係と言うよりは型パラメータの代入。

*2:符号の+, -、否定の !、ビット反転院の ~ など

*3:int 値どうしの和 + など。