Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1879 lines (1394 sloc) 82.9 KB

Javaコーディング規約

1.はじめに

本規約はJavaを使用してアプリケーションを開発するプロジェクトにおいて、アプリケーションプログラマーが守るべきルールやより良いコードを書くための指針を解説しています。

一部、Nablarch Application Frameworkを前提とした項目も含まれますが、多くの項目は特定のフレームワークに限らず汎用的に使用できるようにしてあります。

1.1.前提

本規約が対象とするコードは、次の3つが実施されていることを前提としています。

  • コードフォーマッターでフォーマットしていること(Javaコードフォーマッター
  • Checkstyleで機械的に検出できる規約違反は解消していること(Checkstyleガイド
  • SpotBugsで明らかに問題のあるコードや、後々問題が発生しそうなコードを排除していること(SpotBugsガイド

機械的に対処できることは予め実施し、本規約ではより良いコードを書くためのガイド、あるいはコードレビューの指針となるように作成しました。

なお著名な静的解析ツールは他にもSonarQubeがありますが、サーバーへインストールする形式のSonarQubeよりもMavenの実行だけでチェックが簡潔するCheckstyleとSpotBugsの方が導入の敷居が低いため、本規約ではCheckstyleとSpotBugsを前提にしています。

プロジェクトによってはCheckstyle・SpotBugs以外の手段で静的解析をする場合もあるでしょう。 必要に応じて、規約の前提条件をCheckstyle・SpotBugsからSonarQubeやその他の静的解析ツールに読み換えて頂いて構いません。

1.2.指標

本規約を書くにあたって、次の3つを指標としました。

  • ランタイムエラーよりもコンパイルエラー
  • 変更可能な状態を減らす
  • 状態の変更箇所を局所化する

ランタイムエラーよりもコンパイルエラー:アプリケーションを実際に動かさないと発見できないバグよりも、コンパイル時にエラーとなって発見できるバグの方が素早く修正できます。 ランタイムエラーに頼らず、なるべくコンパイルエラーでバグを発見できるようなコーディングを心がけることで安心感をもって開発を進められます。

変更可能な状態を減らす:変更可能な状態とは、setterなどのメソッドによって再代入できるフィールドや、java.lang.StringBuilderのように値を変更できるクラスで宣言されたフィールドを指します。

//setterで変更できるフィールド(変更可能な状態)
private String mutableState;

public void setMutableState(final String mutableState) {
    this.mutableState = mutableState;
}
//フィールドは再代入できないが、値は変更できる(変更可能な状態)
private final StringBuilder mutableState = new StringBuilder();

public void appendHello(final String yourName) {
    //値を変更している
    mutableState.append("Hello ").append(yourName);
}

変更可能な状態がある場合、現在の状態がどうなっているか頭の中で覚えながらコードを読み進めなくてはいけません。 人それぞれですが、一時的に頭の中で覚えておける情報量はそれほど多くありません。 なるべく変更可能な状態を減らすことで、読み手に負担をかけず保守しやすいコードになります。

状態の変更箇所を局所化する:setterなどで状態を変更することがありますが、状態の変更前後で頭の中で覚えている状態も変更しないといけません。 状態を変更するコードをなるべく近い場所にまとめることで、読み手に負担をかけず保守しやすいコードになります。

本規約はこの指標に則ったコードを書くためにはどのような事に気を配れば良いのかを意識して作成しました。 本規約を読み進める際も、また実際にコードを書く際も、この指標を意識しておくとより効果的でしょう。

1.3.Javaバージョン

本規約はJava 8をベースに作成しており、一部の項目ではJava 9やJava 10で使用できる新機能にも言及しています。

本規約はJava 8以降をお使いのプロジェクトでご利用頂けます。

1.4.表記ルール

本規約ではJavaのバージョンをJ2SE 5やJava SE 8とは書かずに、単にJava 5やJava 8と記載しています。

規約の解説中にコード例を掲載することがあります。 やってはいけない「禁止コード」の例は先頭に//NGと一行コメントの形式で記載しています。 禁止コードとの対比として挙げている、やってもよい・ぜひやるべき「推奨コード」の例は先頭に//OKと記載しています。 //NG//OKのどちらも記載されていないコード例は、「推奨コード」か、もしくは単なる解説の補助として掲載しています。

コード例の中で、特に見せたい箇所以外は省略する場合があります。 その際は ... と書いて省略を表しています。

//省略の例
public void someMethod(...) { //引数の記載を省略
    ... //メソッド本体の記載を省略
}

また、縦に伸びているコード例を中略する場合は . を縦に3つ書いて表していることがあります。

//中略の例
final String text = message.getText();
.
.
.
return text;

本規約ではループインデックスを伴う昔ながらのfor文を「レガシーfor文」と表現しています。

//レガシーfor文の例
for (int i = 0; i < length; i++) {
    ...
}

コード例は本規約、特に「7.推奨事項」で挙げられている規約に従っているため、次のような特徴があります。

  • フィールドにはprivateを付けている
  • 変数には可能な限りfinalを付けている

他にも従っている規約はありますが、詳細は「7.推奨事項」を参照してください。 なお「禁止コード」の例を示すため、一部のコード例では規約に従っていないものもあります。


2.パッケージ構成

アプリケーションのパッケージ構成についての規約です。

2.1.方式設計上のステレオタイプに従ってパッケージを構成してください

業務アプリケーションでは方式設計上のステレオタイプに従ってパッケージを構成してください。

パッケージ名の簡単な例を示します。

役割 命名例
アクション com.example.action
エンティティ com.example.entity
業務フォーム com.example.form
バリデーター com.example.validation
DTO com.example.dto

3.命名

クラスやメソッド、変数の命名についての規約です。

3.1.クラス名は基本的には名詞とし、必要に応じて方式設計上のステレオタイプに従って命名してください

クラス名は基本的には名詞にします。

慣習的に「特定の能力を付与することを表すインターフェース」は、末尾にableを付ける事があります。 Java標準APIにもそのような名前のインターフェースがいくつも含まれています。

  • java.lang.Appendable
  • java.lang.AutoCloseable
  • java.lang.Comparable
  • java.lang.Iterable
  • java.lang.Runnable

業務アプリケーションで作成するクラスは方式設計上のステレオタイプに従った役割を持っていることが多いです。 そのため、ステレオタイプに対応していることが分かるような接尾語を付けるなどして命名をしてください。

役割 命名ルール
アクション 機能を表す名前 + Action
エンティティ テーブルの物理名をアッパーキャメルケースにしたもの
業務ベースフォーム 機能を表す名前 + FormBase
業務フォーム 機能を表す名前 + Form
バリデーター 機能を表す名前 + Validator
DTO データの種類を表す名前 + Dto
排他制御の制御クラス テーブルの物理名をアッパーキャメルケースにしたもの + ExclusiveControl
テスト テスト対象クラス名 + Test
リクエスト単体テスト Actionクラス名 + RequestTest

3.2.メソッド名は基本的には動詞から始まるように命名をしてください

メソッド名は動詞から始まるように命名をするのが基本になりますが、次に示す「機能別の推奨命名ルール」には動詞から始まらないものも含んでいます。

機能 命名ルール
インスタンスを生成する create + 対象 createItemCode
異なる型に変換する to + 変換先の型 toString toItemCode
異なる型として扱う as + 扱う型 asReadLock asList
含んでいるかどうかを返す contains + 対象 containsKey
可能かどうかを返す can + 動作 canRead canEncode
その状態かどうかを返す is + 状態 isClosed isEmpty

「異なる型に変換する」と「異なる型として扱う」は同じように見えますが、少し異なります。 前者は「異なる型に変換してしまって元の型の性質は期待しない」のに対して、後者は「異なる型に変換するが元の型の性質と関連する」ものです。 Java標準APIで言うと、前者にあたるのはInteger.toStringです。整数が文字列に変換されて、整数の性質は期待されません。 後者にあたるのはArrays.asListです。配列がリストに変換されますが、配列とリストは性質が似ていますし、元の配列を変更するとリストも変更されます。

Java標準APIを見てもString.lengthOptional.ofのように動詞から始まらない名前のメソッドがいくつも存在します。 Java標準APIも参考にして、適切な命名を行うようにしてください。

3.3.変数は基本的には名詞で命名してください

フィールド名やローカル変数名は基本的には名詞にします。

Javaの命名規則の慣習上、決まった名前を付ける場合があります。

java.io.InputStreamjava.io.OutputStreamは、それぞれ in isout osと命名しても構いません。

try (final InputStream in = openInputStream(file)) {
    ...
}

レガシーfor文で使用するカウンターはiと命名することが一般的です。

for (int i = 0; i < length; i++) {
    ...
}

catchブロックで例外を受け取る変数はeと命名することが一般的です。

また、値を組み立てるために一時的に使用されるスコープの狭い変数は意味の無い名前でも構いません。

//メッセージを組み立てるための一時変数
//ここではbufという無意味な名前を付けている
//sbやtempといった名前でも良い
final StringBuilder buf = new StringBuilder();
buf.append("Hello, ");
buf.append(yourName);
buf.append("!");
final String message = buf.toString();

3.4.データモデルの用語辞書を活用して命名してください

整理された用語辞書をもとにすると、統一感のある命名が出来ます。

是非、用語辞書を活用してください。 用語辞書が整理されていない場合は、まず用語辞書の整理をすることを検討してみてください。


4.コメント

Javadocコメントや処理の説明をするためのコメントに関する規約です。

4.1.クラスのJavadocコメントを書くようにしてください

クラスのJavadocコメントには、そのクラスの役割を記載してください。

プロジェクトのルールに応じて@author@sinceなども記載してください。

/**
 * 商品情報を表すクラスです。
 * 
 * @author TIS
 */
public class Item {

4.2.フィールドのJavadocコメントを書くようにしてください

フィールドのJavadocコメントには、それがどのような属性を表すものなのか、記載してください。

/**
 * 商品コード
 */
private String code;

/**
 * 商品の名称
 */
private String name;

/**
 * 更新日時
 */
private LocalDateTime updatedAt;

4.3.メソッドのJavadocコメントを書くようにしてください

メソッドのJavadocには、そのメソッドが行う処理の概要を記載してください。 また、そのメソッドを呼び出す上で前提条件があれば、それも記載してください。

@paramで引数の説明を、@returnで戻り値の説明を、@throwsでスローされる可能性がある例外の説明を記載してください。

@throwsでは例外クラスの日本語名だけを書いて済ませてはいけません。 該当の例外がスローされるのはどのような場合かを記載してください。

//NG 例外クラスの名前を記載しているだけなのでNG。
 ...
 * @throws ItemNotFoundException 商品Not Found例外
 */
//OK 例外がスローされるのはどのような場合か記載している。
 ...
 * @throws ItemNotFoundException 更新対象の商品がデータベース内に見つからない場合
 */

次に示す引数と戻り値におけるnullの扱いは、アプリケーション全体のデフォルトがどちらなのかをプロジェクトで決めてください。

  • 引数にnullを許可する、もしくは許可しない
  • 戻り値としてnullを返される場合がある、もしくは必ずnullでない値が返る

そしてデフォルトから外れる場合のみ、その旨をJavadocへ記載するようにしてください。

例えば、「デフォルトでは引数にnullを許可しない」とルールを決めた場合、nullを受け取れる引数にはnullを受け取るとどのように振舞うのかを記載してください。

/**
 * 商品情報を更新します。
 *
 * <p>
 * このメソッドでは既に登録済みの商品に対して更新を行います。
 * まだ商品を登録していない場合は、先に登録処理を行ってください。
 * </p>
 *
 * @param code 商品コード
 * @param name 商品の名称
 * @param updatedAt 更新日時。nullの場合はデフォルト値としてシステム日時が使用される
 * @throws ItemNotFoundException 更新対象の商品がデータベース内に見つからない場合
 */
public void updateItem(final String code, final String name, final LocalDateTime updatedAt)
        throws ItemNotFoundException {
    ...
}

4.4.コードの理解を助けるため、必要に応じて行コメントを記載してください

コードだけを読んで処理の内容を理解できるのが理想的ですが、複雑なロジックや、パフォーマンスのためにあえて特殊な実装をした場合は説明のコメントを記載してください。 コメントは//から始まる一行コメントの形式で記載してください。


5.禁止事項

バグを発生させそうな危ういコードを減らすため、禁止事項を定めています。

5.1.計算式の途中でインクリメント・デクリメントをしないでください

計算式の途中でインクリメント・デクリメントをすると、該当の変数の値が分かりにくくなります。

計算式は計算式で済ませてしまって、そのあとインクリメント・デクリメントをするようにしてください。

//NG
int x = ...
int y = (x++ * height) / width;
//OK
int x = ...
int y = (x * height) / width;
x++;

5.2.フィールドを一時変数として使用しないでください

コレクションの要素を順次処理して必要な値を構築するメソッドなどで中間状態を保持するために一時変数を導入することがありますが、その場合にフィールドを使用しないでください。

フィールドを使用するとクラスが持つ状態を増やすことになります。 なるべく変更可能な状態は持たないようにするためにも、一時変数の用途でフィールドは使用しないでください。

一時変数の用途にはフィールドではなく、ローカル変数を使用してください。

//NG
//フィールドを一時変数として使用している
private List<String> itemNames;

public String collectNames(final List<Item> items) {
    this.itemNames = new ArrayList<>();
    for (Item item : items) {
        collectName(item);
    }
    return this.itemNames.stream().collect(Collectors.joining(", "));
}

private void collectName(final Item item) {
    this.itemNames.add(item.getName());
}
//OK
public String collectNames(final List<Item> items) {
    //一時変数はローカル変数を使用して、メソッドに渡して引き回している
    final List<String> itemNames = new ArrayList<>();
    for (Item item : items) {
        collectName(itemNames, item);
    }
    return itemNames.stream().collect(Collectors.joining(", "));
}

private void collectName(final List<String> itemNames, final Item item) {
    itemNames.add(item.getName());
}

5.3.スーパークラスと同名のフィールドをサブクラスで定義しないでください

スーパークラスに定義されているフィールドと同名のフィールドをサブクラスにも定義すると、スーパークラスに定義されているフィールドを名前だけでアクセスできなくなります。

フィールドはメソッドと異なりオーバーライドできません。

混乱の元となるので、スーパークラスと同名のフィールドをサブクラスで定義しないでください。

//NG
public class SuperClass {

    //サブクラスで使用されることが想定されているフィールド
    protected final String var;

    ...
}

public class SubClass extends SuperClass {

    private final String var;

    public String getVar() {
        return var; //SubClass.var が返される
    }

    ...
}

5.4.戻り値がコレクションや配列の場合は null を返さないでください

値がない状態を表すためにnullを使うことがありますが、戻り値が次に示すようなコレクションや配列の場合は値がない状態を表すためであってもnullを返さないでください。

  • java.util.Collection
  • java.util.Set
  • java.util.List
  • java.util.Map

コレクションの値がない状態というのは、多くの場合はコレクションが空であることを指します。 コレクションには空であることを示すisEmptyというメソッドがあるので、わざわざnullを返す必要はありません。 また、配列の場合はlengthフィールドが0なら空であると判断できます。

nullを返す可能性があると、呼び出し元でnullチェックをしなくてはならずコードが複雑になります。

//NG
public List<Item> findItems(final ItemCategory category) {
    List<Item> items = dao.findItems(category);
    if (items.isEmpty()) {
        return null;
    }
    return items;
}
//OK
public List<Item> findItems(final ItemCategory category) {
    return dao.findItems(category);
}

明示的に空のコレクションを作成して返したい場合はjava.util.Collectionsクラスにあるempty<コレクション>メソッドを使用してください。

例えば条件に応じて空のリストを返したい場合はjava.util.ArrayListをインスタンス化するのではなく、java.util.CollectionsクラスのemptyListメソッドを使用してください。

//NG
if (empty) {
    return new ArrayList<>();
}
//OK
if (empty) {
    return Collections.emptyList();
}

java.util.CollectionsにはemptyList以外にもコレクションの種類に応じてemptySetemptyMapが用意されています。

5.5.コンストラクタ内で自分自身のインスタンスメソッドを呼び出さないようにしてください

コンストラクタ内では、たとえfinalが付いているフィールドであっても初期化前のnull値を参照してしまう場合があります。

そのため、コンストラクタからフィールドを参照するインスタンスメソッドを呼び出す場合、フィールドの初期化とメソッドの呼び出しの順番に注意する必要が出てきてしまいます。

//NG
public class Foo {

    private final String text;
    private final int length;

    public Foo(final String text) {

        //textが初期化されていないのでこの位置でcalculateLengthを呼び出すと
        //NullPointerExceptionがスローされる。
        this.length = calculateLength();

        this.text = text;

        //textが初期化された後のこの位置で呼び出すべき。
        //this.length = calculateLength();
    }

    protected int calculateLength() {
        return text.length();
    }
}

継承を伴うと話は更に複雑化します。

次に示すNG例を見てください。 FooBarという2つのクラスが定義されています。 BarFooを継承しています。

Barをインスタンス化すると、Barのコンストラクタの先頭でFooのコンストラクタが呼び出されます。 FooのコンストラクタではcalculateLengthを呼び出そうとしますが、Barでオーバーライドされているので実際にはBarcalculateLengthが呼び出されます。 BarcalculateLengthではtextを参照していますが、この時点ではまだ初期化されていないのでNullPointerExceptionとなってしまいます。

//NG
public class Foo {

    private final int length;

    public Foo() {
        //コンストラクタで自分自分のメソッドを呼び出している
        this.length = calculateLength();
    }

    protected int calculateLength() {
        return 0;
    }
}

class Bar extends Foo {

    protected final String text;

    public Bar(final String text) {
        this.text = text;
    }

    @Override
    protected int calculateLength() {
        //ここでNullPointerExceptionがスローされる
        return text.length();
    }
}

このような複雑さを持ち込んでしまうため、コンストラクタ内から自分自身のインスタンスメソッドを呼び出さないようにしてください。

コンストラクタ内では基本的には引数をフィールドにセットするだけにしてください。 何かしらの処理を行いたい場合は、コンストラクタ内で処理を完結させるようにしてください。

//OK
public class Foo {

    private final String text;
    private final int length;

    public Foo(final String text) {
        this.text = text;
        // コンストラクタ内で処理が完結している
        this.length = text.length();
    }
}

ただし、コンストラクタ内で長々と処理を書く必要が出てきた場合は、次に挙げるように自クラス以外で処理する方法も含めて検討してください。

  • コンストラクタを呼び出す前にあらかじめ処理を行い、その結果をコンストラクタの引数に渡す
  • コンストラクタ内で別のクラスに処理を委譲し、その結果を使用する

5.6.インスタンスが格納された変数をレシーバにして static メソッドを呼び出さないでください

staticメソッドは通常、クラス名をレシーバにして呼び出すコードを書きます。

インスタンスが格納された変数をレシーバにして呼び出すコードも文法的には許容されていますが、一般的ではないため行わないでください。

//NG
final String text = ...
final int value = ...
return text.valueOf(value);
//OK
final int value = ...
return String.valueOf(value);

5.7.インスタンスが格納された変数をレシーバにして static 変数を参照しないでください

static変数は通常、クラス名をレシーバにして参照するコードを書きます。

インスタンスが格納された変数をレシーバにして参照コードも文法的には許容されていますが、一般的ではないため行わないでください。

//NG
final Integer length = ...
return length.MAX_VALUE;
//OK
return Integer.MAX_VALUE;

5.8.アプリケーションプログラマーの独断で例外クラスは作らないでください

例外はメソッドの処理を中断して大域脱出ができる仕組みです。 適切な管理の元、統一的に扱うべきだと考えます。

必要な例外クラスはフレームワークが提供するものとし、アプリケーションプログラマーは提供されたクラスを使用してください。

もし例外クラスの作成が必要になった場合は、アーキテクトへ相談してください。

5.9.java.lang.Exceptionクラスのインスタンスを生成してスローしないでください

明示的に例外をスローする場合に単なるjava.lang.Exceptionクラスのインスタンスを生成してスローしないでください。 業務アプリケーションの方式設計に沿った例外クラスのインスタンスを生成してスローしてください。

java.lang.Exceptionはすべての例外の基底クラスなので、catchする側が業務アプリケーションの例外なのかネットワークの例外なのかといった判別ができません。

また、java.lang.Exceptionをスローするとthrows Exceptionをメソッドに付与しなければいけませんが、呼び出し元のメソッドにもthrows Exceptionを強要することになり、扱いづらくなってしまいます。

//NG
if (items.isEmpty()) {
    throw new Exception("Items searched by " + code + " are not found.");
}
//OK
if (items.isEmpty()) {
    throw new ItemsNotFoundException(code);
}

5.10.try-catch 文を条件分岐のために使用しないでください

try-catch文は例外を扱うためのものです。 条件分岐をしたい場合はif文を使用してください。

また例外がスローされた場合、catchできるかどうかをチェックしますが、このチェックは高コストです。 特にループ内でtry-catchを条件分岐のために使用していた場合は、性能劣化が顕著に起る可能性があります。

//NG
try {
    //codeに対するitemが無い場合に例外をスローするAPI
    service.findItem(code);
    return "Items exist";
} catch (ItemsNotFoundException e) {
    return "No items";
}
//OK
//codeに対するitemが存在するかチェックするAPI
if (service.exists(code)) {
    return "Items exist";
} else {
    return "No items";
}

5.11.秘匿情報はログ出力やシリアライズされないように注意してください

パスワードのような秘匿情報はログに含まれないようにマスクするなど、注意をしてください。

また、インスタンスをシリアライズする場合も、秘匿情報が含まれないように注意してください。 なお、ここでは「シリアライズ」という言葉の意味として、JavaのシリアライズのみならずJSONやXMLなどのフォーマットに変換することも含めています。

例えばJavaのシリアライズの場合、シリアライズ対象外にしたいフィールドにはtransientキーワードを付与します。

public class LoginForm implements Serializable {

    private String username;
    private transient String password;

    ...
}

JSONやXMLへのシリアライズでも、通常はライブラリがシリアライズ対象外にする方法を用意してありますので、適切に対応してください。

5.12.Java標準ライブラリにあるレガシーなAPIは使用しないでください

Java標準ライブラリには過去のバージョンでは使われていましたが、今となってはレガシーであり使うべきではないAPIがあります。 次に列挙するレガシーAPIは使わないようにしてください。

レガシーAPI 代替となる使っても良いAPI
java.lang.StringBuffer java.lang.StringBuilder
java.util.Dictionary java.util.Map
java.util.Enumeration java.util.Iterator
java.util.Hashtable java.util.HashMap
java.util.Stack java.util.Deque
java.util.StringTokenizer Stringsplitメソッド
またはjava.util.regexパッケージのAPI
java.util.Vector java.util.ArrayList

これらのレガシーAPIを使用しているかどうかは使用不許可APIチェックツールによって検出できますが、知識として知っておくためにも本規約に明記しています。

5.13.リフレクションを直接使用しないでください

java.lang.reflectパッケージにあるクラスを使ってできる操作をリフレクションと言いますが、リフレクションを直接使用しないでください。 リフレクションを使用すると動的なオブジェクト操作ができますが「コンパイルは通っているけれど、実行時にエラーが出る」といった状況を引き起こす原因になります。

フレームワークやアーキテクトから提供される共通部品が内部でリフレクションを使用していることはありますが、アプリケーションで直接使用することは禁止します。

リフレクションを使用しているかどうかは使用不許可APIチェックツールによって検出できますが、危険なコードを書かないためにも本規約に明記しています。

5.14.クラスを不整合な状態にしないでください

クラス内に関連性のある複数のフィールドを定義した場合、それらのフィールド間で値の整合性を保つようにしてください。

//NG
public class ItemList {

    private final List<Item> items = new ArrayList<>();
    private BigDecimal totalPrice = BigDecimal.ZERO;

    public void add(final Item item) {
        //この時点で合計値が変わるためtotalPriceを更新しなければいけない
        items.add(item);
    }

    public void save(final ItemDao dao) {
        //保存時についでに合計値の計算をしている
        totalPrice = BigDecimal.ZERO;
        for (final Item item : items) {
            dao.save(item);
            totalPrice = totalPrice.add(item.getPrice());
        }
    }

    public BigDecimal getTotalPrice() {
        //addの後にsaveを呼ばずにこのメソッドを呼ぶとItem追加前の合計値が返されてしまう
        return totalPrice;
    }
}

上記の例ではitemsを変更した際、同時にtotalPriceを変更すると良いでしょう。

//OK
public class ItemList {

    private final List<Item> items = new ArrayList<>();
    private BigDecimal totalPrice = BigDecimal.ZERO;

    public void add(final Item item) {
        //itemを追加してすぐに合計値の計算をしているので状態の整合性が保たれている
        items.add(item);
        totalPrice = totalPrice.add(item.getPrice());
    }

    public void save(final ItemDao dao) {
        for (final Item item : items) {
            dao.save(item);
        }
    }

    public BigDecimal getTotalPrice() {
        //どのタイミングで呼び出しても正しい合計値を返す
        return totalPrice;
    }
}

もしくは合計値は状態として持たずにgetTotalPrice内で都度計算するようにしても良いでしょう。

//OK
public class ItemList {

    private final List<Item> items = new ArrayList<>();

    public void add(final Item item) {
        items.add(item);
    }

    public void save(final ItemDao dao) {
        for (final Item item : items) {
            dao.save(item);
        }
    }

    public BigDecimal getTotalPrice() {
        return items.stream()
                .map(item -> item.getPrice())
                .reduce(BigDecimal.ZERO, (price1, price2) -> price1.add(price2));
    }
}

5.15.アプリケーションプログラマーの独断でスレッドを作って非同期処理をしないでください

フレームワークはスレッドに紐付けてデータベースコネクションやトランザクションを管理していることがあります。 アプリケーションプログラマーが自分でスレッドを作って非同期処理を行うことは想定されていません。

また、フレームワークの制限が無いとしても非同期処理は高難度です。 パフォーマンスなどの観点から非同期処理が必要になった場合は、アーキテクトに相談してください。

5.16.名前と値が同じ定数は作らないでください

定数の名前と値が同じになっているものは、もっと良い名前がある場合と、そもそも定数にしなくて良い場合があります。

次に示すのは、もっと良い名前がある例です。 定数名が改行コードの値そのものになっています。

//NG
//この名前だともし改行コードがLFに変更となった場合に定数名も変更しなくてはいけない
private static final String CRLF = "\r\n";

この場合は定数名を変えると良いでしょう。

//OK
//この名前だともし改行コードがLFに変更となった場合でも定数名はこのままで良い
private static final String LINE_BREAK = "\r\n";

次に示すのは、そもそも定数にしなくて良い例です。 あるテーブルのカラム名を定数にしたものです。

//NG
private static final String ITEM_CODE = "ITEM_CODE";

もしカラム名が変わるとそれに追随して定数名も変えることになり、定数化している意味が薄まります。 別の名前を付けるとしたらCOLUMN_01といったものしか考えられず、意味ある名前にはなりません。

この場合は定数にしなくて良いでしょう。


6.注意事項

禁止とまではいきませんが、バグを防いだり保守性を損なわないために、注意事項を定めています。

6.1.メソッドには名前から想像できない処理を実装しないでください

メソッド名から想像できない処理が実装されていると、そのメソッドの使用者は混乱します。

//NG
//メソッド名からは「未読通知を取得する」処理だと想像できるが、
//実際には「既読状態への更新」も行っている
private List<Notification> findUnreadNotifications() {
    final List<Notification> notifications = dao.findUnreadNotifications();

    //未読から既読へ更新している
    for (Notification notification : notifications) {
        notification.setStatus(Status.ALREADY_READ);
        dao.update(notification);
    }

    return notifications;
}
//OK
//メソッド名から想像できる通り「未読通知を取得する」処理を行っている
private List<Notification> findUnreadNotifications() {
    return dao.findUnreadNotifications();
}

//「既読状態への更新」は別メソッドとしている
private void updateToAlreadyRead(List<Notification> notifications) {
    for (Notification notification : notifications) {
        notification.setStatus(Status.ALREADY_READ);
        dao.update(notification);
    }
}

実装中はメソッドを使うのは自分だけなので大丈夫だと思っていても、コードレビューや保守など実装以降のフェーズになると他人の目に触れる機会があります。

誰が見ても自然に映るよう、「名は体を表す」ようなメソッドを実装してください。

6.2.単一のメソッドに複数の要素を詰め込まないでください

単一のメソッドに「取得」「更新」「チェック」といった複数の要素を詰め込まないでください。 複数の要素を詰め込んだメソッドの名前は、executeupdateなど抽象的なものになりがちです。

アプリケーションのコードは「プレゼンテーション」や「ビジネスロジック」、「データアクセス」といったレイヤーに分かれています。 それぞれのレイヤーによって抽象度は異なるので一概には言えませんが、アプリケーションプログラマーが書くビジネスロジックでは単一のメソッドには複数の要素を詰め込まず具体的な名前を付けるよう努めてください。

6.3.クラス外に公開されるメソッドの引数や戻り値・フィールドの型は実装クラスではなくインターフェースで宣言してください

クラス外に公開されるメソッドの引数や戻り値、フィールドの宣言には実装クラスではなくインターフェースを使用してください。

引数を実装クラスで宣言していると、引数の型を変更した場合に呼び出し元のコードも変更しなくてはいけません。

//NG
//Listではなく、実装クラスであるArrayListで宣言している
public void saveItems(final ArrayList<Item> items) {
    ...
}
//saveItemsメソッドを呼び出しているコード
//もしsaveItemsの引数がArrayListからLinkedListに変更された場合、
//こちらのコードも変更する必要がある(NG)
final ArrayList<Item> items = ...
dao.saveItems(items);

引数をインターフェースで宣言していると、そもそも実装クラスは呼び出し元で用意するものなので、上記のような問題は起きません。

//OK
//Listインターフェースで宣言している
public void saveItems(final List<Item> items) {
    ...
}

戻り値を実装クラスで宣言している場合も、呼び出し元のコードで戻り値を受け取っている変数の型が実装クラスになっていると、やはり戻り値の型を変更した場合に呼び出し元のコードも変更しなくてはいけません。

//NG
//Listではなく、実装クラスであるArrayListで宣言している
public ArrayList<Item> findAllItems() {
    ...
}
//findAllItemsメソッドを呼び出しているコード
//もしfindAllItemsの戻り値がArrayListからLinkedListに変更された場合、
//こちらのコードも変更する必要がある(NG)
final ArrayList<Item> allitems = dao.findAllItems();

引数とは違って、呼び出し元コードの方で宣言している変数の型をインターフェースにしておけば実害はありません。 しかし、変数の型を実装クラスで宣言してしまう余地をなくすためにも、戻り値の型もインターフェースで宣言するようにしてください。

//OK
//Listインターフェースで宣言している
public List<Item> findAllItems() {
    ...
}

privateメソッドやprivateフィールドはクラス内に閉じており影響範囲が狭いので実装クラスで宣言しても構いませんが、そうする強い理由が無ければインターフェースで宣言しておいてください。

また、ローカル変数はメソッド内に閉じており影響範囲がかなり狭いので実装クラスで宣言しても構いません。 なお、Java 10から導入されたvarを使用してローカル変数を宣言すると、右辺の式を評価した結果の型として扱われます。 これはローカル変数の型を実装クラスで宣言していることと同義です。

//OK
public void someMethod() {

    //ローカル変数なので実装クラスで宣言してもOK
    ArrayList<String> localVariable = new ArrayList<>();

    //Java 10から導入されたvarでローカル変数を宣言してもOK
    //この場合は右辺の値がArrayListなので、ArrayListで宣言したものとして扱われる
    //※ダイヤモンドオペレーターが使えず、右辺で型パラメーターをバインドしなくてはいけない点に注意
    var useVarKeyword = new ArrayList<String>();

    ...
}

6.4.メソッドのオーバーロードはオプションの省略用途のみに使用してください

引数の順番が入れ替わっただけのメソッドをオーバーロードしたり、全く異なる型の引数を取るメソッドをオーバーロードするとコードが複雑になります。

メソッドのオーバーロードはオプション扱いの引数を省略したメソッドを定義する場合にのみ使用してください。 その際も、引数の順番に注意をして後ろの引数から省略するようにメソッドを設計してください。

//NG
//引数の順番が変わっただけのオーバーロード
public void updateItem(final ItemCode code, final String name, final int version) {
    ...
}

public void updateItem(final ItemCode code, final int version, final String name) {
    ...
}

public void updateItem(final String name, final int version, final ItemCode code) {
    ...
}
//NG
//全く異なる型の引数を取るオーバーロード
public void updateItem(final ItemCode code, final String name, final int version) {
    ...
}

public void updateItem(final ItemCode code, final LocalDateTime updatedAt) {
    ...
}
//OK
//オプションの引数を省略するオーバーロード
public void updateItem(final ItemCode code, final String name, final int version) {
    LocalDateTime defaultUpdatedAt = ...
    updateItem(code, name, defaultUpdatedAt);
}

public void updateItem(final ItemCode code, final String name, final int version, final LocalDateTime updatedAt) {
    ...
}

6.5.未使用コードは残したままにしないでください

試行錯誤や性能改善の過程で使用されなくなったメソッドや変数、デバッグ用のコードなどは残したままにせず削除してください。

ただし、次のような未使用コードは例外として扱います。 これらは削除せずに残しておいてください。

  • フレームワークの制約で、必ず定義しないといけないメソッドや変数
  • 自動生成されたコードに含まれる未使用メソッド・変数

6.6.クラスは大きくなりすぎないようにしてください

クラスが大きすぎると内容の把握が難しくなり、保守性が低くなります。 そのような場合は、クラスを分割することを検討してください。

フィールドはクラスの状態を表すものですが、中でも変更可能なフィールドが多いとクラス全体の把握が難しくなります。 なるべく変更可能なフィールドを減らすか、分割して別のクラスに移動してください。

ただし次に示すようなデータの入れ物として機能するクラスは、変更可能なフィールドが多くなる傾向にありますが意味のある単位でクラスが作られているので分割しないでください。

  • データベースのテーブルをマッピングするエンティティクラス
  • HTMLのフォームをマッピングするフォームクラス

6.7.メソッドは大きくなりすぎないようにしてください

メソッドが大きすぎると処理の把握が難しくなり、保守性が低くなります。 そのような場合は、メソッドを分割することを検討してください。

最も簡単なのはある程度の塊ごとにprivateメソッドへ切り出すことです。

また、メソッドの引数の個数も多くなりすぎないようにしてください。

6.8.インナークラスやstaticにネストしたクラス、匿名クラスは作りすぎないようにしてください

インナークラスやstaticにネストしたクラス、匿名クラスの使用を特に禁止しませんが、これらのクラスを作りすぎてしまうとコードの可読性が低くなってしまいます。

もし大量のインナークラスやstaticにネストしたクラス、匿名クラスが必要な場合は、別の独立したクラスに切り出せないか検討してみてください。

6.9.可能な限りキャストは使用しないでください

キャストはある型として扱っている値を強制的に異なる型として扱うようにする仕組みで、「コンパイルは通っているけれど、実行時にエラーが出る」といった状況を引き起こす原因になります。

Java 5からジェネリクスが導入されて、キャストを使用しなくてもほとんど困る事はなくなっているはずです。

6.10.ラッパークラスの変数とプリミティブ値を演算する際は、アンボクシングに注意してください

java.lang.Integerのようなラッパークラスをintのようなプリミティブ値へ変換することをアンボクシングと言います。

ラッパークラスの変数はプリミティブ値を伴う演算を行う場合、コンパイラーによって自動でアンボクシング処理が差し込まれます。

final Integer a = ...
final int b = ...

//コンパイラーによって変数aに対してintValueメソッド呼び出しが差し込まれる
//つまり実際の処理は final int c = a.intValue() + b; となる
final int c = a + b;

自動でアンボクシングが差し込まれるのは便利ですが、ラッパークラスの変数がnullの場合にアンボクシングするとjava.lang.NullPointerExceptionがスローされてしまいます。

final Integer a = null;
final int b = ...

//変数aがnullの場合、コンパイラーによって差し込まれたintValueメソッド呼び出しでNullPointerExceptionがスローされてしまう
final int c = a + b;

このため、ラッパークラスの変数とプリミティブ値を演算する場合はnullチェックを行うなど、注意をしてコーディングしてください。

final Integer a = ...
final int b = ...

if (a != null) {
    final int c = a + b;
    .
    .
    .
}

6.11.計算の誤差が許されない場合はBigDecimalを使用してください

floatdoubleは浮動小数点数と呼ばれる方法で表現される数値型ですが、浮動小数点数の計算はいくつかのケースで誤差を生じることがあります。

GUIの座標計算のように多少の誤差を無視できる場合は浮動小数点数を使用しますが、金利や値引きなどのように誤差が許されない場合はjava.math.BigDecimalを使用してください。

//NG
final double discountRage = 0.07;
return 1 - discountRate; //0.9299999999999999
//OK
final BigDecimal discountRate = new BigDecimal("0.07");
return BigDecimal.ONE.subtract(discountRate); //0.93

なお、BigDecimalのコンストラクタにはdoubleの値を受け取るものがありますが、浮動小数点数で発生する誤差と同じような誤差が発生する場合もあるので使用しないでください。 doubleの値からBigDecimalインスタンスを得たい場合はvalueOfメソッドを使用してください。

//NG
final BigDecimal discountRate = new BigDecimal(0.07); //doubleの値を渡してインスタンス化
return BigDecimal.ONE.subtract(discountRate); //0.929999999999999993338661852249060757458209991455078125
//OK
final BigDecimal discountRate = BigDecimal.valueOf(0.07); //doubleの値をvalueOfに渡してインスタンス化
return BigDecimal.ONE.subtract(discountRate); //0.93

また、BigDecimalequalsメソッドは値とスケールが同じである場合に等しいと見なします。 ですので、2つのBigDecimalの値を比較する場合はcompareToメソッドを使用してください。

//NG
final BigDecimal value = new BigDecimal("10.0"); //スケールは1
if (value.equals(BigDecimal.TEN)) { //BigDecimal.TENのスケールは0なのでequalsはfalseを返す
    ...
}
//OK
final BigDecimal value = new BigDecimal("10.0"); //スケールは1
if (value.compareTo(BigDecimal.TEN) == 0) { //comareToはスケールが異なっても値が等しい場合は0を返す
    ...
}

BigDecimalは誤差の無い計算ができる他、java.math.RoundingModeで多数の丸めモードをサポートしています。

6.12.ソートと集計はJava側ではなく、SQLで行うようにしてください

データベース内のデータのソートや集計はJava側ではなくSQLで行ってください。

データが少量であればJava側でソートを行っても問題はありませんが、データが大量だと性能劣化の原因となります。 また、データベースから少量ずつフェッチして処理を行う場合はそもそもJava側でソートができません。

集計は通常、大量データに対して行うものですので、これもJava側で行うと性能劣化の原因となります。 それにソートと異なり集計前のデータは一時的に使用するもので、本当に必要なのは最終的な集計結果です。 このことからもSUMAVRなどの集計関数を使用してSQLで集計を行ってください。

6.13.ループ処理の中では極力データベースアクセスしないようにしてください

業務アプリケーションでは、データベースから取得したデータをループで1件ずつ処理することがよくあります。

このとき、ループの中で追加の情報を取得するためにデータベースへアクセスしないようにしてください。 データベースアクセスの回数が多くなり、性能劣化の原因となります。

可能な限り、最初のデータ取得時にテーブル結合を使用して1回のデータベースアクセスで必要な情報を取得するようにしてください。

6.14.外部からの入力値は共通部品を用いてチェックしてください

アプリケーション外部からの入力値にはいくつかの種類があります。 次に3つの例を挙げます。

  • ブラウザから送信されたフォームの値
  • 外部システムから連携されたファイルの内容
  • MQで連携されたメッセージ

これらの値は必ず入力仕様に沿ってチェックをしてください。 例えばブラウザから送信されたフォームの値であればBean Validationを使用してバリデーションを行ってください。 他のものも同様で、フレームワークが提供している機能を使用して入力値チェックを行ってください。

なお「アプリケーション外部からの入力値」というとデータベースから取得したデータも該当しそうですが、自分たちのコントロール下にあるデータベースには既にチェック済みの信頼できる値が格納されているはずです。 このことから、データベースから取得したデータは入力値チェック対象外です。

6.15.ファイル入出力は共通部品のクラスを使用してください

アプリケーションプログラマーが思い思いにファイル入出力を行うと文字コードや改行コードの取り扱いを統一させるのに労力がかかります。

アーキテクトはプロジェクトで定めるファイルフォーマット仕様に基づいて、ファイル入出力の共通部品を作成してください。 そしてアプリケーションプログラマーはファイル入出力を行う際は、共通部品のクラスを使用してください。

不足機能は共通部品に追加する方向でアーキテクトへ依頼・相談してください。

共通部品を使ってファイル入出力を行うことで文字コードや改行コードの取り扱いを統一できます。 また、共通部品内で要求されたファイルパスのチェックを行うことでディレクトリトラバーサルを防げます。

6.16.リソースをクローズする必要がある場合はtry-with-resources構文を使用してください

java.io.InputStreamjava.io.OutputStreamなど、外部リソースを使用するオブジェクトはtry-with-resources構文を使用してクローズ漏れが無いようにしてください。

try-with-resources構文はJava 7で導入されました。

//NG
//Java 6までのレガシーな書き方
final InputStream in = openStream();
try {

    final String content = readAsString(in);

} finally {
    in.close();
}
//OK
//Java 7以降のtry-with-resources
try (final InputStream in = openStream()) {

    final String content = readAsString(in);

}

6.17.例外処理はプロジェクトの方式設計に従って統一的にコーディングしてください

例外処理方式はプロジェクトで統一されていることが重要になります。 プロジェクトの方式設計に従って統一的にコーディングしてください。

6.18.ループのネストはできれば二重までにしてください

ループのネストが深くなるとコードの可読性が低下します。 可読性は主観によるものなのでこの指標は絶対ではないですが、本規約ではループのネストは二重までと定めます。


7.推奨事項

より良いコードを書くために、推奨事項を定めています。

7.1.クラスやメソッドなどには適切なアクセス修飾子を付与してください

クラスやメソッドなど、アクセス修飾子を付与できる場所では適切なアクセス修飾子を選択してください。

アクセス修飾子の種類と公開範囲を次に示します。

アクセス修飾子 公開範囲
public 全てのクラスからアクセス可能
protected 自分自身、同一パッケージのクラス、サブクラスからアクセス可能
(なし) 自分自身、同一パッケージのクラスからアクセス可能
private 自分自身のみアクセス可能

みだりにpublicで宣言せず、必要がなければ狭い範囲になるようアクセス修飾子を付与してください。

7.2.インスタンス変数は原則 private にしてください

インスタンス変数はクラス外に露出するべきではありません。 原則privateとしてください。

例外的に、フレームワークの制約でprivate以外にしなくてはならない場合は適切なアクセス修飾子を付与してください。

また、抽象クラスを作成してサブクラスで参照させたいインスタンス変数がある場合はprotectedにしてください。 ただし、その場合でもみだりにインスタンス変数を公開するのではなく、メソッドを併用することでインスタンス変数をprivateにできないか検討してください。

7.3.ローカル変数はできるだけ狭いスコープで使用してください

ローカル変数のスコープができるだけ狭くなるように利用する場所と近い位置で宣言をしてください。

//NG
final String text = ...

callMethod1();
callMethod2();
callMethod3();
.
.
.
callMethodN();

//textを使わない処理が延々と続いた後に初めてtextを使う処理が登場
useText(text);
//OK
callMethod1();
callMethod2();
callMethod3();
.
.
.
callMethodN();

//textを宣言してすぐに使用している
final String text = ...
useText(text);

また、ローカル変数のスコープはブロック単位です。 特定のブロック内でしか使用しないローカル変数は該当のブロック内で宣言してください。

//NG
//if文のブロック内でしか使用されないのにブロック外で宣言されている
final String text = ...
if (isSuccess(result)) {
    useText(text);
}
callOtherMethod();
return;
//OK
//if文のブロック内で宣言されている
if (isSuccess(result)) {
    final String text = ...
    useText(text);
}
callOtherMethod();
return;

7.4.なるべく再代入は避けて、そのためにfinalを活用してください

コードは「変更可能な状態」が少なければ少ないほど、全体の把握がしやすく理解しやすい傾向にあります。

変数を再代入するということは、知らないうちに「変更可能な状態」を作っていることになります。

String value = "hello";

...

//変数の再代入をすると「変更可能な状態」となり、コードを読んでいる途中で
//「今この変数の値は何か?」を常に気にする必要が出てくる
value = "world";

コードを読むときに把握すべきことを減らしましょう。 そのために変数の再代入は避けましょう。

変数の再代入を避けるためにfinalを活用してください。 変数を宣言する際にfinalを付けると、その変数は再代入不可になります。

final String value1 = "hello";

...

//再代入不可なので次のコメントアウトしているコードはコンパイルエラーになる
//value1 = "world";

//既存の変数に再代入せずに、新しい変数を導入する
final String value2 = "world";

ローカル変数だけではなく、フィールドにもfinalを付けて再代入不可に出来ないか検討してみてください。 フレームワークの制約などで必ずsetterを定義しないといけない場合もありますが、そうでない場合はなるべくフィールドも再代入不可にしてください。

7.5.なるべく引数の状態を変えないでください

コードは「状態を変更する箇所」が局所的であればあるほど、全体の把握がしやすく理解しやすい傾向にあります。

引数の状態を変更してしまうと、状態を変更する箇所が広がってしまうことになります。

//※これはやらない方が良い非推奨コードの例
//
//消費税計算をした後に引数自身に税をセットしている
//なるべくなら消費税計算をするだけにとどめて、itemに税をセットするのは呼び出し元が行った方が良い
public BigDecimal calculateTax(final Item item) {
    BigDecimal tax = item.getPrice().multiply(taxRate);
    item.setTax(tax);
    return tax;
}

privateメソッドであればクラス内に閉じているので影響範囲が限られていますが、publicprotectedなどのクラス外に公開されるメソッドではなるべく引数の状態を変更しないことを推奨します。

7.6.戻り値として null を使いたい場合、Optional の使用を検討してください

値が「無い」状態を表す手段としてnullを使う方法と、Java 8から導入されたjava.util.Optionalを使う方法があります。

戻り値をjava.util.Optionalにすると、値を返さない場合があるメソッドだということをシグネチャで表現できます。

//値を返さない場合があることがメソッドのシグネチャから読み取れる
public Optional<String> maybeReturnValue() {
    ...
    if (...) {
        return Optional.of(value);
    }
    return Optional.empty();
}

//(Optionalではないので)必ず値を返すことがメソッドのシグネチャから読み取れる
public String mustReturnValue() {
    ...
}

nullは値がある場合と同じ型で表現できるので、値を返さない場合があるメソッドだということをシグネチャで表現できず、Javadocへの記載などで対応しなくてはいけません。

//値を返さない場合があることがメソッドのシグネチャから読み取れない
public String maybeReturnValue() {
    ...
    if (...) {
        return value;
    }
    return null;
}

//必ず値を返すことがメソッドのシグネチャから読み取れない
public String mustReturnValue() {
    ...
}

java.util.Optionalを使用すると、メソッドの呼び出し元で戻り値の存在チェックをするべきかどうかを型で表現できるので、コンパイラーのサポートを受けられて安全です。 値を返さない場合があるメソッドでは戻り値をjava.util.Optionalにすることを検討してみてください。

//Optionalを使用したメソッドの場合
//戻り値が無い場合を考慮しないといけないことが型を見れば分かる
final Optional<String> optional = maybeReturnValue();
final String value = optional.orElse(defaultValue);
//nullを使用したメソッドの場合
//戻り値が無い場合を考慮しないといけないことが型を見ても分からない
//※nullチェックが漏れていてもコンパイルエラーにならず発見が遅れる
String value = maybeReturnValue();
if (value == null) {
    value = defaultValue;
}

7.7.コレクションのように型パラメーターを取るクラスを使用する場合は適切な型をバインドしてください

java.util.List<E>java.util.Map<K, V>のようなコレクションやjava.util.Optional<T>は、型パラメーターが定義されています。 このようなクラスを使用する場合は適切な型をバインドしてください。

適切な型をバインドすることでコンパイラーによる型チェックを活かせます。

//NG
//型パラメーターがバインドされていないためNG
final List raw = new ArrayList();
//バインドされた型が抽象的すぎるためNG
final List<Object> objects = new ArrayList<>();
//OK
//具体的な型をバインドしているためOK
final List<Item> items = new ArrayList<>();

final String item = ...
items.add(item); //バインドされた型と不一致なのでコンパイルエラーになる

final Item item = ...
items.add(item); //バインドされた型と一致するのでコンパイルが通る

7.8.コレクションの処理はStream APIを使用して簡潔に書くことを検討してください

Java 8から導入されたStream APIを使用すればjava.util.Listjava.util.Setなどのコレクションを簡潔なコードで処理できる場合があります。

Stream APIはfiltermapcollectといったメソッドを使用して各要素に対する操作を小さく設定できます。 そのため、各要素に対してどのような処理をするのかが分かりやすくなる傾向にあります。

メソッド 説明 コード例
filter 条件に合う要素だけに絞り込む stream.filter(x -> x % 2 == 0) //偶数だけに絞り込む
map 要素を変換する stream.map(x -> x.getClass()) //Classに変換する
collect CollectorによってStreamを変換する stream.collect(Collectors.joining(", ")) //要素をカンマ区切りの文字列に変換する

その他のメソッドはjava.util.stream.StreamのJavadocで確認してください。

Stream APIを使用したコード例と拡張for文を使用したコード例を次に示します。 どちらも従業員のリストから職種がプログラマーの従業員だけに絞り込んで平均年齢を算出しています。

Stream APIを使用したコード例の方が、どのような処理を積み重ねて結果を得ているのかが分かりやすく感じないでしょうか。

//Stream APIを使用したコード例
final List<Employee> employees = ...
final IntSummaryStatistics statistics = employees.stream()
        //職種がプログラマーだけに絞る
        .filter(emp -> emp.getJobCategory().equals(programmer))
        //年齢を抽出
        .mapToInt(emp -> emp.getAge())
        //集計する
        .summaryStatistics();
//平均を算出
final double average = statistics.getAverage();
//拡張for文を使用したコード例
final List<Employee> employees = ...
double tempAge = 0;
int tempSize = 0;
for (final Employee employee : employees) {
    //職種がプログラマーだけに絞る
    if (employee.getJobCategory().equals(programmer)) {
        //年齢を抽出して一時変数へ足し込む
        tempAge += employee.getAge();
        //平均を求めるため分母となる数をインクリメント
        tempSize++;
    }
}
//平均を算出
final double average = tempAge / tempSize;

必ず簡潔になるとは限りませんし、コードの読みやすさ・分かりやすさは主観によるものなので強制ではありませんが、コレクションを処理する際はStream APIを使用することを検討してみてください。

7.9.チェック例外のスローを伴う処理はStream APIではなく拡張for文を使用して実装してください

Stream APIはコレクションの操作を簡潔に書けますが、チェック例外のスローが宣言されているメソッドを呼び出す場合はラムダ式の中でtry-catchを書かなくてはいけません。 せっかく簡潔に書けるはずのStream APIがtry-catchによって煩雑なコードになってしまいます。 そのため、チェック例外のスローを伴う操作が含まれる場合は拡張for文を使用してください。

ファイルの入出力では多くの場合にjava.io.IOExceptionがスローされます。 コレクション操作に伴ってファイルを処理する場合は拡張for文を使用すると良いでしょう。

//NG
final List<String> heads = files.stream()
        .map(file -> {
            //readLineでIOExceptionがスローされる可能性がある
            try (BufferedReader in = openReader(file)) {
                return in.readLine();
            } catch (final IOException e) {
                throw new UncheckedIOException(e);
            }
        })
        .filter(head -> head != null)
        .collect(Collectors.toList());
//OK
final List<Path> files = ...
final List<String> heads = new ArrayList<>();
for (Path file : files) {
    //readLineでIOExceptionがスローされる可能性がある
    try (BufferedReader in = openReader(file)) {
        final String head = in.readLine();
        if (head != null) {
            heads.add(head);
        }
    }
}

7.10.レガシーfor文はなるべく使用せず、Stream APIか拡張for文の使用を検討してください

コレクションの要素を順次処理するにはStream APIと拡張for文が使えるので、レガシーfor文を使用する場面は通常ありません。 レガシーfor文はなるべく使用しないようにしてください。

7.11.配列全体をコピーする場合はcloneメソッドを使用してください

配列全体をコピーする場合はcloneメソッドを使用するのが最もシンプルです。

//OK
final Item[] values = ...
final Item[] copied = values.clone();

java.util.ArraysクラスのcopyOfメソッドを使用しても良いです。 このメソッドは第2引数でコピーする長さを指定できます。

//OK
final Item[] values = ...
final Item[] copied = Arrays.copyOf(values, values.length);

cloneArrays.copyOfはどちらもシャローコピーを行います。 ディープコピーをしたい場合はループしながら各要素に対してもコピー処理を行う必要があります。

//OK
final Item[] values = ...
final List<Item> temp = new ArrayList<>(values.length);
for (Item item : values) {
    temp.add(copyItem(item));
}
final Item[] copied = temp.toArray(new Item[0]);

例に示したケースではJava 8から導入されたStream APIを使用すると、より簡潔なコードになります。

//OK
final Item[] values = ...
final Item[] copied = Arrays.stream(values)
        .map(item -> copyItem(item))
        .toArray(Item[]::new);

7.12.コレクションを配列に変換する場合はtoArrayメソッドを使用してください

コレクションには配列に変換するtoArrayメソッドが用意されています。 各要素をループして配列を作ったりせず、toArrayメソッドを使用してください。

//NG
final List<Item> items = ...
final Item[] itemArray = new Item[items.size()];
int index = 0;
for (final Item item : items) {
    itemArray[index++] = item;
}
//OK
final List<Item> items = ...
final Item[] itemArray = items.toArray(new Item[0]);

//Stream APIにもtoArrayメソッドが用意されている
//Streamを配列に変換したい場合はこのメソッドを使用する
final Item[] itemArray = items.stream().toArray(Item[]::new);

このコード例ではtoArrayメソッドに渡す配列を長さ0で初期化しています。 元となるコレクションのsizeメソッドを長さに指定して初期化することも可能ですが、パフォーマンスの差はほぼありません。 そのため、どちらの初期化方法を選択しても良いですが、本規約のコード例では見やすさを考慮して長さ0で初期化しています。

7.13.配列をコレクションに変換する場合はArrays.asList、またはList.ofを使用してください

配列のユーティリティであるjava.util.Arraysクラスにはリストに変換するasListメソッドが用意されています。 各要素をループしてリストを作ったりせず、java.util.ArraysクラスのasListメソッドを使用してください。

//NG
final Item[] itemArray = ...
final List<Item> items = new ArrayList<>(itemArray.length);
for (final Item item : itemArray) {
    items.add(item);
}
//OK
final Item[] itemArray = ...
final List<Item> items = Arrays.asList(itemArray);

なお、Java 9からはjava.util.Listofメソッドが追加されたので、こちらを使用しても構いません。

//OK
final Item[] itemArray = ...
final List<Item> items = List.of(itemArray);

Java 9からはjava.util.Setにもofメソッドが追加されました。 これまで配列からjava.util.Setに変換しようとすると、一旦java.util.Listに変換してからjava.util.Setを生成したり、Java 8からはStream APIを使用して変換していました。 Java 9からは簡潔なコードで変換できるようになりました。

//Java 7までの変換方法
final Set<Item> items =  new HashSet<>(Arrays.asList(itemArray));

//Java 8からはStream APIで変換できる
final Set<Item> items =  Arrays.stream(itemArray).collect(Collectors.toSet());

//Java 9からはより簡潔に変換できる
final Set<Item> items = Set.of(itemArray);

7.14.メソッドをオーバーライドしたり、抽象メソッドを実装する場合はメソッドに@Overrideを付けてください

サブクラスでスーパークラスのメソッドをオーバーライドする場合、サブクラス側のメソッドに@Overrideを付けてください。 @Overrideを付けておくと、コンパイラーが本当にオーバーライドされたメソッドであるかチェックしてくれます。

//NG
public class SuperClass {

    public void someMethod() {
        ...
    }
}

public class SubClass extends SuperClass {

    //メソッド名が間違っておりオーバーライドになっていない。
    //コンパイルは通るのでミスに気付きにくい。
    public void sameMethod() {
        ...
    }
}
//OK
public class SuperClass {

    public void someMethod() {
        ...
    }
}

public class SubClass extends SuperClass {

    //@Overrideを付けているとオーバーライドになっていない場合はコンパイルエラーになる。
    //コンパイル時にミスに気づくことができる。
    @Override
    public void sameMethod() {
        ...
    }
}

Java 6からはインターフェースで宣言された抽象メソッドを実装する際にも@Overrideが使用できるようになりました。 スーパークラスのメソッドをオーバーライドする際と同様に@Overrideを付けるようにしてください。

//OK
public class SomeAction implements Runnable {

    @Override
    public void run() {
        ...
    }
}

8.使用可能なAPI

業務アプリケーションを開発するのに十分なだけのAPIを厳選することで、品質を確保するための規約です。

8.1.使用可能な標準APIを使用して実装してください

Java標準ライブラリのうち使用可能なAPIについては、Java標準ライブラリ使用可能APIを参照してください。

また、使用不許可APIチェックツールを使用してチェックを行えますので活用してください。


9.Nablarchライブラリ

Nablarchではアプリケーションプログラマー向けに公開するAPIを厳選しています。 それらのAPIに絞って開発することで、品質を確保するための規約です。

9.1.使用可能なNablarchのAPIを使用して実装してください

Nablarchのライブラリのうち使用可能なAPIについては、アプリケーションプログラマー向けのJavadocを参照してください。

業務要件上Javadocに記載のないAPIを使用する必要がある場合は、アーキテクトへ相談するようにしてください。

また、使用不許可APIチェックツールを使用してチェックを行えますので活用してください。

You can’t perform that action at this time.