インターフェイスのコンストラクタ?


148

インターフェイスでコンストラクタを定義することは不可能であることを知っています。しかし、それは非常に役立つと思うので、なぜだろうと思います。

したがって、クラスの一部のフィールドがこのインターフェースのすべての実装に対して定義されていることを確認できます。

たとえば、次のメッセージクラスについて考えます。

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)私がこのようなメソッドを使用する場合、このメソッドが本当に呼び出されるかどうか確信が持てません。コンストラクタでは、それを確認できました。



2
あなたは、「コンストラクターで、[このクラスのすべての実装に実際にレシーバーセットがある]ことを確認できます」と言います。しかし、いいえ、あなたはおそらくそれを行うことができませんでした。このようなコンストラクターを定義できれば、パラメーターは実装者にとって強力なヒントになるだけですが、必要に応じて単に無視することもできます。
Julien Silland

3
@mattbうーん、それは別の言語です。
yesennes 2016年

回答:


129

あなたが説明したことのいくつかを取る:

「そのため、クラスの一部のフィールドが、このインターフェースのすべての実装に対して定義されていることを確認できます。」

「メッセージインターフェースを実装するクラスを増やすために、このクラスのインターフェースを定義する場合、送信メソッドのみを定義でき、コンストラクタは定義できません。」

...これらの要件は、抽象クラスの目的とまったく同じです。


ただし、@ Sebiが説明する(親コンストラクターからオーバーロードされたメソッドを呼び出す)ユースケースは、私の回答で説明されているように悪い考えです。
rsp

44
マット、確かにそうですが、抽象クラスには単一継承の制限があるため、階層を指定する他の方法を検討するようになります。
CPerkins

6
これは真実であり、セビの当面の問題を解決するかもしれません。しかし、Javaでインターフェースを使用する理由の1つは、多重継承ができないためです。他のものから継承する必要があるために自分の「もの」を抽象クラスにすることができない場合、問題は残ります。私が解決策を持っていると主張しているわけではありません。
ジェイ

7
@CPerkinsはこれに該当しますが、抽象クラスを使用するだけでSebiのユースケースを解決できるとは示唆していません。どちらかといえばMessagesend()メソッドを定義するインターフェースを宣言することが最善であり、セビがMessageインターフェースの実装のための「ベース」クラスを提供したい場合はAbstractMessage、同様に提供します。抽象クラスはインターフェースの代わりになるべきではなく、そのように提案することは決してありませんでした。
matt b

2
わかった、マット。私はあなたと議論していませんでした、それはオペレーションが望むものの完全な置き換えではないことをさらに指摘しました。
CPerkins

76

インターフェースでコンストラクターを許可するときに発生する問題は、同時に複数のインターフェースを実装する可能性に起因します。クラスが異なるコンストラクターを定義する複数のインターフェースを実装する場合、クラスは複数のコンストラクターを実装する必要があります。各コンストラクターは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); 
  }
}

1
言語は次のようなことを許可することでそれを実行できませんでしたclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako

1
「インターフェース内のコンストラクター」の最も有用な意味は、許可されている場合、new Set<Fnord>()「私が何かとして使えるものをくれ」を意味するように解釈できるかどうかSet<Fnord>です。の作成者が、特に何かを必要としないもののSet<T>頼りHashSet<T>になる実装になることを意図している場合、インターフェースはと同じnew Set<Fnord>()意味であると定義できますnew HashSet<Fnord>()。クラスが複数のインターフェースを実装する場合、インターフェースで指定されnew InterfaceName()たクラスを単に構築するだけなので、問題は発生しません。
スーパーキャット2014

反論:あなたのA(String,List)コンストラクタは指定されたコンストラクタでありA(String)A(List)それを呼び出す二次的なコンストラクタである可能性があります。あなたのコードは反例ではなく、単に悪い例です。
Ben Leggiero、2018

実装ですべてのコンストラクターを呼び出すのはなぜですか?はい、1つがStringでもう1つがIntであるctorを使用してより多くのInterfacesを実装する場合、2つのctorが必要ですが、またはのいずれかを使用できます。それが当てはまらない場合、クラスは単に両方のインターフェイスを実装しません。だから何!?(ただし、インターフェイスにctorがない理由は他にもあります)。
kai

