Java 8関数インタフェースfunctional interfaceの秘密
JDK 8以前に既存の関数インタフェース新しく定義された関数インタフェース関数インタフェースでは複数の抽象メソッドを追加定義できますが、これらの抽象メソッド署名はObjectのpublicメソッドと同じでなければなりません.
宣言例外スタティツクメソッドデフォルトメソッド汎用および継承関係関数インタフェースの交差@FunctionalInterface
関数インタフェース(Functional Interface)は、Java 8の特殊なタイプのインタフェースに対する呼称である.このようなインタフェースは、唯一の抽象的なメソッドのインタフェース(隠されたObjectオブジェクトを除く共通のメソッド)のみを定義するため、最初はSAMタイプのインタフェース(Single Abstract Method)を行う.
なぜインタフェースだけからこのようなインタフェースを定義するのでしょうか.Java Lambdaの実装では、開発グループはLambda式のために特殊なStructural関数タイプを単独で定義したくない.矢印タイプ(arrow type)と呼ばれ、Java既存のタイプシステム(class,interface,methodなど)を採用したいが、構造化された関数タイプを追加すると関数タイプの複雑さが増加し、既存のJavaタイプを破壊し、何千ものJavaクラスライブラリに深刻な影響を及ぼしています.利害を考慮すると,最終的にはSAMインタフェースをLambda式のターゲットタイプとして利用する.
JDKに存在するいくつかのインタフェース自体が
Runnable
のような関数インタフェースである.JDK 8にはjava.util.function
パケットが追加され、一般的な関数インタフェースが提供される.関数インタフェースは、特定の関数タイプに対する契約を表す契約です.それが現れる場所では,契約要件に合致する関数が実際に期待される.Lambda式はコンテキストから離れて存在することはできません.明確なターゲットタイプが必要です.このターゲットタイプは関数インタフェースです.
もちろん、Java 8が発表されてもうすぐ1年になりますが、以上の概念についても知っているはずです.この文章では、これらの基礎的なものを紹介するのではなく、関数式インタフェースの概念と応用を深く検討したいと思っています.
JDK 8以前に存在していた関数インタフェース
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
新しく定義された関数インタフェース
java.util.function
には、いくつかのタイプの関数インタフェースと、基本データ型のサブクラスが定義されている.Predicate — パラメータを入力し、bool結果を返します.方法は
boolean test(T t)
です.Consumer — パラメータが入力され、戻り値がなく、純消費されます.方法は
void accept(T t)
である.Function — パラメータを入力し、結果を返します.方法は
R apply(T t)
です.Supplier — パラメータ入力なしで、結果を返します.方法は
T get()
です.UnaryOperator — メタオペレータは、Functionを継承し、入力パラメータのタイプと戻りタイプが同じです.
BinaryOperator — バイナリオペレータは、入力された2つのパラメータのタイプと戻りタイプが同じで、BiFunction を継承します.
Java APIの関数インタフェースには、次のように表記されています.
1
2
3
4
5
6
7
8
9
10
11
java.langInterface Runnable
All Known Subinterfaces:RunnableFuture, RunnableScheduledFuture
All Known Implementing Classes:AsyncBoxView.ChildState, ForkJoinWorkerThread, FutureTask, RenderableImageProducer, SwingWorker, Thread, TimerTask
Functional Interface:This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
函数式接口中可以额外定义多个抽象方法,但这些抽象方法签名必须和Object的public方法一样
接口最终有确定的类实现, 而类的最终父类是Object。 因此, 函数式接口可以定义Object的public方法。
如以下的接口依然是函数式接口:
public タイプ限定の方法はなぜですか?インタフェースで定義されている方法はすべてpublic タイプであるからです.たとえば、次のインタフェースは関数インタフェースではありません. Object.clone メソッドはprotected タイプだからです.例外の宣言 関数インタフェースの抽象メソッドは宣言できます (checked exception). ターゲットオブジェクトのこのメソッドを呼び出すにはcatchという例外が必要です. これは、以前のインタフェース/メソッド呼び出しと同じです. ただし、Lambda式で例外が放出され、ターゲットインタフェースの抽象関数がこのチェック可能性を宣言していない場合、このインタフェースはこのlambda式のターゲットタイプとして使用できません.
|
上面的例子中不能编译, 因为lambda表达式要求的目标类型和InterfaceWithException
不同。 InterfaceWithException
的函数没有声明异常。
静态方法
函数式接口中除了那个抽象方法外还可以包含静态方法。
Java 8以前的规范中接口中不允许定义静态方法。 静态方法只能在类中定义。 Java 8中可以定义静态方法。
1个或者多个静态方法不会影响SAM接口成为函数式接口。
下面的例子中FunctionalInterfaceWithStaticMethod
包含一个SAM: apply
,还有一个静态方法sum
。 它依然是函数式接口。
|
默认方法
Java 8中允许接口实现方法, 而不是简单的声明, 这些方法叫做默认方法,使用特殊的关键字default
。
因为默认方法是不是抽象方法,所以不影响我们判断一个接口是否是函数式接口。
|
InterfaceWithDefaultMethod
仍然是一个函数式接口。
泛型及继承关系
接口可以继承接口。 如果父接口是一个函数接口, 那么子接口也可能是一个函数式接口。 判断标准依据下面的条件:
对于接口
I
, 假定M
是接口成员里的所有抽象方法的继承(包括继承于父接口的方法), 除去具有和Object的public的实例方法签名的方法, 那么我们可以依据下面的条件判断一个接口是否是函数式接口, 这样可以更精确的定义函数式接口。
如果存在一个一个方法m, 满足:
- m的签名(subsignature)是M中每一个方法签名的子签名(signature)
- m的返回值类型是M中的每一个方法的返回值类型的替代类型(return-type-substitutable)
那么I就是一个函数式接口。
看几个例子。
1)
インタフェース Z は、X 、Y インタフェースのm メソッドを継承している.この2つのメソッドの署名は同じであり、戻り値も同じであるため、Z には唯一の抽象メソッドint m(Iterable があり、関数インタフェースとして機能することができる.2) メソッド署名 Y.m 一致署名はX.m であり、戻り値もreturn-type-substitutable である.したがって、Z は関数インタフェースであり、関数タイプはIterable である.3) コンパイルエラー.すべてのメソッドのサブ署名であるメソッドの署名は1つもありません. 4) Compiler error:No method has a subsignature of all abstract methodsコンパイルエラー、すべてのメソッドのサブ署名に署名する方法はありません 5) Compiler error:no method is return type substitutableコンパイルエラー、戻り値タイプが異なります. 6) Compiler error:different signatures,same erasureコンパイルエラー 7) 関数インタフェースではなく、2つのメソッドのタイプパラメータが異なります. 8) X.m ,Y.m ,Z.m メソッド署名は同じで,戻り値タイプはvoidであるが,異常リストは異なる. EOFException はIOException のサブクラスである.この場合、XY とXYZ はいずれも関数インタフェースですが、関数タイプは異なります.//XY has function type ()->void throws EOFException//XYZ has function type ()->void (throws nothing) 9) //D has function type (List)->List throws EOFException, SQLTransientException//E has function type (List)->List throws EOFException, SQLTransientException 10) //G has function type ()->String throws F 関数インタフェースの交差 1)
|
I和J方法的交集依然符合函数式接口的定义。 上述代码可以用JDK中的javac编译通过但是Eclipse报错,这是Eclipse的一个bug。
2)
|
上述代码Eclipse不会报错但是javac无法编译,javac认为 (I & J)不是一个函数式接口。 看起来javac工作正常, Eclipse处理这样的case还有问题。
@FunctionalInterface
Java 不会强制要求你使用@FunctionalInterface注解来标记你的接口是函数式接口, 然而,作为API作者, 你可能倾向使用@FunctionalInterface指明特定的接口为函数式接口, 这只是一个设计上的考虑, 可以让用户很明显的知道一个接口是函数式接口。
関数式ではないインタフェースで@FunctionalInterfaceタグを使用すると、どうなりますか?コンパイル中にエラーが発生しました.
|