フィールドを抽象化しないのはなぜですか?


99

抽象メソッドを持つことができるように、Javaクラスが抽象フィールドを持つことができないのはなぜですか?

例:同じ抽象基本クラスを拡張する2つのクラスがあります。これら2つのクラスには、エラーメッセージであるString定数を除いて、同じメソッドが含まれています。フィールドが抽象的である場合、この定数を抽象化し、メソッドを基本クラスに引き上げることができます。代わりに、getErrMsg()この場合はStringを返す抽象メソッドを作成し、2つの派生クラスでこのメソッドをオーバーライドして、メソッド(現在は抽象メソッドを呼び出す)をプルアップする必要があります。

そもそもなぜフィールドを抽象化できないのでしょうか。Javaはこれを可能にするように設計されているのでしょうか?


3
フィールドを非定数にして、コンストラクタを介して値を提供するだけで、この問題全体を回避し、2つのクラスではなく1つのクラスの2つのインスタンスを作成することもできたようです。
2010

5
スーパークラスでフィールドを抽象化することにより、すべてのサブクラスがこのフィールドを持たなければならないことを特定できます。したがって、これは非抽象フィールドと同じです。
Peter Lawrey、2010

@ピーター、私はあなたのポイントに従っているかどうかわかりません。抽象クラスで非抽象定数が指定された場合、その値はすべてのサブクラスでも一定です。抽象的である場合、その値は各サブクラスによって実装/提供される必要があります。したがって、まったく同じではありません。
liltitus27 2013年

1
@ liltitus27 3.5年前の私のポイントは、抽象フィールドを持つことは、インターフェースのユーザーを実装から分離するという考え全体を壊すことを除いて、あまり変化しないということだったと思います。
Peter Lawrey

それは子クラスでカスタムフィールドの注釈を可能性がありますので、これは参考になる
GEG

回答:


102

コンストラクター(テストされていないコード)で初期化された抽象クラスの最終フィールドを持つことで、説明したことを実行できます。

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

子クラスがスーパーコンストラクターを通じてfinalを初期化することを「忘れた」場合、コンパイラーは、抽象メソッドが実装されていない場合と同様に、エラーを警告します。


ここではそれを試してエディタを得たが、していない、これは私も投稿しようとしていたものだった...
ティム・

6
「コンパイラーは警告を出します」。実際、Childコンストラクターは、存在しないnoargsコンストラクターを使用しようとしますが、これはコンパイルエラーです(警告ではありません)。
スティーブンC

また、@ Stephen Cのコメントに「抽象メソッドが実装されていないときのように」追加することも、警告ではなくエラーです。
Laurence Gonsalves、2010

4
良い答え-これは私のために働いた。投稿されてからしばらく経っていますBaseが、宣言する必要があると思いますabstract
Steve Chambers

2
コンストラクターに引数を追加するため、このアプローチは(唯一の方法ですが)まだ嫌いです。ネットワークプログラミングでは、メッセージタイプを作成するのが好きです。新しいメッセージはそれぞれ抽象タイプを実装しますが、AcceptMessageには「A」、LoginMessageには「L」のような一意の指定文字が必要です。これらにより、カスタムメッセージプロトコルがこの特徴的な文字をネットワークに送信できるようになります。これらはクラスに対して暗黙的であるため、コンストラクタに渡されるものではなく、フィールドとして提供するのが最適です。
アダムヒューズ

7

その意味は分からない。関数を抽象クラスに移動して、保護されたフィールドをオーバーライドするだけです。これが定数で機能するかどうかはわかりませんが、効果は同じです。

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

あなたのポイントはerrorMsg、サブクラスでの実装/オーバーライド/何でも強制したいということですか?メソッドを基本クラスに含めたいだけだと思っていましたが、そのときフィールドを処理する方法がわかりませんでした。


4
もちろん、私はそれを行うことができました。そして私はそれを考えていました。しかし、すべての派生クラスでその値をオーバーライドすることがわかっているのに、なぜベースクラスに値を設定する必要があるのですか。あなたの議論は、抽象メソッドを許可することにも価値がないと主張するためにも使用できます。
Paul Reiners、2010