@kaiいいえ、インスタンスを作成するときに両方のインターフェイスコンストラクターを呼び出す必要があります。つまり、私の例では、インスタンスには名前とリストの両方があるため、各インスタンスは名前とリストの両方をインスタンス化する必要があります。
ダニエル・カルマン

13

インターフェイスは、APIのコントラクトを定義します。これは、APIの実装者とユーザーの両方が同意する一連のメソッドです。インターフェイスにはインスタンス化された実装がないため、コンストラクタはありません。

あなたが説明するユースケースは、コンストラクターが子クラスに実装されている抽象メソッドのメソッドを呼び出す抽象クラスに似ています。

ここでの固有の問題は、ベースコンストラクターが実行されている間、子オブジェクトがまだ構築されていないため、予測できない状態にあることです。

要約すると:あなたが親コンストラクタからオーバーロードされたメソッドを呼び出すときに引用する、トラブルを求めているmindprodを

一般に、コンストラクターでfinal以外のメソッドを呼び出さないようにする必要があります。問題は、派生クラスのインスタンス初期化子/変数の初期化が、基本クラスのコンストラクターのに実行されること です。


6

あなたが試すことができる回避策はgetInstance()、インターフェイスでメソッドを定義することです。これにより、実装者は、処理する必要のあるパラメーターを認識できます。抽象クラスほど堅固ではありませんが、インターフェースであるほど柔軟性が高くなります。

ただし、この回避策では、を使用しgetInstance()て、このインターフェイスのすべてのオブジェクトをインスタンス化する必要があります。

例えば

public interface Module {
    Module getInstance(Receiver receiver);
}

5

サブクラスでのオブジェクト作成中に初期化する必要のないインターフェースには静的フィールドのみがあり、インターフェースのメソッドはサブクラスで実際の実装を提供する必要があるため、インターフェースにコンストラクターは必要ありません。

2番目の理由は、サブクラスのオブジェクト作成中に親コンストラクターが呼び出されます。ただし、複数のインターフェースが実装されている場合、インターフェースコンストラクターの呼び出し中に、どのインターフェースのコンストラクターが最初に呼び出すかに関して競合が発生します。


3

インターフェースのすべての実装に特定のフィールドが含まれていることを確認する場合は、そのフィールドのゲッターをインターフェースに追加するだけです

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • それはカプセル化を壊しません
  • Receiverオブジェクトを何らかの方法で(コンストラクターまたはセッターによって)クラスに渡す必要があることを、インターフェースを使用するすべての人に知らせます

2

インターフェースメソッドで参照されない依存関係は、インターフェースが実施するものではなく、実装の詳細と見なされます。もちろん例外もあり得ますが、原則として、予想される動作としてインターフェースを定義する必要があります。特定の実装の内部状態は、インターフェースの設計上の問題であってはなりません。


1

理由については、この質問を参照してください(コメントから取得)。

本当にこのようなことをする必要がある場合は、インターフェースではなく抽象基本クラスが必要になる場合があります。


1

これは、インターフェイスではメソッド本体を定義できないためです。ただし、インターフェイスが定義するすべてのメソッドのデフォルトの抽象修飾子と同じように、コンストラクターを同じクラスで定義する必要があります。そのため、インターフェイスでコンストラクタを定義できません。


0

これは、このテクニックを使用した例です。この特定の例では、コードは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);
}

どのようにしてprivateアクセス修飾子を使用できますinterfaceか?
Rudra

0

一般に、コンストラクターは、オブジェクトに関して特定のクラスの非静的メンバーを初期化するためのものです。

宣言されたメソッドのみが存在し、定義されたメソッドは存在しないため、インターフェースのオブジェクト作成はありません。宣言されたメソッドに対してオブジェクトを作成できないのは、is-objectの作成が、静的でないメンバーに(ヒープメモリ内の)メモリを割り当てることだけです。

JVMは、完全に開発されてすぐに使用できるメンバーのメモリーを作成します。これらのメンバーに基づいて、JVMはメンバーに必要なメモリーの量を計算し、メモリーを作成します。

宣言されたメソッドの場合、実装は将来行われるため、JVMはこれらの宣言されたメソッドに必要なメモリ量を計算できません。したがって、オブジェクトの作成はインターフェースでは不可能です。

結論:

オブジェクトを作成しないと、コンストラクターを介して非静的メンバーを初期化することはできません。そのため、インターフェース内でコンストラクターを使用できません(インターフェース内でコンストラクターを使用しないため)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.