デフォルトの方法は何ですか?
Java 8 のリリース後、新しいメソッドをインターフェースに追加できますが、インターフェースはその実装クラスとの互換性を維持します。開発するライブラリは複数の開発者によって広く使用される可能性があるため、これは重要です。 Java 8 より前では、クラス ライブラリでインターフェイスが公開された後、新しいメソッドがインターフェイスに追加された場合、このインターフェイスを実装したアプリケーションは、新しいバージョンのインターフェイスを使用するとクラッシュする危険がありました。
Java 8 ではそのような危険はないのでしょうか?答えはノーです。
デフォルトのメソッドをインターフェースに追加すると、一部の実装クラスが使用できなくなる場合があります。
まず、デフォルトのメソッドの詳細を見てみましょう。
Java 8 では、インターフェイス内のメソッドを実装できます (Java 8 の静的メソッドもインターフェイス内に実装できますが、それは別のトピックです)。インターフェイスに実装されたメソッドはデフォルト メソッドと呼ばれ、修飾子としてキーワード default で識別されます。クラスがインターフェイスを実装する場合、そのインターフェイスにすでに実装されているメソッドを実装できますが、これは必須ではありません。このクラスはデフォルトのメソッドを継承します。これが、インターフェースが変更された場合に実装クラスを変更する必要がない理由です。
多重継承の場合はどうなるでしょうか?
クラスが複数 (2 つなど) のインターフェイスを実装し、これらのインターフェイスが同じデフォルト メソッドを持つ場合、状況は非常に複雑になります。クラスはどのデフォルト メソッドを継承しますか?どちらでもない!この場合、クラス自体 (直接、または継承ツリーの上位のクラス) がデフォルト メソッドを実装する必要があります。
あるインターフェイスがデフォルト メソッドを実装し、別のインターフェイスがデフォルト メソッドを抽象として宣言する場合も同様です。 Java 8 は曖昧さを避け、厳密さを維持しようとします。メソッドが複数のインターフェイスで宣言されている場合、デフォルトの実装は継承されず、コンパイル時エラーが発生します。
ただし、クラスをコンパイルした場合、コンパイル時エラーは発生しません。この時点で、Java 8 には一貫性がありません。これには独自の理由があり、ここでは詳細に説明したり、深く議論したくありません (理由: バージョンがリリースされており、議論の時間が長すぎるため、このプラットフォームにはこれまでにないものがあります)。そのような議論)。
1. 2 つのインターフェイスと 1 つの実装クラスがあるとします。
2. インターフェースの 1 つはデフォルトのメソッド m() を実装します。
3. インターフェースと実装クラスを一緒にコンパイルします。
4. m() メソッドを含まないインターフェイスを変更し、m() メソッドを抽象として宣言します。
5. 変更したインターフェースを個別に再コンパイルします。
6. 実装クラスを実行します。
1. 抽象メソッド m() を含むインターフェイスを変更し、デフォルトの実装を作成します。
2. 変更したインターフェースをコンパイルします。
3. クラスの実行: 失敗しました。
2 つのインターフェイスが同じメソッドのデフォルト実装を提供する場合、実装クラスもデフォルト メソッドを (直接または継承ツリー内の上位クラスによって) 実装しない限り、このメソッドを呼び出すことはできません。
コード例:
上記の例を示すために、C.java のテスト ディレクトリを作成しました。その下には、I1.java と I2.java を保存するための 3 つのサブディレクトリがあります。テスト ディレクトリには、クラス C のソース コード C.java が含まれています。ベース ディレクトリには、コンパイルして実行できるインターフェイスのバージョンが含まれています。 I1 にはデフォルト実装の m() メソッドが含まれていますが、I2 にはメソッドが含まれていません。
実装クラスには main メソッドが含まれているため、テストで実行できます。コマンドラインパラメータがあるかどうかをチェックするので、m() を呼び出すテストと m() を呼び出さないテストを簡単に実行できます。
次のようにコードをコピーします。
~/github/test$ cat C.java
パブリック クラス C は I1、I2 を実装します {
public static void main(String[] args) {
C c = 新しい C();
if(args.length == 0){
cm();
}
}
}
~/github/test$catbase/I1.java
パブリックインターフェイス I1 {
デフォルトの void m(){
System.out.println("hello インターフェイス 1");
}
}
~/github/test$catbase/I2.java
パブリックインターフェイス I2 {
}
次のコマンド ラインを使用してコンパイルして実行します。
次のようにコードをコピーします:~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
こんにちはインターフェイス 1
互換ディレクトリには、抽象メソッド m() を備えた I2 インターフェイスと、変更されていない I1 インターフェイスが含まれています。
次のようにコードをコピーします:~/github/test$ cat pretty/I2.java
パブリックインターフェイス I2 {
void m();
}
これをクラス C のコンパイルに使用することはできません。
次のコードをコピーします:~/github/test$ javac -cp .:compatibility C.java
C.java:1: エラー: C は抽象ではないため、I2 の抽象メソッド m() をオーバーライドしません
パブリック クラス C は I1、I2 を実装します {
^
1 エラー
エラーメッセージは非常に正確です。前回のコンパイルで取得した C.class があるため、互換性のあるディレクトリでインターフェイスをコンパイルすると、実装クラスを実行できる 2 つのインターフェイスが引き続き取得されます。
次のようにコードをコピーします。
~/github/test$ javac 互換/I*.java
~/github/test$ java -cp .:compatibility C
こんにちはインターフェイス 1
間違っているという 3 番目のディレクトリには、m() メソッドも定義する I2 インターフェイスが含まれています。
次のようにコードをコピーします。
~/github/test$猫が間違っています/I2.java
パブリックインターフェイス I2 {
デフォルトの void m(){
System.out.println("hello インターフェイス 2");
}
}
わざわざコンパイルする必要があります。 m() メソッドは 2 回定義されていますが、定義されたメソッドを複数回呼び出さない限り実装クラスは実行できます。ただし、m() メソッドを呼び出すとすぐに失敗します。使用するコマンドラインパラメータは次のとおりです。
次のようにコードをコピーします。
~/github/test$ javac が間違っています/*.java
~/github/test$ java -cp .:間違った C
スレッド「メイン」java.lang.IncompatibilityClassChangeError での例外: 競合しています
デフォルトのメソッド: I1.m I2.m
Cm(C.java)で
C.main(C.java:5) で
~/github/test$ java -cp .:間違った C x
~/github/test$
結論は
デフォルトの実装をインターフェースに追加するクラス ライブラリを Java 8 環境に移植する場合、通常は問題はありません。少なくとも、Java8 クラス ライブラリ開発者がコレクション クラスにデフォルト メソッドを追加したときにそう考えました。ライブラリを使用するアプリケーションは、引き続きデフォルトのメソッドを持たない Java 7 ライブラリに依存します。複数の異なるクラス ライブラリを使用および変更する場合、競合が発生する可能性がわずかにあります。どうすればこれを回避できるでしょうか?
以前と同様にクラス ライブラリを設計します。デフォルトの方法に依存する場合は、軽視しないでください。最後の手段としては使用しないでください。他のインターフェイスとの競合を避けるために、メソッド名を賢明に選択してください。 Java プログラミングでの開発にこの機能を使用する方法を学びます。