web-dev-qa-db-ja.com

抽象クラスのすべてのサブクラスでコンストラクターを強制的に定義するにはどうすればよいですか

抽象メソッドを定義する抽象クラスAがあります。つまり、クラスをインスタンス化するには、すべての抽象メソッドを実装する必要があります。

すべてのサブクラスに、パラメーターとして2つのintを持つコンストラクターを実装してほしい。

コンストラクタを宣言すると、サブクラスで定義されたコンストラクタが必要になり、実装について何も知りませんので、私の目的を無効にします。さらに、コンストラクターを抽象として宣言することはできません。

これを行う方法はありますか?

私が欲しいものの例:

MatrixクラスのAPIを定義しているとしましょう。私の問題では、Matrixはディメンションを変更できません。

マトリックスを作成するには、そのサイズを指定する必要があります。

そのため、すべての実装者がコンストラクターにパラメーターとしてサイズを提供する必要があります。このコンストラクタは、実装の問題ではなく、問題によって動機付けられます。メソッドのすべてのセマンティックが保持されている場合、実装はこれらを使用して必要な処理を実行できます。

抽象クラスでinvert()メソッドの基本的な実装を提供したいとしましょう。このメソッドは、this反転次元を持つ新しいマトリックスを作成します。より具体的には、抽象クラスで定義されているため、2つの整数を取るコンストラクターを使用して、thisと同じクラスの新しいインスタンスを作成します。インスタンスを知らないので、リフレクション(getDefinedConstructor)を使用します。それを取得し、実装にとって意味があることを保証する方法が必要です。

64
dodecaplex

サブクラスでコンストラクターの特定のシグネチャを強制することはできませんが、canを使用して、抽象クラスのコンストラクターに2つの整数を取得させます。サブクラスcouldパラメーターなしのコンストラクターからそのコンストラクターを呼び出し、たとえば定数を渡します。それはあなたが来ることができる最も近いです。

さらに、あなたが言うように、あなたは実装について何も知らない-だから、2つの整数を必要とするコンストラクタを持つことが適切であるとどうやって知っているのか?それらのいずれかが文字列も必要とする場合はどうなりますか?または、それらの整数の1つに定数を使用することは理にかなっています。

ここでの全体像は何ですか-whyサブクラスで特定のコンストラクター署名を強制したいですか? (私が言うように、あなたは実際にdoすることはできませんが、なぜそれが必要なのかを説明すると、解決策が現れるかもしれません。)

1つのオプションは、ファクトリー用に別個のインターフェースを持つことです:

interface MyClassFactory
{
    MyClass newInstance(int x, int y);
}

次に、MyClassの各具象サブクラスにも、2つの整数を指定してインスタンスを構築する方法を知っているファクトリーが必要です。しかし、それほど便利ではありません-そして、あなたはまだ工場自体のインスタンスを構築する必要があります。繰り返しますが、ここでの実際の状況は何ですか?

53
Jon Skeet

以下のようなものを試すことができます。実装するクラスに適切な引数を持つコンストラクターがない場合、コンストラクターは例外をスローします。

これはばかげています。 [OK]と[悪い]を比較します。 OKは要件を満たし、ランタイムチェックに合格することを除いて、両方のクラスは同じです。したがって、要件を強制すると、逆効果の忙しい作業が促進されます。

より良い解決策は、何らかのファクトリです。

abstract class RequiresConstructor
{
    RequiresConstructor( int x, int y ) throws NoSuchMethodException
    {
    super();
    System.out.println( this.getClass().getName() ) ;
    this.getClass(). getConstructor ( int.class , int.class ) ;
    }

    public static void main( String[] args ) throws NoSuchMethodException
    {
    Good good = new Good ( 0, 0 );
    OK ok = new OK ();
    Bad bad = new Bad ();
    }
}

class Good extends RequiresConstructor
{
    public Good( int x, int y ) throws NoSuchMethodException
    {
    super( x, y ) ;
    }
}

class OK extends RequiresConstructor
{
    public OK( int x, int y ) throws NoSuchMethodException
    {
    super( x, y ) ;
    throw new NoSuchMethodException() ;
    }

    public OK() throws NoSuchMethodException
    {
    super( 0, 0 ) ;
    }
}

class Bad extends RequiresConstructor
{
    public Bad() throws NoSuchMethodException
    {
    super( 0, 0 ) ;
    }
}
6
emory

実装クラスが使用する内部表現をインターフェイスで定義する必要がある場合、それは間違っています。 カプセル化 および データの抽象化 について読んでください。

抽象実装が特定の実装の詳細に依存している場合、それらはその抽象クラスに属します。つまり、抽象クラスは、抽象メソッドを機能させるために必要な内部状態を初期化できるコンストラクターを定義する必要があります。

一般に、コンストラクターは、そのオブジェクトインスタンスの初期状態の詳細を提供することにより、クラスのインスタンスを作成することを目的としています。これは、構築されているインスタンスが、個々の引数への参照をコピーする必要があることを意味するものではありません。これは、ほとんどのソフトウェアでよく見られます。したがって、Javaがサブクラスで特定のConstructorシグネチャの実装を強制するための構造を提供したとしても、それらのサブクラスは引数を簡単に破棄できます。

3
Tim Bender

少し遅れましたが...

常にスーパーコンストラクターとして呼び出されるデフォルトのコンストラクターをクラスに作成するだけです。このデフォルトのコンストラクターでは、定義されたすべてのコンストラクターを、自身のクラスオブジェクト(抽象スーパークラスではなく、具象サブクラス)に反映してチェックできます。実装するコンストラクターが欠落している場合、ランタイム例外をスローします。

それは裏口からハッキングする味があるので、私は反省の偉大な友人ではありませんが、時にはそれが役立ちます...

この例を見てください:

import Java.lang.reflect.Constructor;

public abstract class Gaga {
  public Gaga() {
    boolean found = false;
    try {
      Constructor<?>[] constructors = getClass().getConstructors();
      for (Constructor<?> c : constructors) {
        if (c.getParameterTypes().length==2) {
          Class<?> class0 = c.getParameterTypes()[0];
          Class<?> class1 = c.getParameterTypes()[1];
          if ( (class0.getName().equals("int") || class0.isAssignableFrom(Integer.class))
              &&  (class1.getName().equals("int") || class1.isAssignableFrom(Integer.class)) )
            found = true;
        }
      }
    } catch (SecurityException e)
    {
      found = false;
    }

    if (!found)
      throw new RuntimeException("Each subclass of Gaga has to implement a constructor with two integers as parameter.");

    //...
  }

}

そしてテストクラス:

public class Test {
  private class Gaga1 extends Gaga {
    public Gaga1() { this(0, 0); }
    public Gaga1(int x, Integer y) { }
  }

  private class Gaga2 extends Gaga {

  }

  public static void main(String[] args)
  {
    new Gaga1();
    new Gaga1(1, 5);
    new Gaga2();
    System.exit(0);
  }
}

メイン関数ではGaga1のオブジェクトが作成されますが、Gaga2の作成ではランタイム例外がスローされます。

しかし、このコンストラクターが呼び出されていることを確認することはできません-必要なことを実行していることを確認することさえできません。

このテストは、リフレクションを使用している場合にのみ役立ちます。

3
Abdullah