「定数」を共有するためのJavaの静的フィールドとのインターフェース


116

私はJavaにアクセスするためにいくつかのオープンソースのJavaプロジェクトを調べています。それらのプロジェクトの多くはある種の「定数」インターフェースを持っていることに気づきます。

たとえば、processing.orgにPConstants.javaというインターフェースがあり、他のほとんどのコアクラスがこのインターフェースを実装しています。インターフェースは静的メンバーでなぞられています。このアプローチには理由がありますか、それとも悪い習慣と見なされていますか?意味のある列挙型、または静的クラスを使用しないのはなぜですか?

インターフェイスを使用して、ある種の疑似「グローバル変数」を許可するのは奇妙です。

public interface PConstants {

  // LOTS OF static fields...

  static public final int SHINE = 31;

  // emissive (by default kept black)
  static public final int ER = 32;
  static public final int EG = 33;
  static public final int EB = 34;

  // has this vertex been lit yet
  static public final int BEEN_LIT = 35;

  static public final int VERTEX_FIELD_COUNT = 36;


  // renderers known to processing.core

  static final String P2D    = "processing.core.PGraphics2D";
  static final String P3D    = "processing.core.PGraphics3D";
  static final String JAVA2D = "processing.core.PGraphicsJava2D";
  static final String OPENGL = "processing.opengl.PGraphicsOpenGL";
  static final String PDF    = "processing.pdf.PGraphicsPDF";
  static final String DXF    = "processing.dxf.RawDXF";


  // platform IDs for PApplet.platform

  static final int OTHER   = 0;
  static final int WINDOWS = 1;
  static final int MACOSX  = 2;
  static final int LINUX   = 3;

  static final String[] platformNames = {
    "other", "windows", "macosx", "linux"
  };

  // and on and on

}

15
注:static final必須ではありません。インターフェースにとっては冗長です。
ThomasW 2016

また、それは注意するplatformNamesことがありpublicstaticそしてfinal、それは間違いなく一定ではありません。唯一の定数配列は、長さがゼロの配列です。
Vlasec

@ThomasW私はこれが数年前のものであることを知っていますが、コメントでエラーを指摘する必要がありました。static final必ずしも冗長ではありません。finalキーワードのみのクラスまたはインターフェースフィールドは、クラスまたはインターフェースのオブジェクトを作成するときに、そのフィールドの個別のインスタンスを作成します。を使用static finalすると、各オブジェクトがそのフィールドのメモリ位置を共有します。言い換えると、クラスMyClassにフィールドがあったfinal String str = "Hello";場合、MyClassのN個のインスタンスに対して、フィールドstrのN個のインスタンスがメモリに存在します。staticキーワードを追加すると、インスタンスは1つだけになります。
Sintrias

回答:


160

これは一般的に悪い習慣と考えられています。問題は、定数が実装クラスのパブリック「インターフェース」の一部であるということです(わかりやすくするため)。これは、これらの値が内部でのみ必要な場合でも、実装クラスがこれらすべての値を外部クラスに公開していることを意味します。定数はコード全体で増殖します。例としては、Swing のSwingConstantsインターフェースがあります。これは、すべての定数(使用しないものも含む)をすべて独自に「再エクスポート」する数十のクラスによって実装されます。

しかし、私の言葉を信じるだけでなく、Josh Blochはそれ悪いと言います:

一定のインターフェイスパターンは、インターフェイスの不適切な使用です。クラスがいくつかの定数を内部的に使用することは、実装の詳細です。定数インターフェースを実装すると、この実装の詳細がクラスのエクスポートされたAPIにリークします。クラスが定数インターフェースを実装することは、クラスのユーザーにとって重要ではありません。実際には、それらを混乱させることさえあります。さらに悪いことに、これはコミットメントを表しています。将来のリリースでクラスが変更され、定数を使用する必要がなくなった場合でも、バイナリ互換性を確保するためにインターフェイスを実装する必要があります。非最終クラスが定数インターフェースを実装する場合、そのすべてのサブクラスは、インターフェースの定数によって汚染された名前空間を持ちます。

列挙型の方が良い方法かもしれません。または、インスタンス化できないクラスのパブリック静的フィールドとして定数を単に置くこともできます。これにより、別のクラスが独自のAPIを汚染することなくそれらにアクセスできます。


8
列挙型はここでの赤いニシンです-または少なくとも別の質問。列挙型はもちろん使用する必要がありますが、実装者が必要としない場合は非表示にする必要もあります。
DJClayworth 2008年

12
ところで、インスタンスのない列挙型は、インスタンス化できないクラスとして使用できます。;)
Peter Lawrey 2010

5
しかし、なぜこれらのインターフェースを最初に実装するのでしょうか。それらを定数リポジトリとして使用しないのはなぜですか?グローバルに共有される定数の種類が必要な場合、それを行うための「よりクリーンな」方法がわかりません。
shadox

2
@DanDyerええ、しかしインターフェースはいくつかの宣言を暗黙的にします。public static finalのように、これは単なるデフォルトです。なぜクラスに悩むのですか?Enum-まあ、それは異なります。列挙型は、異なるエンティティの値のコレクションではなく、エンティティの可能な値のコレクションを定義する必要があります。
shadox

