1. 序章

この記事では、一般的なパターンとアンチパターンに焦点を当てて、Javaで定数を使用する方法について学習します。

定数を定義するためのいくつかの基本的な規則から始めます。 そこから、一般的なアンチパターンに移り、一般的なパターンを見ていきます。

2. 基本

定数は、定義された後も値が変更されない変数です。

定数を定義するための基本を見てみましょう。

private static final int OUR_CONSTANT = 1;

ここで説明するパターンのいくつかは、publicまたはprivateアクセス修飾子の決定に対応します。 定数staticfinalを作成し、Javaプリミティブ、クラス、列挙型のいずれであっても、適切な型を指定します。 名前はすべて大文字で、単語をアンダースコアで区切る必要があります。スネークケースの叫びとしても知られています。 最後に、値自体を提供します。

3. アンチパターン

まず、何をしてはいけないかを学ぶことから始めましょう。 Java定数を操作するときに遭遇する可能性のあるいくつかの一般的なアンチパターンを見てみましょう。

3.1. マジックナンバー

マジックナンバーは、コードブロック内の数値リテラルです。

if (number == 3.14159265359) {
    // ...
}

他の開発者が理解するのは難しいです。 さらに、コード全体で数値を使用している場合、値の変更に対処するのは困難です。 代わりに、数値を定数として定義する必要があります。

3.2. 大規模なグローバル定数クラス

プロジェクトを開始するときに、アプリケーションのすべての定数を定義する目的で、ConstantsまたはUtilsという名前のクラスを作成するのが自然なことかもしれません。 小規模なプロジェクトの場合、これは問題ないかもしれませんが、これが理想的なソリューションではない理由をいくつか考えてみましょう。

まず、定数クラスに100以上の定数があると想像してみましょう。 クラスが維持されていない場合、ドキュメントに遅れずについていくためにも、定数を論理グループにリファクタリングするためにも、かなり読みにくくなります。 わずかに異なる名前の重複した定数になってしまう可能性もあります。 このアプローチは、最小のプロジェクト以外では、読みやすさと保守性の問題を引き起こす可能性があります。

Constants クラス自体を維持するためのロジスティクスに加えて、この1つのグローバル定数クラスおよびアプリケーションの他のさまざまな部分との相互依存性を促進することにより、他の保守性の問題も招きます。

より技術的な側面では、 Javaコンパイラは、定数の値を、それらを使用するクラスの参照変数に配置します。 したがって、定数クラスの定数の1つを変更し、そのクラスのみを再コンパイルし、参照クラスは再コンパイルしない場合、一貫性のない定数値を取得する可能性があります。

3.3. コンスタントインターフェイスのアンチパターン

定数インターフェイスパターンは、特定の機能のすべての定数を含むインターフェイスを定義し、インターフェイスを実装するためにそれらの機能を必要とするクラスを持つ場合です。

電卓の定数インターフェイスを定義しましょう。

public interface CalculatorConstants {
    double PI = 3.14159265359;
    double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
    enum Operation {ADD, SUBTRACT, MULTIPLY, DIVIDE};
}

次に、CalculatorConstantsインターフェイスを実装します。

public class GeometryCalculator implements CalculatorConstants {    
    public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
       // Code to do an operation
    }
}

定数インターフェイスの使用に反対する最初の議論は、インターフェイスの目的に反するということです。 インターフェイスを使用して、実装クラスが提供する動作のコントラクトを作成することを目的としています。 定数でいっぱいのインターフェースを作成するとき、動作を定義していません。

次に、定数インターフェイスを使用すると、フィールドシャドウイングによって引き起こされる実行時の問題が発生する可能性があります。 GeometryCalculatorクラス内でUPPER_LIMIT定数を定義することにより、これがどのように発生するかを見てみましょう。

public static final double UPPER_LIMIT = 100000000000000000000.0;

GeometryCalculator クラスでその定数を定義したら、クラスのCalculatorConstantsインターフェイスで値を非表示にします。 その後、予期しない結果が生じる可能性があります。

このアンチパターンに対する別の議論は、名前空間の汚染を引き起こすということです。 これで、 CalculatorConstants は、インターフェイスを実装するすべてのクラスとそのサブクラスの名前空間に含まれるようになります。

