1. 概要

ユーティリティクラスには、特定のトピックを中心にグループ化した静的メンバーのみが含まれます。 したがって、クラス自体はステートレスですが、そのメンバーには、複数のレイヤーで再利用することを目的としたコードが含まれています。

このチュートリアルでは、静的コードアナライザーがユーティリティクラスにパブリックコンストラクターを含めるべきではないと報告する理由を説明します。 プライベートコンストラクターを実装することで、この問題を解決する方法を見ていきます。 さらに、どのLombokアノテーションがそれを生成するのに役立つかを探ります。 これらの警告を無効にする方法も示します。

最後に、Javaでユーティリティクラスを実装するためのいくつかの代替アプローチを評価します。

2. ユーティリティクラス

オブジェクトを定義するクラスとは異なり、 ユーティリティクラスはデータや状態を保存しません。 それらは振る舞いだけを含んでいます 。 ユーティリティには静的メンバーのみが含まれます。 それらのメソッドはすべて静的ですが、データはメソッド引数としてのみ渡されます。

2.1. なぜユーティリティクラス?

オブジェクト指向プログラミングでは、問題のドメインをモデル化し、同様の機能のファミリーをグループ化することを目指しています。

特に関数型プログラミングを使用する場合は、コードベース全体で共通の動作をモデル化するために純粋関数を作成することもできます。 オブジェクトメソッドとは異なり、これらの純粋関数はオブジェクトのインスタンスに関連していません。 しかし、彼らには家が必要です。 Javaには、一連の関数を格納するための特定のタイプが用意されていないため、ユーティリティクラスを作成することがよくあります。

Javaで人気のあるユーティリティクラスの良い例は、 java.util 、およびStringUtilsArraysおよびCollectionsです。 ]フォームorg.apache.commons.lang3

2.2. Javaでの実装

Javaは、ユーティリティクラスを作成するための特別なキーワードや方法を提供していません。 したがって、通常はユーティリティクラスをプレーンJavaクラスとして作成しますが、静的メンバーのみを使用します

public final class StringUtils {

    public static boolean isEmpty(String source) {
        return source == null || source.length() == 0;
    }

    public static String wrap(String source, String wrapWith) {
        return isEmpty(source) ? source : wrapWith + source + wrapWith;
    }
}

この例では、ユーティリティクラスをpublicおよびfinalとしてマークしました。 ユーティリティは、複数のレイヤーで再利用することを目的としているため、通常は公開されます。

final キーワードは、サブクラス化を防ぎます。 ユーティリティクラスは継承用に設計されていないため、サブクラス化しないでください。

2.3. パブリックコンストラクターの警告

人気のある静的コード分析ツールであるSonarQubeを使用して、サンプルのユーティリティクラスを分析してみましょう。 ビルドツールプラグイン(この場合はMaven)を使用して、JavaプロジェクトでSonarQube分析を実行できます。

mvn clean verify sonar:sonar -Dsonar.host.url=http://localhost:9000 -Dsonar.login=XYXYXYXY

静的コード分析の結果、主要なコードの臭いが発生します。 SonarQubeは、ユーティリティクラスの暗黙のパブリックコンストラクターを非表示にするように警告します。

ユーティリティクラスにコンストラクタを追加しませんでしたが、Javaは暗黙的にデフォルトのパブリッククラスを追加しました。 したがって、APIユーザーがそのインスタンスを作成できるようにします。

StringUtils utils = new StringUtils();

これは、インスタンス化されるように設計されていないため、ユーティリティクラスの誤用です。 したがって、SonarQubeルールは、デフォルトのパブリックコンストラクターを非表示にするためにプライベートコンストラクターを追加するようにアドバイスしています。

3. プライベートコンストラクターの追加

ユーティリティクラスにプライベートコンストラクタを追加して、報告されたコードの臭いを解決しましょう。

3.1. デフォルトのプライベートコンストラクタ

ユーティリティクラスに引数のないプライベートコンストラクタを追加しましょう。 このプライベートコンストラクターを実際に使用することはありません。 したがって、次のように呼び出された場合に備えて、例外をスローすることをお勧めします。