1
この方法では、すべてのサブクラス値をオーバーライドする必要があるわけではありません。またprotected String errorMsg;、サブクラスに値を設定することを強制する方法を定義することもできます。これは、実際にすべてのメソッドをプレースホルダーとして実装する抽象クラスに似ているため、開発者はすべてのメソッドを実装する必要はありませんが、必要はありません。
Felix Kling

7
言ってprotected String errorMsg;ないではないあなたはサブクラスで値を設定することを強制します。
Laurence Gonsalves、2010

1
設定しない場合に値をnullにすると「強制」としてカウントされる場合、非強制とは何と呼ばれますか?
Laurence Gonsalves、2010

9
@felix、施行契約には違いがあります。この投稿全体は、抽象クラスを中心としています。抽象クラスは、サブクラスが実行する必要があるコントラクトを表します。したがって、抽象フィールドを持つことについて話すとき、サブクラス契約ごとにそのフィールドの値を実装する必要があると言っています。あなたのアプローチはいかなる価値も保証せず、契約のように期待されていることを明示しません。非常に異なります
liltitus27 2013年

3

当然、これを可能にするように設計することもできますが、内部的には動的なディスパッチ、つまりメソッドの呼び出しを行う必要があります。Javaの設計(少なくとも初期の段階)は、ある程度、最小限にしようとする試みでした。つまり、設計者は、既に言語で使用されている他の機能によって簡単にシミュレートできる場合は、新しい機能を追加しないようにしました。


@Laurence- 抽象フィールドが含まれていた可能性があることは私に明らかではありません。そのようなことは、型システムと言語のユーザビリティに深く根本的な影響を与える可能性があります。(多重継承が深刻な問題を引き起こすのと同じように...それは不注意なC ++プログラマーを悩ますことができます。)
Stephen C

0

タイトルを読んで、抽象的なインスタンスメンバーについて言及していると思いました。そして私はそれらの多くの使用を見ることができませんでした。しかし、抽象静的メンバーはまったく別の問題です。

私はJavaで次のようなメソッドを宣言できることを望んでいました。

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

基本的に、私の親クラスの具体的な実装は、静的なファクトリメソッドに特定のシグネチャを提供することを主張します。これにより、具体的なクラスへの参照を取得Class.forName()でき、自分が選択した規則でそれを構築できることが確実になります。


1
ええと...これはおそらく、反射をはるかに多く使用していることを意味します!!
スティーブンC

私には奇妙なプログラミング習慣のように聞こえます。Class.forName(..)は、設計上の欠陥のようなにおいがします。
ウイスキーシエラ2010

最近では、基本的に常にこのようなことを実現するためにSpring DIを使用しています。しかし、そのフレームワークが知られ人気になる前に、プロパティまたはXMLファイルで実装クラスを指定してから、Class.forName()を使用してそれらをロードします。
Drew Wills、

それらのユースケースを紹介します。抽象ジェネリッククラスでジェネリック型のフィールドを宣言することはほぼ不可能である「抽象フィールド」: @autowired T fieldName。場合Tのようなものとして定義されT extends AbstractController、型消去は、フィールドの型として生成されますになりAbstractController、それがないコンクリートとユニークではありませんので、これはautowiredすることはできません。私は一般的にそれらの使用があまりないので、言語に属していないことに同意します。私が同意しないのは、それらを使用する必要がないということです。
DaBlick

0

別のオプションは、基本クラスでフィールドをパブリック(必要に応じて最終)として定義し、現在使用されているサブクラスに応じて、基本クラスのコンストラクターでそのフィールドを初期化することです。循環依存関係を導入するという点で、少し怪しいです。ただし、少なくともこれは変更できる依存関係ではありません。つまり、サブクラスは存在するか存在しないかのどちらかですが、サブクラスのメソッドまたはフィールドはの値に影響を与えることができませんfield

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.