4
個人的にはジョシュが間違ってボールを打っているように感じます。定数をリークさせたくない場合は、どのタイプのオブジェクトに定数を置いても、それらがエクスポートされたコードの一部ではないことを確認する必要があります。インターフェースまたはクラス。どちらもエクスポートできます。したがって、正しい質問は次のとおりではありません。どのタイプのオブジェクトにオブジェクトを配置するかではなく、このオブジェクトをどのように整理するかです。また、エクスポートされたコードで定数が使用されている場合でも、エクスポート後に定数が使用可能であることを確認する必要があります。したがって、「悪い習慣」の主張は私の控えめな意見では無効です。
ローレンス

99

Java 1.5以降では、「定数インターフェース」を実装する代わりに、静的インポートを使用して、別のクラス/インターフェースから定数/静的メソッドをインポートできます。

import static com.kittens.kittenpolisher.KittenConstants.*;

これにより、機能を持たないインターフェースをクラスに実装させる醜さを回避できます。

定数を格納するだけのクラスを作成することについては、時々必要になると思います。クラスには自然な場所がない定数もあるので、「中立」な場所に置くことをお勧めします。

ただし、インターフェイスを使用する代わりに、プライベートコンストラクターを持つ最終クラスを使用します。(クラスをインスタンス化またはサブクラス化することを不可能にし、静的でない機能/データが含まれていないという強力なメッセージを送信します。)

例えば:

/** Set of constants needed for Kitten Polisher. */
public final class KittenConstants
{
    private KittenConstants() {}

    public static final String KITTEN_SOUND = "meow";
    public static final double KITTEN_CUTENESS_FACTOR = 1;
}

つまり、静的インポートのため、インターフェースの代わりにクラスを使用して、以前と同じエラーを再実行する必要があることを説明しています。ばかげている!
ギズモ

11
いいえ、それは私が言っていることではありません。私は2つの独立したことを言っています。1:継承を悪用する代わりに静的インポートを使用します。2:定数リポジトリが必要な場合は、インターフェースではなく最終クラスにします。
ザルコネン2008年

継承の一部として設計されていない「定数インターフェース」は決してありません。したがって、静的インポートは構文上の砂糖のためだけのものであり、そのようなインターフェースからの継承は恐ろしいエラーです。Sunがこれを実行したことは知っていますが、他にも多くの基本的なエラーが発生しました。これは、それらを模倣する言い訳にはなりません。
ギズモ

3
質問に対して投稿されたコードの問題の1つは、インターフェースの実装が定数へのアクセスを容易にするためだけに使用されることです。何かがFooInterfaceを実装しているのを見たとき、それはその機能に影響を与えると予想し、上記はこれに違反しています。静的インポートはその問題を修正します。
ザルコネン2008年

2
gizmo-私は静的インポートのファンではありませんが、彼がそこで行っていることは、クラス名、つまりConstClass.SOME_CONSTを使用する必要がないようにしています。静的インポートを実行しても、それらのメンバーがクラスZに追加されるわけではありません。インターフェイスから継承するとは言われていませんが、実際には反対です。
mtruesdell 2008年

8

私は権利が正しいと偽っていませんが、この小さな例を見てみましょう:

public interface CarConstants {

      static final String ENGINE = "mechanical";
      static final String WHEEL  = "round";
      // ...

}

public interface ToyotaCar extends CarConstants //, ICar, ... {
      void produce();
}

public interface FordCar extends CarConstants //, ICar, ... {
      void produce();
}

// and this is implementation #1
public class CamryCar implements ToyotaCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

// and this is implementation #2
public class MustangCar implements FordCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

ToyotaCarはFordCarについて何も知らず、FordCarはToyotaCarについて知りません。原則CarConstantsを変更する必要がありますが...

ホイールは丸くて機械は機械式なので、定数を変更するべきではありませんが...将来的には、トヨタの研究エンジニアが電子エンジンとフラットホイールを発明しました。新しいインターフェースを見てみましょう

public interface InnovativeCarConstants {

          static final String ENGINE = "electronic";
          static final String WHEEL  = "flat";
          // ...
}

そして今、私たちは抽象を変更することができます:

public interface ToyotaCar extends CarConstants

public interface ToyotaCar extends InnovativeCarConstants 

エンジンやホイールのコア値を変更する必要がある場合は、実装に触れずに、抽象レベルでToyotaCarインターフェイスを変更できます。

それは安全ではない、私は知っているが、私はまだあなたがこれについて考えていることを知りたいです


私は今2019年にあなたの考えを知りたいです。私にとって、インターフェースフィールドはいくつかのオブジェクト間で共有されることを意図しています。

:私はあなたの考えに関連した答えを書いたstackoverflow.com/a/55877115/5290519
雨が降る

これは、PMDのルールが非常に制限されている良い例です。官僚機構を通じてより良いコードを得ようとすることは、無駄な試みのままです。
bebbo

6

