インターフェイスでコンストラクターを定義することはできないことを知っています。しかし、私はそれが非常に有用だと思うので、なぜだろうと思っています。
したがって、クラス内の一部のフィールドは、このインターフェイスのすべての実装に対して定義されていることを確認できます。
たとえば、次のメッセージクラスを考えます。
public class MyMessage {
public MyMessage(String receiver) {
this.receiver = receiver;
}
private String receiver;
public void send() {
//some implementation for sending the mssage to the receiver
}
}
このクラスのインターフェイスを定義して、メッセージインターフェイスを実装するクラスを増やすことができる場合、コンストラクタではなくsendメソッドのみを定義できます。それでは、このクラスのすべての実装が実際にレシーバーセットを持っていることをどのように確認できますか? setReceiver(String receiver)
のようなメソッドを使用する場合、このメソッドが実際に呼び出されるかどうかはわかりません。コンストラクターで確認できました。
あなたが説明したことをいくつか取ってください:
「そのため、クラス内の一部のフィールドは、このインターフェースのすべての実装に対して定義されていることを確認できます。」
「メッセージインターフェイスを実装するより多くのクラスを持つことができるようにこのクラスのインターフェイスを定義する場合、sendメソッドのみを定義でき、コンストラクターは定義できない」
...これらの要件は、まさに abstract classes の目的です。
インターフェイスでコンストラクターを許可するときに発生する問題は、同時に複数のインターフェイスを実装する可能性に起因します。クラスが異なるコンストラクターを定義する複数のインターフェースを実装する場合、クラスは複数のコンストラクターを実装する必要があり、各コンストラクターは1つのインターフェースのみを満たしますが、他は満たしません。これらの各コンストラクターを呼び出すオブジェクトを作成することはできません。
またはコード内:
interface Named { Named(String name); }
interface HasList { HasList(List list); }
class A implements Named, HasList {
/** implements Named constructor.
* This constructor should not be used from outside,
* because List parameter is missing
*/
public A(String name) {
...
}
/** implements HasList constructor.
* This constructor should not be used from outside,
* because String parameter is missing
*/
public A(List list) {
...
}
/** This is the constructor that we would actually
* need to satisfy both interfaces at the same time
*/
public A(String name, List list) {
this(name);
// the next line is illegal; you can only call one other super constructor
this(list);
}
}
インターフェイスは、APIのコントラクトを定義します。これは、APIの実装者とユーザーの両方が同意する一連のメソッドです。インターフェイスにはインスタンス化された実装がないため、コンストラクタはありません。
説明するユースケースは、コンストラクターが子クラスに実装されている抽象メソッドのメソッドを呼び出す抽象クラスに似ています。
ここでの固有の問題は、ベースコンストラクターが実行されている間、子オブジェクトがまだ構築されておらず、したがって予測不可能な状態にあることです。
要約すると: mindprod を引用するために、親コンストラクターからオーバーロードされたメソッドを呼び出すときに問題が発生しますか?
一般に、コンストラクターで最終以外のメソッドを呼び出さないようにする必要があります。問題は、派生クラスのインスタンス初期化子/変数の初期化が、基本クラスのコンストラクターの後にafter実行されることです。
インターフェイスには、サブクラスでのオブジェクト作成中に初期化する必要のない静的フィールドのみがあり、インターフェイスのメソッドはサブクラスで実際の実装を提供する必要があります。したがって、インターフェイスにコンストラクタは必要ありません。
2番目の理由-サブクラスのオブジェクト作成中に、親コンストラクターが呼び出されますが、複数のインターフェイスが実装されている場合、インターフェイスコンストラクターの呼び出し中に、どのインターフェイスのコンストラクターが最初に呼び出すかについて競合が発生します
回避策としては、インターフェイスでgetInstance()
メソッドを定義して、実装者が処理する必要のあるパラメーターを認識できるようにします。抽象クラスほど強固ではありませんが、インターフェイスであるほど柔軟性が高まります。
ただし、この回避策では、getInstance()
を使用してこのインターフェイスのすべてのオブジェクトをインスタンス化する必要があります。
例えば。
public interface Module {
Module getInstance(Receiver receiver);
}
インターフェイスメソッドで参照されない依存関係は、インターフェイスが適用するものではなく、実装の詳細と見なされる必要があります。もちろん例外もありますが、原則として、振る舞いが期待されるものとしてインターフェースを定義する必要があります。特定の実装の内部状態は、インターフェースの設計上の問題ではありません。
インターフェイスのすべての実装に特定のフィールドが含まれていることを確認する場合は、単にそのフィールドのゲッターをインターフェイスに追加する必要があります:
interface IMyMessage(){
@NonNull String getReceiver();
}
Receiver
オブジェクトを何らかの方法で(コンストラクターまたはセッターによって)クラスに渡す必要があることを知らせます。why(コメントから引用)については この質問 を参照してください。
本当にこのようなことをする必要がある場合は、インターフェイスではなく抽象基本クラスが必要な場合があります。
これは、インターフェイスではメソッド本体を定義できないためです。ただし、定義するすべてのメソッドに対して、インターフェイスがデフォルトで抽象修飾子を持っているのと同じクラスでコンストラクタを定義する必要があります。これが、インターフェイスでコンストラクターを定義できない理由です。
このテクニックを使用した例を次に示します。この特定の例では、コードは、抽象クラス、コンストラクターとのインターフェースとしてマスクされたインターフェースであるモックMyCompletionListener
を使用してFirebaseを呼び出しています。
private interface Listener {
void onComplete(databaseError, databaseReference);
}
public abstract class MyCompletionListener implements Listener{
String id;
String name;
public MyCompletionListener(String id, String name) {
this.id = id;
this.name = name;
}
}
private void removeUserPresenceOnCurrentItem() {
mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
@Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
}
});
}
}
@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
CompletionListener cListener = new CompletionListener() {
@Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (var1 != null){
System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
var1.onComplete(databaseError, databaseReference);
}
}
};
ref.removeValue(cListener);
}
通常、コンストラクターは、オブジェクトに関して特定のクラスの非静的メンバーを初期化するためのものです。
宣言されたメソッドのみがあり、定義されたメソッドはないため、インターフェイスのオブジェクト作成はありません。宣言されたメソッドに対してオブジェクトを作成できない理由は、オブジェクトの作成は、静的でないメンバーに(ヒープメモリ内の)メモリを割り当てることに他なりません。
JVMは、完全に開発され、すぐに使用できるメンバーのメモリを作成します。これらのメンバーに基づいて、JVMはメンバーに必要なメモリ量を計算し、メモリを作成します。
宣言されたメソッドの場合、実装は将来行われるため、JVMはこれらの宣言されたメソッドに必要なメモリ量を計算できません。そのため、インターフェイスではオブジェクトを作成できません。
結論:
オブジェクトを作成しないと、コンストラクターを介して非静的メンバーを初期化する機会がありません。そのため、コンストラクターはインターフェイス内では許可されません(インターフェイス内ではコンストラクターを使用しないため)