public final class StringUtils {

    private StringUtils() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }
  
    // public static methods
}

プライベートコンストラクターもテストできないことに注意してください。 したがって、このアプローチでは、コードカバレッジ測定で1行のカバーされていないコードが生成されます。

3.2. LombokNoArgsConstructorの使用

NoArgsConstructor Lombokアノテーションを使用して、プライベートコンストラクターを自動生成できます。

@NoArgsConstructor(access= AccessLevel.PRIVATE)
public final class StringUtils {

    // public static methods
}

このようにして、カバーされていないコードを手動で追加することを回避できます。

3.3. LombokUtilityClassの使用

クラス全体をユーティリティとしてマークするUtilityClassLombokアノテーションを使用することもできます

@UtilityClass
public class StringUtils {

    // public static methods
}

この場合、Lombokは自動的に次のことを行います。

  • 例外をスローするプライベートコンストラクターを生成する
  • 追加する明示的なコンストラクターにエラーとしてフラグを立てます
  • クラスにfinalのマークを付けます

現時点では、UtilityClassアノテーションはまだ実験的な機能であることに注意してください。

4. 警告を無効にする

推奨される解決策に従わない場合は、パブリックコンストラクターの警告を無効にするオプションもあります。

4.1. 警告の抑制

JavaのSuppressWarningsアノテーションを利用して、単一のクラスレベルで警告を無効にしましょう

@SuppressWarnings("java:S1118")
public final class StringUtils {

    // public static methods
}

正しいSonarQubeルールIDを値パラメーターとして渡す必要があります。 SonarQubeサーバーのUIで見つけることができます:

4.2. ルールの非アクティブ化

SonarQubeのすぐに使える品質プロファイルでは、事前定義されたルールを非アクティブ化することはできません。 したがって、プロジェクト全体レベルで警告を無効にするには、最初にカスタム品質プロファイルを作成する必要があります。

カスタム品質プロファイルでは、事前定義されたJavaルールを検索して非アクティブ化できます。

5. 代替実装

クラスを使用する以外にユーティリティを作成する方法のいくつかの可能な代替方法を見てみましょう。

5.1. 静的インターフェースメソッド

Java 8以降、interfacesで静的メソッドを定義および実装できます。

public interface StringUtils {

    static boolean isEmpty(String source) {
        return source == null || source.length() == 0;
    }

    static String wrap(String source, String wrapWith) {
        return isEmpty(source) ? source : wrapWith + source + wrapWith;
    }
}

インターフェイスをインスタンス化できないため、ユーティリティクラスのインスタンス化の問題を排除しました。 ただし、別の問題が発生しています。 インターフェイスは他のクラスによって実装されるように設計されているため、APIユーザーが誤ってこのインターフェイスを実装する可能性があります。

さらに、インターフェイスにプライベート定数と静的初期化子を含めることはできません。

5.2. 静的列挙型メソッド

列挙型は、管理対象インスタンスのコンテナです。 ただし、静的メソッドのみを含むインスタンスがゼロの列挙型としてユーティリティを作成できます。

public enum StringUtils {;

    public static boolean isEmpty(String source) {
        return source == null || source.length() == 0;
    }

    public static String wrap(String source, String wrapWith) {
        return isEmpty(source) ? source : wrapWith + source + wrapWith;
    }
}

列挙型をインスタンス化できないため、ユーティリティクラスのインスタンス化の問題を排除しました。 一方、名前が示すように、列挙型は、ユーティリティクラスではなく、実際の列挙を作成するために設計されています。

6. 結論

この記事では、ユーティリティクラスについて説明し、パブリックコンストラクターを使用しない理由を説明しました。

例では、プライベートコンストラクターを手動で実装し、Lombokアノテーションを使用する方法について説明しました。 次に、関連するSonarQube警告を抑制および無効にする方法を確認しました。 最後に、インターフェイスと列挙型を使用してユーティリティを作成する2つの代替方法を検討しました。

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