4. パターン

以前、定数を定義するための適切な形式を検討しました。 アプリケーション内で定数を定義するための他のいくつかのグッドプラクティスを見てみましょう。

4.1. 一般的なグッドプラクティス

定数がクラスに論理的に関連している場合は、そこで定義するだけです。 定数のセットを列挙型のメンバーと見なす場合、enumを使用してそれらを定義できます。

Calculatorクラスでいくつかの定数を定義しましょう。

public class Calculator {
    public static final double PI = 3.14159265359;
    private static final double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
    public enum Operation {
        ADD,
        SUBTRACT,
        DIVIDE,
        MULTIPLY
    }

    public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
        if (numberOne > UPPER_LIMIT) {
            throw new IllegalArgumentException("'numberOne' is too large");
        }
        if (numberTwo > UPPER_LIMIT) {
            throw new IllegalArgumentException("'numberTwo' is too large");
        }
        double answer = 0;
        
        switch(operation) {
            case ADD:
                answer = numberOne + numberTwo;
                break;
            case SUBTRACT:
                answer = numberOne - numberTwo;
                break;
            case DIVIDE:
                answer = numberOne / numberTwo;
                break;
            case MULTIPLY:
                answer = numberOne * numberTwo;
                break;
        }
        
        return answer;
    }
}

この例では、 UPPER_LIMIT の定数を定義しましたが、これは Calculator クラスでのみ使用する予定なので、privateに設定しました。 ]。 他のクラスがPIOperation列挙型を使用できるようにするため、これらをpublicに設定しました。

操作列挙型を使用する利点のいくつかを考えてみましょう。 最初の利点は、可能な値を制限することです。 4つの定数文字列のうちの1つが提供されることを期待して、このメソッドが操作値の文字列を取得するとします。 メソッドを呼び出す開発者が独自の文字列値を送信するシナリオは簡単に予測できます。 列挙型を使用すると、値は定義した値に制限されます。列挙型はswitchステートメントでの使用に特に適していることもわかります。

4.2. 定数クラス

いくつかの一般的なグッドプラクティスを見てきたので、定数クラスが良いアイデアである場合を考えてみましょう。 アプリケーションに、さまざまな種類の数学的計算を実行する必要のあるクラスのパッケージが含まれていると想像してみてください。 この場合、計算クラスで使用する定数に対して、そのパッケージで定数クラスを定義することはおそらく理にかなっています。

MathConstantsクラスを作成しましょう。

public final class MathConstants {
    public static final double PI = 3.14159265359;
    static final double GOLDEN_RATIO = 1.6180;
    static final double GRAVITATIONAL_ACCELERATION = 9.8;
    static final double EULERS_NUMBER = 2.7182818284590452353602874713527;
    
    public enum Operation {
        ADD,
        SUBTRACT,
        DIVIDE,
        MULTIPLY
    }
    
    private MathConstants() {
        
    }
}

最初に気付くべきことは、私たちのクラスは拡張されないようにするための最終的なものです。 さらに、 private コンストラクターを定義したため、インスタンス化できません。 最後に、この記事の前半で説明した他のグッドプラクティスを適用したことがわかります。 パッケージの外部でアクセスする必要があると予想されるため、定数PIpublicです。 package-private として残した他の定数は、パッケージ内でアクセスできるようにします。 すべての定数staticfinalを作成し、叫び声を上げるスネークケースに名前を付けました。 操作は特定の値のセットであるため、列挙型を使用して定義しました。

特定のパッケージレベルの定数クラスは、パッケージにローカライズされており、そのパッケージのクラスに関連する定数が含まれているため、大規模なグローバル定数クラスとは異なることがわかります。

5. 結論

この記事では、Javaで定数を使用するときに見られる最も一般的なパターンとアンチパターンのいくつかの長所と短所について検討しました。 アンチパターンについて説明する前に、いくつかの基本的なフォーマットルールから始めました。 いくつかの一般的なアンチパターンについて学習した後、定数に適用されることがよくあるパターンを調べました。

いつものように、コードはGitHubから入手できます。