Javaのこのパターンには多くの嫌悪感があります。ただし、静的定数のインターフェースには値がある場合があります。基本的に次の条件を満たす必要があります。

  1. 概念は、いくつかのクラスのパブリックインターフェイスの一部です。

  2. それらの値は、将来のリリースで変更される可能性があります。

  3. すべての実装が同じ値を使用することが重要です。

たとえば、架空のクエリ言語の拡張機能を記述しているとします。この拡張機能では、インデックスでサポートされているいくつかの新しい操作で言語構文を拡張します。たとえば、地理空間クエリをサポートするRツリーを作成します。

したがって、静的定数を使用してパブリックインターフェイスを記述します。

public interface SyntaxExtensions {
     // query type
     String NEAR_TO_QUERY = "nearTo";

     // params for query
     String POINT = "coordinate";
     String DISTANCE_KM = "distanceInKm";
}

後で、新しい開発者はより良いインデックスを構築する必要があると考え、R *実装を作成します。彼の新しいツリーにこのインターフェイスを実装することにより、さまざまなインデックスがクエリ言語で同じ構文を持つことを保証します。さらに、後で「nearTo」が紛らわしい名前であると判断した場合は、それを「withinDistanceInKm」に変更し、すべてのインデックス実装で新しい構文が尊重されることを確認できます。

PS:この例のインスピレーションは、Neo4j空間コードから得られました。


5

後知恵の利点を考えると、Javaは多くの点で壊れていることがわかります。Javaの大きな欠点の1つは、抽象メソッドと静的なfinalフィールドへのインターフェースの制限です。Scalaのような新しい、より洗練されたオブジェクト指向言語は、アリティゼロ(定数!)を持つ可能性のある具体的なメソッドを含めることができる(通常は含める)トレイトによってインターフェイスを包含します。構成可能な動作の単位としての特性の説明については、http: //scg.unibe.ch/archive/papers/Scha03aTraits.pdfを参照してください。Scalaの特性とJavaのインターフェースとの比較の簡単な説明については、http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5を参照してください。。オブジェクト指向の設計を教えるという文脈では、インターフェースに静的フィールドを含めてはならないという主張のような単純なルールはばかげています。多くのトレイトには当然定数が含まれており、これらの定数はトレイトがサポートするパブリック「インターフェース」の一部です。Javaコードを記述する場合、特性を表現するための簡潔で洗練された方法はありませんが、インターフェース内でstatic finalフィールドを使用することは、多くの場合、適切な回避策の一部です。


12
ひどく大げさで最近は時代遅れです。
Esko、2014

1
すばらしい洞察(+1)、Javaに対しては少し重要すぎるかもしれません。
peterh-モニカを2016年

0

JVM仕様によれば、インターフェースのフィールドとメソッドは、パブリック、スタティック、ファイナル、アブストラクトのみを持つことができます。内部Java VMからの参照

デフォルトでは、インターフェイスのすべてのメソッドは、明示的に言及しなかったとしても抽象的です。

インターフェイスは仕様のみを提供することを目的としています。実装を含めることはできません。したがって、仕様を変更するためのクラスの実装を回避するために、それは最終的なものになります。インターフェイスはインスタンス化できないため、インターフェイス名を使用してフィールドにアクセスするために静的にされます。


0

私はプリロックにコメントをするのに十分な評判がないので、答えを作成する必要があります。申し訳ありませんが、少し頑張られたのでお答えしたいと思います。

プリロック、あなたはこれらの定数がインターフェースから独立し、継承から独立しなければならない理由を示す完璧な例を作成しました。アプリケーションのクライアントにとって、車の実装に技術的な違いがあることは重要ではありません。彼らはクライアントのために同じです、ただ車。そのため、クライアントはI_Somecarのようなインターフェースであるその観点からそれらを確認したいと考えています。アプリケーション全体を通して、クライアントは1つのパースペクティブのみを使用し、異なる自動車ブランドごとに異なるパースペクティブを使用しません。

クライアントが購入する前に車を比較したい場合は、次のような方法があります。

public List<Decision> compareCars(List<I_Somecar> pCars);

インターフェイスは動作に関する規約であり、1つの観点からさまざまなオブジェクトを表示します。あなたがそれを設計する方法は、すべての自動車ブランドが独自の継承系統を持つことになります。実際にはかなり正しいですが、車はまったく異なるタイプのオブジェクトを比較するのと同じように異なる場合があるため、結局、異なる車の間で選択肢があります。そして、それはすべてのブランドが共有しなければならないインターフェースの視点です。定数の選択はこれを不可能にすべきではありません。どうか、ザルコネンの答えを検討してください。


-1

これは、Java 1.5が存在し、列挙型を提供する前の時代のものです。それ以前は、一連の定数または制約付きの値を定義する適切な方法はありませんでした。

これは、多くのプロジェクトで、下位互換性のために、または取り除くために必要なリファクタリングの量のために、ほとんどの場合、まだ使用されています。


2
Java 5より前は、タイプセーフな列挙パターンを使用できました(java.sun.com/developer/Books/shiftintojava/page1.htmlを参照)。
Dan Dyer、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.