Java8のインターフェースの変更点-静的メソッド、デフォルトメソッド
Java 8のインターフェースの変更には、インターフェース内での静的メソッドやデフォルトメソッドの追加が含まれています。Java 8以前では、インターフェース内でのメソッドの宣言のみが可能でした。しかし、Java 8からは、デフォルトメソッドや静的メソッドをインターフェース内に記述することができるようになりました。
Java 8のインターフェース
インターフェースの設計は常に困難な仕事であり、インターフェースに追加のメソッドを追加する場合は、すべての実装クラスの変更が必要になります。インターフェースが古くなると、それを実装するクラスの数が増える可能性があるため、インターフェースを拡張することができなくなることもあります。そのため、アプリケーションの設計時には、ほとんどのフレームワークがベースの実装クラスを提供し、それを拡張してアプリケーションに適用可能なメソッドをオーバーライドします。Java 8のインターフェースの変更におけるデフォルトのインターフェースメソッドと静的インターフェースメソッド、及びその導入の理由について見てみましょう。
Javaのインターフェースデフォルトメソッド
Javaのインターフェースでデフォルトメソッドを作成するためには、メソッドのシグネチャに「default」キーワードを使用する必要があります。例えば、
package com.scdev.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
Interface1において、log(String str)はデフォルトメソッドです。クラスがInterface1を実装する場合、インターフェースのデフォルトメソッドの実装を提供する必要はありません。この機能により、インターフェースを追加のメソッドで拡張することが可能となります。必要なのはデフォルトの実装を提供するだけです。別のインターフェースには以下のメソッドがあるとしましょう。
package com.scdev.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
Javaでは、複数のクラスを拡張することは許されていないことがわかっています。なぜなら、そうすると「ダイアモンド問題」と呼ばれる問題が発生し、コンパイラはどのスーパークラスのメソッドを使うかを決めることができません。デフォルトメソッドを使用すると、インターフェースでもダイアモンド問題が発生します。なぜなら、クラスがInterface1とInterface2の両方を実装しており、共通のデフォルトメソッドを実装していない場合、コンパイラはどちらを選ぶかを決めることができないからです。複数のインターフェースを拡張することは、Javaのコアクラスやほとんどのエンタープライズアプリケーションやフレームワークでも欠かせない要素です。したがって、インターフェースでこの問題が発生しないようにするために、共通のデフォルトメソッドの実装が必須とされています。したがって、上記の両方のインターフェースを実装するクラスは、log()メソッドの実装を提供する必要があります。そうしないと、コンパイラはコンパイル時エラーをスローします。Interface1とInterface2の両方を実装した簡単なクラスは以下のとおりです。
package com.scdev.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
Javaインターフェースデフォルトメソッドに関する重要なポイント:
-
- Javaのインターフェースのデフォルトメソッドは、実装クラスを壊す心配なく、インターフェースの拡張を助けてくれます。
-
- Javaのインターフェースのデフォルトメソッドは、インターフェースと抽象クラスの違いを埋めてくれます。
-
- Java 8のインターフェースのデフォルトメソッドは、ユーティリティクラスを避けるのに役立ちます。例えば、すべてのCollectionsクラスのメソッドはインターフェース自体に提供されることができます。
-
- Javaのインターフェースのデフォルトメソッドは、基本的な実装クラスを削除するのに役立ちます。デフォルトの実装を提供し、実装クラスはオーバーライドするメソッドを選択することができます。
-
- インターフェースにデフォルトメソッドを導入する主な理由の1つは、Java 8でのラムダ式のサポートを含むCollections APIを強化するためです。
-
- 階層内のクラスに同じシグネチャを持つメソッドがある場合、デフォルトメソッドは無関係になります。デフォルトメソッドはjava.lang.Objectクラスのメソッドをオーバーライドすることはできません。その理由は非常に単純で、Objectはすべてのjavaクラスの基底クラスです。したがって、インターフェースでObjectクラスのメソッドをデフォルトメソッドとして定義しても無意味です。常にObjectクラスのメソッドが使用されます。したがって、混乱を避けるために、Objectクラスのメソッドをオーバーライドするデフォルトメソッドを持つことはできません。
- Javaのインターフェースのデフォルトメソッドは、Defender MethodsまたはVirtual extension methodsとも呼ばれています。
Javaのインターフェースの静的メソッド
Javaのインターフェースのstaticメソッドは、デフォルトメソッドと似ていますが、実装クラスではオーバーライドすることができません。この機能は、実装クラスでの実装の不良による望ましくない結果を避けるのに役立ちます。簡単な例を使って見てみましょう。
package com.scdev.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
では、実装が不十分なisNull()メソッドを持つ実装クラスを見てみましょう。
package com.scdev.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
isNull(String str)メソッドは、インターフェースのメソッドをオーバーライドしているのではなく、単純なクラスメソッドであることに注意してください。例えば、isNull()メソッドに@Overrideアノテーションを追加すると、コンパイルエラーが発生します。アプリケーションを実行すると、以下の出力が得られます。
Interface Null Check
Impl Null Check
もし私達がインターフェースメソッドを静的からデフォルトに変更したら、以下の出力結果が得られます。
Impl Null Check
MyData Print::
Impl Null Check
Javaのインターフェースの静的メソッドは、インターフェースのメソッドからのみ見えます。もしMyDataImplクラスからisNull()メソッドを削除した場合、MyDataImplオブジェクトには使用できません。しかし、他の静的メソッドと同様に、クラス名を使用してインターフェースの静的メソッドを使用することができます。たとえば、有効な文は以下の通りです:
boolean result = MyData.isNull("abc");
Javaのインターフェースの静的メソッドについて重要なポイントは以下の通りです。
-
- Javaのインターフェースの静的メソッドは、インターフェースの一部であり、実装クラスのオブジェクトには使用できません。
-
- Javaのインターフェースの静的メソッドは、ユーティリティメソッドの提供に適しています。たとえば、nullチェック、コレクションのソートなどです。
-
- Javaのインターフェースの静的メソッドは、実装クラスがオーバーライドできないため、セキュリティを提供します。
-
- Objectクラスのメソッドに対しては、インターフェースの静的メソッドを定義することはできません。この場合、コンパイラエラーが表示されます。「この静的メソッドは、Objectのインスタンスメソッドを隠すことはできません」という理由です。これはJavaでは許可されていないためであり、すべてのクラスの基本クラスであるObjectに対してクラスレベルの静的メソッドと同じシグネチャを持つインスタンスメソッドを持つことはできません。
- Collectionsなどのユーティリティクラスを削除し、対応するインターフェースにすべての静的メソッドを移動するためにJavaのインターフェースの静的メソッドを使用することができます。これにより、見つけやすく使用しやすくなります。
Javaの機能インタフェース
投稿をまとめる前に、関数インターフェースについて簡単に紹介したいと思います。正確に1つの抽象メソッドを持つインターフェースを、関数インターフェースと呼びます。@FunctionalInterfaceという新しいアノテーションが導入され、インターフェースを関数インターフェースとしてマークすることができます。@FunctionalInterfaceアノテーションは、関数インターフェースに抽象メソッドが誤って追加されることを防ぐためのものです。オプションですが、使用することをおすすめします。関数インターフェースは、Java 8の待望の機能であり、ラムダ式を使用してインスタンス化することができます。ラムダ式とメソッド参照の対象型を提供するために、java.util.functionという新しいパッケージにいくつかの関数インターフェースが追加されました。今後の投稿で関数インターフェースとラムダ式について詳しく見ていきます。