Javaコーディング規約

規約条目ランク

No. 项目
⭐ ⭐ ⭐ ⭐ ⭐ ほぼ無条件に適用する
⭐ ⭐ ⭐ ⭐ 特に理由がない限り、適用する
⭐ ⭐ ⭐ プロジェクトの方針に左右されるが、大体のケースで適用する
⭐ ⭐ 適用しなくても全体には影響しない
⭐ このようなやり方もあるという紹介

コーディングの心得5ヵ条

  1. 見やすさを重視せよ
  2. ネーミングはわかりやすく
  3. サンプルを鵜呑みにしない
  4. おなじコードを2度書かない
  5. 役割は1つに

1.ネーミングルール

1.1 全般

1.1.1 英語を使う

⭐ ⭐ ⭐

【説明・動機】
  名前をつける時は基本的にすべて**英語**を使ってください。
  これを統一することによって、他の人にも**読みやすい**コードとなります。

1
2
3
4
5
6
7
違反例
      public boolean hasZaiko(){//違反
      }

修正例
      public boolean hasStock(){//修正済み
      }

1.1.2 大文字/小文字の違いで名前を区別しない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Javaの仕様で、大文字と小文字は別の文字として扱われますが、その違いだけで区別される名前を付けないでください。大文字/小文字の違いだけでは、利用者が使用する変数・メソッド・クラスなどを簡単に間違える可能性があります。
  これを統一することによって、他の人にとっても読みやすいコードとなります。

1
2
3
4
5
6
7
違反例
      private int number;
      private int Number;//違反

修正例
      private int lineNumber;
      private int trainNumber;//修正済み

1.2 パッケージ

1.2.1 パッケージ名は小文字で統一する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  特に取り決めがない限り、パッケージ名はすべて小文字で統一してください。
  これは、Javaの一般的なルールです。

1
2
3
4
5
    違反例
      jp.co.isid.FrameWork.Banking.ACCOUNT//違反

    修正例
      jp.co.isid.framework.banking.account//修正済み

⭐ MobileOfficeシステムで追加する【パッケージ名称ルール】
  Vシリーズの開発においては、【開発環境構築マニュアル】の通り、Android SDKで使用する名称についてはLibrary系、Utility系、Application系の3つに分類され、それぞれ以下の通りの命名規則を適用することになっています。

分類 名称
Library系 jp.casio.vx.framework.xxx
Utility系 jp.casio.vx.util.xxx
Application系 jp.casio.vx.app.xxx

1.2.2 パッケージ名には意味のある名前をつける

⭐ ⭐ ⭐

【説明・動機】
  パッケージ名は、パッケージの内容にどのようなクラスがあるか推測しやすい名前をつけてください。
  よくパッケージ名に機能ID(例えばssau45690のような番号)や連番(例えばad0001など)を用いてしまいがちですが、新しくプロジェクトに参画したメンバーや将来保守する人には理解しづらく、得策ではありません。
  「パッケージ内に何か含まれるか」がきちんとわかる名前を付けるよう心がけましょう。

1
2
3
4
5
    違反例
    jp.co.isid.framework.banking.a00001//違反

    修正例
    jp.co.isid.framework.banking.account//修正済み

1.2.3 パッケージ名は省略しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  パッケージ名は、多少長くなっても、出来るだけ省略せず、わかりやすい名前をつけてください。
  省略する場合は、その省略語がプロジェクトに関わる人たち全員がわかるようにしましょう。

1
2
3
4
5
    違反例
    jp.co.isid.fw.bkg.acc//違反

    修正例
    jp.co.isid.framework.banking.account//修正済み

1.2.4 サブパッケージ名の重複はしてもよい

⭐ ⭐ ⭐ ⭐

【説明・動機】
  親パッケージが異なっているのであれば、同一のサブパッケージ名が存在してもかまいません。

1
2
3
    サンプル
    jp.co.isid.framework.banking.entity //銀行のアカウント
    jp.co.isid.framework.trading.entity //取引アカウント

1.2.5 パッケージの詳細はpackage-info.javaに記載する

⭐ ⭐ ⭐

【説明・動機】
  パッケージ内にどのようなクラスが含まれ、どういう役割のクラスが含まれるかなどはJava5から導入されたpackage-info.javaのドキュメンテーションコメントに記載してください。
  このpackage-infoを使い、パッケージ内の情報を明確に記載しましょう。またサブパッケージの概略も記載すると、パッケージ構成への理解が深まり、コードの保守性も高まります。積極的に使いましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    サンプル
    /**
      * このパッケージはXXXプロジェクトの業務YYYのルートパッケージです。
      * YYYとは...
      *
      * YYYは以下の要素で構成されています。
      *  詳細は以下パッケージを参照してみてください。
      * <ul>
      * <li>jp.co.isid.xxx.yyy.controller</li>
      * <li>jp.co.isid.xxx.yyy.entity</li>
      * <li>jp.co.isid.xxx.yyy.logic</li>
      * <li>jp.co.isid.xxx.yyy.util</li>
      * </ul>
      * 
      */
    package jp.co.gihyo.javarulebook.chapter1_2_5;

⭐ MobileOfficeシステムで追加する【コメントの記述】

  • 全てのファイルの先頭には、まずコピーライト文を入れる。(Apache 2.0 Licenceの場合はライセンス文を入れる。)
  • 次に、authorとクラスの概要を記述する。(authorはeclipseではAlt + Shift + Jにより挿入。)
  • その下に更新者、更新履歴、更新日付、更新内容を記述する。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    /*
     * Copyright (C) 2013 The CASIO Android Project
     *
     *
     */
    package jp.casio.vx.xxx.yyy;

    import java.util.List
    /**
     * @author 〇〇〇<br>
     *         概要   :XXXXXXXXXXXXXXXX(ページリンクのアダプター)<br>
     *         作成日付:9999/99/99<br>
     * <br>
     *         更新者  :XXXXXXXXXXXXXXX<br>
     *         更新履歴 :【1】<br>
     *         更新日付 :9999/99/99<br>
     *         更新内容 :XXXXXXXXXXXXXXXXXXXXXX<br>
     * 
     *         更新者  :XXXXXXXXXXXXXXX<br>
     *         更新履歴 :【2】<br>
     *         更新日付 :9999/99/99<br>
     *         更新内容 :XXXXXXXXXXXXXXXXXXXXXX<br>
     */
    public class PageLinkAdapter extends ArrayAdapter<Page>{
        ................
    }

1.3 クラス・インタフェース

1.3.1 クラス名は役割を表す名前にする

⭐ ⭐ ⭐

【説明・動機】   クラス名には、機能IDや連番など、内容を想定しにくい名前を使わないでください。   クラス名に意味のある名前を付ければ内容が直感的にわかり、他の人にも読みやすいコードとなります。

1
2
3
4
5
6
7
    違反例
    public class A00001 {//違反
    }

    修正例
    public class LoginAction {//修正済み
    }

1.3.2 クラス名は単語の先頭を大文字にする

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  クラス名は、先頭を大文字にしてください。クラス名が複数の単語で構成されている場合は、各単語の先頭(区切り)を大文字にしてください。これは、Javaの一般的なルールです。

1
2
3
4
5
6
7
    違反例
    public class sampleClass {//違反
    }

    修正例
    public class SampleClass {//修正済み
    }

1.3.3 例外クラス名には末尾に「Exception」をつける

⭐ ⭐ ⭐ ⭐

【説明・動機】
  例外クラス名には、名前の最後に「Exception」をつけてください。ランタイム例外のときは「RuntimeException」と末尾につけるとよりわかりやすくなります。
  これを統一することによって、「このクラスは例外クラスである」ということが他の人にもわかりやすくなります。

1
2
3
4
5
6
    違反例
    public class Sample extends Exception {//違反
    }
    修正例
    public class SampleException extends Exception {//修正済み
    }

1.3.4 インターフェイス名と実装クラス名の対応関係を明確にする

⭐ ⭐ ⭐

【説明・動機】
  クラス名の命名規則は、基本的にインタフェースに準じます。例えばインタフェース名をSampleとすると、その実装クラス名はSampleImplなどとします。
  または実装クラスをSampleとして、インタフェース名の先頭に「I」をつけてISampleとするなど、プロジェクトで統一してください。これを統一することによって、他の人にも読みやすいコードとなります。

1
2
3
    サンプル
    public class SampleImpl implements Sample {
    }

1.3.5 抽象クラス名と実装クラス名の対応関係を明確にする

⭐ ⭐ ⭐

【説明・動機】
  抽象クラス名と実装クラス名の対応関係は明確に名前で表現しましょう。例えば、クラス名をSampleとするならば。その親抽象クラス名はAbstractSampleとしてください。このように統一することによって、「このクラスは抽象クラスである」ということが他の人にもわかりやすくなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    サンプル
    public abstract class AbstractSample {
      public abstract String getName();

      public class Sample extends AbstractSample {
        @Override
        public String getName() {
          return "sample";
        }
      }
    }

1.3.6 能力付加型のインタフェース名は最後に"able"を付ける

⭐ ⭐

【説明・動機】
  クラスに対してある能力を付加するようなインタフェース(例えばRunnable,Cloneable,Closeableなど)を定義する場合は、その能力を示す形容詞(~able)を名前としてください。これを統一することによって、他の人にも読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    サンプル
    public interface Movable {
      int moveLeft(int from, int dest);

      int moveRight(int from, int dest);

      int moveForward(int from, int dest);

      int moveBack(int from, int dest);
    }

    public class Car implements Movable {
    }

1.4 テストクラス

1.4.1 単体テストを作成する場合にはJUnitなどのテストフレームワークを使う

⭐ ⭐ ⭐ ⭐

教育資料:[[テストコードの作成について]]
【説明.動機】
  単体テストを作成する場合は、Junit3やJunit4などの単体テストフレームワークを使いましょう。単体テストフレームワークには、複数テストの自動実行やテスト前後の処理呼び出しなど、単体テストを補助する機能が提供されており、効率のに単体テストを行うことが出来ます。
  以下は最もよく使われているJunit3と、次パーションのJUnit4の例です。JUnitは以下からダウンロード出来ます。
http://junit.org/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
サンプル
    //JUnit3.xの例
public class EmployeeTest extends TestCase {
    public void testCheckAssign() throws Exception {
        Employee e = new Employee(123);
        e.assign(8990);
        asserttrue(e.isAssigned());
    }
}
// JUnit4.xの例
public class EmployeeTest {
@test
public void checkAssign() {
    Employee e = new Employee(123);
    e.assign(8990);
    asserttrue(e.isAssigned());
}
}

1.4.2 テストクラス名は「テスト対象クラス名+Test」にする

⭐ ⭐ ⭐ ⭐

【説明.動機】
  テストクラス名は、「テスト対象クラス名+Test」としてください。これを統一することによって、他の人にもテストコードであることがわかりやすくなります。

1
2
3
サンプル
public class SampleTest extends TestCase {
}

1.5 メソッド

1.5.1 メソッド名は動詞で始まる適切な名前をつける

⭐ ⭐ ⭐ ⭐ ⭐

【説明.動機】
  メソッド名は動詞で始まる適切な名前を付けてください。例えば、「そのメソッドがどのような処理をするのか」がメソッド名から理解出来ないメソッドを作ってはいけません。何をするメソッドなのかを明確に記述するようにしましょう。これを統一することによって、他の人にも読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    違反例
    public class BadSample {
        protected int number;

        public BadSample() {
        }
        // 違反
        public void nnn(int number) {
            this.number = number;
        }
        public int getNumber() {
            return number;
        }
    }

    修正例
    public class GoodSample {
        protected int number;
        public GoodSample() {
        }
        // 修正済み
        public void setNumber(int number) {
            this.number = number;
        }
        public int getNumber() {
            return number;
        }
    }

1.5.2 メソッド名は区切りのみ大文字にする

⭐ ⭐ ⭐ ⭐

【説明.動機】
  メソッド名は、ひとつの単語の場合はすべて小文字で記述してください。メソッド名が複数の単語で構造されている場合は、2語目以降の単語の先頭を大文字にしてください。
  これは、Javaの一般的なルールです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    違反例
    public class BadSample {
        // 違反
        public int executesample() {
            return 0;
        }
    }

    修正例
    public class GoodSample {
        // 修正済み
        public int executeSample() {
            return 1;
        }
    }

1.5.3 オブジェクトを生成するメソッド名は「create+オブジェクト名」にする

⭐ ⭐ ⭐

【説明・動機】
  オブジェクトを生成するメソッド(ファクトリメソッド)の名前は「create」で始め、その後にこのメソッドで生成されるオブジェクト名を続けてください。
  これを統一することによって、他の人にも読みやすいコードとなります。

1
2
3
4
5
6
7
8
    サンプル
    public class SampleCreator {
        // オブジェクトを生成するファクトリメソッドは
        // createからはじめるメソッド名にする
        public static Sample createSample(int no, String name) {
            return new Sample(no, name);
        }
    }

1.5.4 変換メソッド名は「to+変換先オブジェクト名」または「convert+変換先オブジェクト名」にする

⭐ ⭐ ⭐

【説明・動機】
  オブジェクトを別のオブジェクトに変換するメソッド(コンバータメソッド)は、「to」または「convert」で始め、その後に変換後のオブジェクトの名前を続けてください。
  これを統一することによって、他の人も読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    サンプル
    public class DateUtil {
        // Dateオブジェクトに変換する
        public static Date toDate(String s) {
            try {
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy/mm/dd");
                return formatter.parse(s);
            } catch (ParseException e) {
                throw new ParseRuntimeException(e);
            }
        }
    }

1.5.5 booleanの戻り値を返すメソッド名はtrue/falseの状態がわかるようにする

⭐ ⭐ ⭐

【説明・動機】
  boolean変数を返すメソッド名は、その返り値のtrue/falseがどのような状態を指しているのかわかる名前を付けてください。記述形式は、YesまたはNoを表す疑問文の形式にすると良いでしょう。
  以下が一般的な命名例です。

1
2
3
4
5
6
7
|命名|例|
|---|---|
|is+形容詞|isEmpty()|
|can+動詞|canGet()|
|has+過去分詞|hasChaged()|
|三単元動詞|contains()|
|三単元動詞+名詞|containsKey()|
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    サンプル
    public class Employee {
        // ……
        public boolean isAsleep() {
            return asleep;
        }
        public boolean canSpeakMultiLanguages() {
            return lauguages != null && 1 < lauguages.length;
        }
        public boolean hasExpired() {
            return expired == false;
        }
    }

1.5.6 英語の対義語を意識する

⭐ ⭐ ⭐

【説明・動機】
  役割、機能などが対になっているメソッドの名前は英単語の対義語を意識したものにしてください。例えば対義語は以下のようになります。

send receive
start stop
register unregister
put get

これによって、他の人にも読みやすいコードとなります。

1
2
3
4
5
6
    サンプル
    //send/receiveで対になっている
    public interface MessageHandler {
        boolean send(String message);
        String receive();
    }

1.6 引数

1.6.1 コンストラクタおよびメソッドのパラメータ名にはわかりやすい名前をつける

⭐ ⭐ ⭐

【説明・動機】
  コンストラクタやメソッドのパラメータ名には、わかりやすい名前をつけてください。
  パラメータ名とメンバ変数名を同一とする場合、メンバ変数はthis参照としてください(thisがない場合、正しくメンバ変数にセットされません)。
  これによって、どのメソッド引数をどのメンバに代入するかがわかりやすくなり、他の人も読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    違反例
    public class BadSample {
        private String name;

        public BadSample(String str) {
            this.name = str; // 違反
        }

        public void setName(String str) {
            this.name = str; // 違反
        }

        public String getName() {
            return name;
        }
    }

    修正例
    public class GoodSample {
        private String name;

        public GoodSample(String name) {
            this.name = name; // 修正済み
        }

        public void setName(String name) {
            this.name = name; // 修正済み
        }

        public String getName() {
            return name;
        }
    }

1.7 変数全般

1.7.1 定数は不変にし、すべて大文字で、区切りは「_」にする

⭐ ⭐ ⭐ ⭐

【説明・動機】
  定数やenum定数は、すべで大文字で記述してください。また定数はすべでstatic final宣言してください。
  定数またはenum定数が複数の単語で構成されている場合、各単語の間は「_」(アンダースコア)で区切ってください。
  これを統一することによって、他の人にも読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    違反例
    public class BadSample {
        public static final int SampleValue = 1; // 違反

        public enum Language {
            japanese, english, chinaese; // 違反
        }

        public static void main(String[] args) {
            System.out.println(SampleValue);
            System.out.println(Language.english);
        }
    }

    修正例
    public class GoodSample {
        public static final int SAMPLE_VALUE = 1; // 修正済み

        public enum Language {
            JAPANES, ENGLISH, CHINAESE; // 修正済み
        }

        public static void main(String[] args) {
            System.out.println(SAMPLE_VALUE);
            System.out.println(Language.JAPANES);
        }
    }

1.7.2 変数名は役割を反映させる

⭐ ⭐ ⭐

【説明・動機】
  変数にはその役割が反映された名前を付けてください。
  これを統一することによって、他の人にも読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample {
        private String str1 = "server1"; // 違反
        private String str2 = "client1"; // 違反

        public static void main(String[] args) {
            BadSample sample = new BadSample();
            System.out.println(sample.str1);
            System.out.println(sample.str2);
        }
    }

    修正例
    public class GoodSample {
        private String serverName = "server1"; // 修正済み
        private String clientName = "client1"; // 修正済み

        public static void main(String[] args) {
            GoodSample sample = new GoodSample();
            System.out.println(sample.serverName);
            System.out.println(sample.clientName);
        }
    }

1.8 ローカル変数

1.8.1 スコープが狭い変数名は、省略した名前でもよい

⭐

【説明・動機】
  本来、変数名には役割を反映した名前を用いるべきです。しかし、スコープが狭い変数は識別が比較的容易なため、型名の略など、省略した名前を用いてもかまいません。
  省略した名前はIDEに生成させると便利です。例えばEclipseでは以下の操作で型から自動的に変数名を決めてくれます。このような機能を利用すると良いでしょう。

  • Ctrl+space
  • Ctrl+1
  • Alt+Shift+L

ただし、以下の場合は変数名を省略しないほうが良いでしょう。

  • 省略しなくても適切な長さになる場合
  • 今後の修正で変数のスコープが広がる可能性がある場合
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    違反例
    public class BadSample {
        public static void main(String[] args) {
            InputStream a = getInputStream(PATH); // 違反
            InputStreamReader isr = null; // 違反
            try {
                isr = new InputStreamReader(a);
                BufferedReader br = new BufferedReader(isr);
                // スコープが狭いため、省略名で問題ない
                for (String s = br.readLine(); s != null; s = br.readLine()) {
                    System.out.println(s);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                // 終了処理
            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            InputStream inputStream = getInputStream(PATH); // 修正済み
            InputStreamReader reader = null; // 修正済み
            try {
                reader = new InputStreamReader(inputStream);
                BufferedReader br = new BufferedReader(reader);
                // スコープが狭いため、省略名で問題ない
                for (String s = br.readLine(); s != null; s = br.readLine()) {
                    System.out.println(s);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                // 終了処理
            }
        }
    }

1.8.2 for文のループカウンタは、ネストごとに「i」「j」「k」...を使う

⭐ ⭐ ⭐

【説明・動機】
  for文のループカウンタには、その階層ごとに「i」「j」「k」という文字をこの順序(アルファベットで順)で使用してください。
  これを統一することによって、他の人にも読みやすいコードとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    違反例
    public class BadSample {
        public static void main(String[] args) {
            for (int j = 0; j < 2; j++) {
                for (int n = 0; n < 3; n++) {
                    for (int t = 0; t < 5; t++) {
                        System.out.println(j + "," + n + "," + t);
                    }
                }
            } // 違反
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 3; j++) {
                    for (int k = 0; k < 5; k++) {
                        System.out.println(i + "," + j + "," + k);
                    }
                }
            } // 修正済み
        }
    }

1.9 リソースファイル

1.9.1 画像ファイル

⭐

【説明・動機】
  アイコン、背景画像等の画像ファイルは res/drawable フォルダに格納される。ファイル名は以下の規則に則る。
【使用ファイル】
  JPG,PNG
  アイコン画像、背景画像は基本的にPNGファイルを利用する。
  (対応形式としては、BMP,PNG,GIF,JPGに対応している。)
【画像ファイルの命名規則】
  識別子①_名前_連番.拡張子
  識別子① = 画像を分類するための記号。必要に応じて複数組み合わせる。
  名前/連番 = 個別の画像を判別するための名前・連番。必要に応じて複数組み合わせる。
【識別子の例】

識別子 内容
bg 背景画像
btn ボタン
ico アイコン
img 画像全般

・[番号]
  番号は2桁の数字で示す。
  また、ページ内で唯一のものであるもの(ロゴなど)のものは省略する。

【ボタンON,OFF時の画像の切り替え】
  ボタンのON,OFFで画像を切り替えたい場合は、画像ファイルの他に以下のようなxmlファイルを用意する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--     pressed -->
    <item
        android:state_pressed="true"
        android:drawable="@drawable/ico_back_on">
    <!--     released -->
    <item
        android:state_pressed="false"
        android:drawable="@drawable/ico_back_off">
    </selector>

この時、画像ファイルの名前は、xmlファイルは上記規則に従った名前にし、上記規則に従った名前 + (アンダーバー) + on,off とすること。
例)
ON時の画像ファイルの名前 : ico_back_on.png
OFF時の画像ファイルの名前 : ico_back_off.png
xmlファイルの名前 : ico_back.xml

2.プログラミングルール/基礎編

2.1 全般

2.1.1オブジェクトの参照はインタフェースを利用する

⭐ ⭐ ⭐

【説明・動機】
  オブジェクトを参照する際は、そのオブジェクトの実装クラスを用いて宣言出来ます。しかし、実装クラスに適切なインタフェースが存在 している場合は、必ずインタフェースを用いて宣言してください。
  インタフェースを利用することのメリットは、コードの柔軟性が上がることにあります。実装クラスを用いて宣言してしまった場合、後になって実装クラスを変更するとなると、すべての参照個所を変更しなければなりません。一方、インタフェースを利用して宣言していれば、インスタンスを生成する個所を変更するだけで、実装クラスを変更することが出来ます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    違反例
    public class BadSample {
        public static void main(String[] args) {
            ArrayList<String> sampleList = new ArrayList<String>();// 違反
            sampleList.add("bad_sample");
            BadSample badSample = new BadSample();
            String ret = badSample.badMethod(sampleList);
            System.out.println(ret);

            // Listの実装が変更されたとする
            LinkedList<String> otherList = new LinkedList<String>();
            // これはコンパイルエラーになる
            // badSample.badMethod(otherList);
        }

        /**
         * 違反:呼び出し元の実装クラスがArrayList以外に変更されると、
         *  このメソッドも変更しなければならない
         */
        private String badMethod(ArrayList<String> input) {
            return input.toString();
        }
    }

    修正例
    public class GoodSample {

        public static void main(String[] args) {
            List<String> sampleList = new ArrayList<String>();// 修正済み
            sampleList.add("good_sample");
            GoodSample sample = new GoodSample();
            String ret = sample.goodMethod(sampleList);
            System.out.println(ret);

            // Listの実装が変更されたとする
            List<String> otherList = new LinkedList<String>();
            //メソッドListインタフェースを引数にしているため、影響しない
            String ret2=sample.goodMethod(otherList);
            System.out.println(ret2);
        }

        /**
         * 修正済み:呼び出し元の実装クラスがArrayList以外に変更されても、
         * このメソッドには影響しない
         */
        private String goodMethod(List<String> input) {
            return input.toString();
        }

    }

2.1.2推奨されないAPIを使用しない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  「推奨されない」と指定されたクラス、メソッドなどは使用しないでください。推奨されないAPIは、将来のバージョンアップに伴い削除され、利用出来なくなる可能性があります。これらの機能が必要な際は、Javadoc内などに示されている代替案を参照して、そちらの機能が使用するようにしてください。
  推奨されないAPIかどうかはJavadocで確認出来ますが、以下の手段を利用するのもよい方法です。

  • 推奨されないAPIを利用すると警告を表示してくれるIDEやツールを利用する
  • コンパイル時に出力される警告を利用する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    違反例
    public class BadSample {
        public static void main(String[] args) {
            Date sampleDate=new Date();
            System.out.println(sampleDate.getYear());//違反
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            Calendar sampleCalendar=Calendar.getInstance();
            System.out.println(sampleCalendar.get(Calendar.YEAR));//修正済み
        }
    }

2.1.3 使われないコードは書かない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  使わないprivateメソッドや変数、あるいはローカル変数が存在すると、コードの可読性は低下し、理解を妨げることになります。コードが使用されているかどうかを調べ、使用されていない場合は削除してください。必要のあるものについては、それが使用されるようコードを見直してください。
  「将来的に利用する可能性がある」という理由でコードを残したい場合は、バージョン管理ツールで履歴を残し、現在のコードからは削除してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    違反例
    public class BadSample {
        public static void main(String[] args) {
            new BadSample().usedMethod();
        }
        private void usedMethod(){
        }
        private void unusedMethod(){
            //違反  使われていないメソッド
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            new GoodSample().usedMethod();
        }
        private void usedMethod(){
        }
        //削除済み
    }

2.1.4 適切なアクセス修飾子を使って宣言する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  private、publicなど、アクセス修飾子の意味を十分理解し、クラス、メソッド、変数、定数などは適切なアクセス修飾子で宣言するようにしてください。
  通常、EclipseなどのIDEのコードアシスト機能を使用すると、アシストの一覧にはそのクラスからアクセス出来る範囲しか表示されません。そのため、アクセス修飾子を適切に宣言することで、クラスの利用者に対して使用してほしい部分だけを公開することが出来ます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    違反例
    public class BadSample {
        public void execute() {
            internalMethod();
        }
        /**
         * 違反:内部利用のみを想定したメソッドがpublic宣言されている
         */
        public void internalMethod() {
        }
    }

    修正例
    public class GoodSample {
        public void execute() {
            internalMethod();
        }

        /**
         * 修正済み:内部利用のみメソッドはprivateにすることで、
         * 外部からのアクセスを制御出来る
         */
        private void internalMethod() {
        }
    }

2.1.5 finalを適切に利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  以下のような変化のないもの/変化させたくないものについてはfinalで宣言するようにしてください。変更されないことが保証されます。

  • 継承されないクラス
  • オーバーライドされないメソッド
  • 値の変わらない変数

なお、定数(プリミティブ型もしくはString型の変数のうち、final宣言した変数)については、コンパイル時に、その定数を参照しているクラスファイル内に値が直接展開されます(インライン展開されます)。従って、一度決めたら変わることがない値を定数として宣言するようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    違反例
    public class BadSample {
         public static int DEFAULT_INTERVAL=60;
         //違反
    }
    修正例
    public class GoodSample {
         public static final int DEFAULT_INTERVAL=60;
         //修正済み
    }

2.1.6 プリミティブ型と参照型の違いを意識する(定義編)

⭐ ⭐ ⭐ ⭐ ⭐

教育資料:[[プリミティブ型と参照型の引数渡すの区別]]
【説明・動機】
  プリミティブ型および参照型は、主に下記3点で特性が異なります。

  • 実行速度とリソース消費
  • デフォルト値(インスタンス変数宣言時)
  • データ構造

それぞれの相違点は以下のとおりです。
実行速度とリソース消費

  • プリミティブ型⇒使用する際にオブジェクトを新たに生成する必要がない
  • 参照型⇒使用する際にオブジェクトを新たに生成する必要がある

プリミティブ型を使うほうが時間とリソースを節約出来る一方。参照型はオブジェクトとして扱えるためjavaのCollectionなどのAPIとの相性は良いです。
  デフォルト値(インスタンス変数宣言時)

  • プリミティブ型⇒型によって異なる
  • 参照型 ⇒ null|

「参考」プリミティブ型のデフォルト値

プリミティブ型 デフォルト
boolean false
byte (byte)0
char '\u0000'
double 0.0d
float 0.0f
int 0
long 0L
short (short)0

データ構造
プリミティブ型と参照型の対応関係は以下のようになっています。

プリミティブ型 参照型
boolean Java.lang.Boolean
byte Java.lang.Byte
char Java.lang.Character
double Java.lang.Double
float Java.lang.Float
int Java.lang.Integer
long Java.lang.Long
short Java.lang.Short

2.1.7 プリミティブ型と参照型の違いを意識する(引数編)

⭐ ⭐ ⭐ ⭐ ⭐

教育資料:[[プリミティブ型と参照型の引数渡すの区別]]
【説明・動機】
  メソッドに渡される引数がプリミティブ型である場合と参照型である場合では、以下のように挙動が異なります。

  • プリミティブ型である場合 ⇒ 値そのものが渡される
  • 参照型である場合 ⇒ インスタンスへの参照が値として渡される

したがって、複数箇所からそのインスタンスが参照されているような場合、参照型の引数の状態をメソッド内でむやみに変更すると、不整合を引き起こすことがあります。参照型の引数を持つメソッドでは、渡された引数を直接操作する際に注意してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    サンプル
    import java.awt.Point;

    public class Sample {
        public static void main(String[] args) {
            int intValue = 0;
            Point point = new Point(0, 0);
            Sample sample = new Sample();
            sample.printValue(intValue,point);
            sample.modifyValue(intValue,point);
            sample.printValue(intValue,point);
        }
        private void modifyValue(int value,Point point) {
            //引数の値を変更する
            value=10;
            point.setLocation(55, 77);
        }
        private void printValue(int value,Point point) {
            StringBuffer buffer=new StringBuffer();
            buffer.append(value);
            buffer.append(" : ");
            buffer.append(point);
            System.out.println(buffer);
        }
    }

【解説】
実行結果は以下のとおりです。
0 : java.awt.Point[x=0,y=0]
0 : java.awt.Point[x=55,y=77]
  上記の例では、プリミティブ型の変数intValueと、参照型の変数pointを扱っています。実行結果を見るとわかるように、modifyValue()メソッドを実行する前と後では、プリミティブ型のintValueは変化していないのに対し、参照型のpointは変更されています。
  プリミティブ型(intValue)は引数として値のコードが渡され、そのコピー値に対して操作を行うため、intValue自体には影響を及ぼしません。一方、参照型(point)は引数として値>の参照が渡されるので、メソッド内での操作がpointの参照している値に影響を及ぼすことになります。

2.2 メトリクス

【解説】
  この項で定義している個数や行数はあくまでも目安であり、この数値を守ることが本来の目的ではありません。メトリクス値を導入するのは、可読性やメンテナンス性を向上させたり、メトリクス値より突出した部分がないかどうかをチェックするためです(突出している部分には何か問題があることが多いため)。
  したがって、この節で説明している内容を元に、パッケージやクラスやメソッドの責務、また実装に利用するPCの画面サイズなどを考慮し、「どの程度のメトリクス値が適切なのか」を各プロダクト・各プロジェクトごとに決定してください。

2.2.1 1メソッドの行数は約20行以下にする

⭐ ⭐

【説明・動機】
  1メソッドの行数は、メソッド内の行コメントも含めて20行程度までが理想です。多くても50行程度にとどめてください。
  これ以上行数が増えてしまう場合は、複数のメソッドに分割するなど、設計を見直してください。
  1メソッドの行数を少なくすることで1度に把握するコード量が減り、コードが把握しやすくなります。

2.2.2 1クラスの行数は約600行以下にする

⭐ ⭐

【説明・動機】
  1クラスの行数はコメントも含めて600行程度までが理想です。多くても1000行程度にとどめてください。これ以上行数が増えてしまう場合は、複数のクラスに分割するなど、設計を見直してください。
  1クラスの行数を少なくすることで、クラス構造を把握しやすくなります。

2.2.3 1クラス内のpublicメソッドは約20個以下にする

⭐ ⭐

【説明・動機】
  1クラス内の単純なアクセサを除くpublicメソッドは20個以下にとどめてください。これ以上メソッド数が増えてしまう場合は、クラスを分割するなど、設計を見直してください。
  publicメソッドが多いと、クラスを利用する側に公開する部分が多くなりを把握しにくくなります。

2.2.4 1パッケージ内のクラス数は20個以下にする

⭐ ⭐

【説明・動機】
  1パッケージに含まれるクラス数は20個程度までが理想です。多くでも30個程度にはとどめてください。これ以上クラス数が増えてしもう場合は、パッケージを分割するなど、設計を見直してください。
  1パッケージにさまざまな意味合いのクラスを入れて数が多くなってしまうと、そのパッケージにどういったものが入っているかわからなくなり、パッケージの意味がなくなります。「パッケージに入っているクラスが本当にそのパッケージに入っている必要があるのかどうか」を常に検討してください。

2.2.5 相互参照を極力避ける

⭐ ⭐ ⭐

【説明・動機】
  クラス間の相互参照は構造を複雑にし、可読性や保守性を大きく落とします。クラス変更時に影響を受ける箇所も多くなり、バグの原因にもなります。パッケージ間も同様で、パッケージ間で相互に参照していると、相互の依存関係で複雑度が高くなり、保守性を大きく落とします。
  相互参照には常に気をつけ、必要がない限りは循環的な構造にならないようにしてください。クラスやパッケージの相互参照は、UMLなどで図示したりすることで把握出来ます。UMLを生成するようなツールを利用し、依存関係を把握することも有効です。
  またクラス内であっても、ブロックのネストの数、込み入ったクラス/メソッド呼び出し、複合した条件部などの数が多くなれば多くなるほど、コードは複雑になります。コードが複雑になるにつれて可読性/保守性は低下し、バグが発生する可能性も高くなってしまいます。
  クラスやメソッド内が(定性的な表現になってしまいますが)複雑だと感じた場合は、再度クラス設計やメソッド設計を見直すべきでしょう。また、クラス間やパッケージ間の相互参照を見つけ出す作業は、なるべく入手ではなく、静的解析ツールなどを利用して効率的に行うようにしてください。

2.3 フォーマット

2.3.1 コードフォーマットは定義ファイルを作成し、IDEでフォーマットする

⭐ ⭐ ⭐ ⭐

【説明・動機】
  ソースコードのインデント方法や空白の入れ方など、コードのフォーマットの規約は数多くありますが、一番重要なのは「決めた規約が守られ、誰が書いたコードでも同じフォーマットになること」です。
  最近のIDEは、コードフォーマットについて細かく定義することが出来、機械的にフォーマットを整えることが出来ます。
プログラマ個々に規約を教えて規約が守られているかどうかを誰かがチェックするより、コードフォーマットの定義ファイルを配布し、IDEで自動的にフォーマットするほうがより効率的です。
  フォーマット用のテンプレートを作成し、常にフォーマットするようにしましょう。
  また、ファイルの保存時にフォーマットする機能のあるIDEもあります。こういった機能を利用すると、手間なくフォーマットが行えるので、積極的に活用しましょう。

2.3.2 return文ではカッコを使わない

⭐ ⭐

【説明・動機】
  return文では、不要な括弧は使用しないようにしてください。Javaの仕様上、return出来るオブジェクトは1つだけです。括弧を使用するとreturn文を何らかのメソッドと見間違えてしまうなど、可読性の低下につながる可能性があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    違反例
    public class BadSample {
        /**
         * 違反:return文で演算を行っているため、演算結果が確認できない
         */
        public int badSampleMethod1() {
            int a = 1;
            int b = 2;
            return (a + b);
        }

        /**
         * 違反:変数を括弧でくくっているため、なんらかのメソッド呼び出しと 間違える可能性がある
         */
        public int badSampleMethod2() {
            short a = 1;
            // 何か処理
            return (int) (a);
        }
    }

    修正例
    public class GoodSample {
        /**
         * 修正済み:return文の前に演算を済ませている
         */
        public int goodSampleMethod1() {
            int a=1;
            int b=2;
            int c=a+b;
            return c;
        }
        /**
         * 修正済み:変数を括弧でくくっていないため、わかりやすい
         */
        public int goodSampleMethod2() {
            short a=1;
            //何か処理
            return (int)a;
        }
    }

2.3.3 条件分岐では、「!」を使わない

⭐ ⭐ ⭐

【説明・動機】
  条件分岐でboolean型の条件を比較する場合は、「!」を使わず、trueかfalseと比較するようにしましょう。
  条件分岐で呼び出すメソッドが長い場合や、条件が複数ある場合に「!」を使用すると、「!」を見落としがちになります。
  また、trueとの比較も明示的に書いたほうが可読性が高くなる場合があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    違反例
    /**
     * 違反:true/falseと比較していないてめ、可読性が低い
     */
    public void badSampleMethod() {
        String name1="aaa";
        String name2="bbb";
        if (!StringUtil.isEmpty(name1)&& StringUtil.isEmpty(name2)) {
            //処理
        }
    }

    修正例
    /**
     * 修正済み:true/falseと比較していないてめ、わかりやすい
     */
    public void goodSampleMethod() {
        String name1="aaa";
        String name2="bbb";
        if (StringUtil.isEmpty(name1)==false &&
         StringUtil.isEmpty(name2)==true) {
            //処理
        }
    }

boolean値を true と比較しない (⭐ MobileOfficeシステムで追加する【Javaコーディング推奨事項】)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 boolean isCheck=true;
 //×
  if(isCheck==true){
      System.out.println("OK");
      }
  //〇
  if(isCheck){
      System.out.println("OK");
      }
  }

2.3.4 不等号の向きは左向き("<"、"<=")にする

⭐ ⭐ ⭐

【説明・動機】
  不等号の向きを左向きに統一すると、「左側が常に小さい」という規約でコード全体を確認出来るため、読みやすくなります。特に意図的に記述する場合を除いて、向きは左向きに統一してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    違反例
    public void badSampleMethod() {
        int a = 1;
        int i = 2;
        if (a < i) {

        } else if (a > i) {// 違反

        }else{
        }
        if(a>0 && a<100){//違反
        }
    }

    修正例
    public void goodSampleMethod() {
        int a = 1;
        int i = 2;
        if (a < i) {

        } else if (i < a) {// 修正済み

        } else {

        }

        if (0 < a && a < 100) {// 修正済み

        }
    }

2.3.5 条件文で続けてメソッド呼び出しせずに一時変数に代入する

⭐ ⭐ ⭐

【説明・動機】
  条件文内でメソッド呼び出しが必要なケースでは、メソッドを重ね合わせて呼び出してはいけません。続けてメソッドを呼び出さず、変数にメソッド呼び出しの結果を一度代入することで、条件文を記述した部分を出来る限りわかりやすく保ってください。
  メソッド呼び出しを重ね合わせると、括弧の対応付けがわかりにくくなり、可読性が落ちます。また途中の値がデバッガで取得しにくくなり、デバッグしづらくなります。
  なるべくメソッド呼び出しは重ね合わせず、読みやすくデバッグしやすくするように心がけてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    違反例
    public class BadSample {
        private int getNumber(){
            return 1;
        }
        private String getname(int i){
            return "Yonebayashi" + String.valueOf(i);
        }
        /**
         * 違反:条件分岐でメソッドを多段で呼び出しているため、値を確認しづらい
         */
        public void badSampleMethod(){
            if (StringUtil.isEmpty(getName(getNumber()))==false) {
            }
        }
    }

    修正例
    public class GoodSample {
        private int getNumber(){
            return 1;
        }
        private String getname(int i){
            return "Yonebayashi" + String.valueOf(i);
        }
        /**
         * 修正済み:メソッド呼び出し後の値に対して条件分岐しているため、
         * わかりやすい。
         */
        public void goodSampleMethod(){
            int number=getNumber();
            String name=getname(number);
            if (StringUtil.isEmpty(name)==false) {
                //修正済み
            }
        }
    }

2.3.6 アクセサメソッド(get/setメソッド)はIDEで自動生成する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  いわゆるアクセサメソッドであるゲッターメソッドとセッターメソッドは、IDEで自動生成することが出来ます。IDEで自動生成されるゲッター、セッターメソッドはJavaの規約に従ったものになり、タイブミスやコーディングがなくなるため、大変便利です。またコメント文を生成してくれるものもあります。
  ゲッター/セッターを作成する場合は、自動生成機能を使いましょう。

2.4 ドキュメンテーションコメント

2.4.1 キュメンテーションコメント(Javadoc)には、少なくともauthor,param,return,throwsを記述する

⭐ ⭐ ⭐

【説明・動機】
  クラス、メソッド、フィールドには下記のようにドキュメンテーションコメントを記述してください。特にpublicで宣言されたものについては必須です。下記以外のタグも必要に応じて記述してください。
「ドキュメンテーションコメントの様式」

  • 「/**」で始める
  • 2行目以降は「*」で始め、下記のJavadocタグで本文を記述する
  • 「*/」で終わる

「クラス」

  • @author[作成者]
    クラスの作成者および更新者を記述する。
     複数記述する場合は1人ずつタグを記述する。
    「メソッド」

  • @param[名前][説明]
     パラメータの名前とその説明を、宣言されている順に記述する。

  • @return[説明]
     戻り値があれば、戻り値の説明を記述する。
  • @throws[名前][説明]
     メソッドが呼ばれた際にthrowされる可能性のある例外名と、その説明を記述する。

「フィールド」
 フィールドについての説明
 出来るだけわかりやすい名前をつけるようにする。名前だけではどうしてもわかりにくい場合は、ドキュメンテーションコメントで説明を記述する。

1
**注意:本書のサンプルコードは、規約の説明を目的としてきさいしています。規約のポイントをよりはっきりさせるために、ドキュメンテーションコメントを省略している部分があります。**
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    サンプル
    /**
     * sampleMethodの説明
     *
     * @param value
     *引数の説明
     * @return 返り値の説明
     * @throws SampleException
     *例外の説明
     */

    public String sampleMethod(String value) throws SampleException {
        String returnVaule = "SAMPLE";
        // 何か処理を実行
        return returnVaule;
    }

MEMO javadocの表記一覧
Javaを記述する際のその他一般的な項目をもとめてみました

@since このクラス、メソッドが導入されたバージョンを明記する
@see そのほかのクラス、メソッド、外部の文献などを表現する
@deprecated 廃止したクラス、メソッドなどを表現する
[@link] Javadocにリンクを埋め込むのタグ

2.4.2 ドキュメンテーションコメント(javadoc)は必要なものだけを簡潔に

⭐ ⭐ ⭐ ⭐

【説明・動機】
  ドキュメンテーションコメントには、本当に必要なものだけを簡潔に記述します。「本当に必要なもの」の最たる例がコードの意図と目的です。意図と目的はコードからは読み取りにくい情報です。まずはなぜこのコードが必要なのか、コードの意図と目的をしっかり書きましょう。
  また、コメントを読みやすくするコツとして、下記の3つが挙げられます。

  1. フィールドの宣言時、わかりやすく命名することによってコメントで説明する手間を省く
  2. 修正者名、修正日などお書かない(修正履歴の管理は、バージョン管理ツールを活用する)
  3. (コメントがやむを得ず長くなる場合)最初の1文に要旨をまとめ、内容を理解しやすくする

次ページの違反例のように意図と目的ではなく実際のロジックの内容を都度説明すると可読性が低下します。コメントで長々と補足しなくてはいけないケースのほとんどが、適切な変数名・メソッド名・クラス名がついていないケースです。以下のようにして対処しましょう。

  • 適切なクラス名・メソッド名・変数名がつけられているかを確認する
  • メソッド名などからは類推しにくい処理の例外ケースなど、本当に補足すべき部分をコメントとして追加する

なお、サンプルでは、クラスのコメントは省略しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    違反例
    /**
     *  「ドキュメンテーションコメント(javadoc)は必要なものだけを簡潔に 」 の悪い例
     * 違反:なぜ配列の最後の値が加算されないのかわからない
     */
    public class BadSample {
        /**
         * 和を求めるメソッド
         *
         * @param elements
         *            和を求めたい数値の配列
         * @return  引数の配列に対して求めた和
         */
        public int sum(int[] elements) {
            if (elements == null) {
                throw new IllegalArgumentException("要素はnullであってはいけません");
            }
            // 和の結果を格納する変数
            int sum = 0;
            // 配列の和を計算するループ。最後の値は加算されない
            for (int i = 0; i < elements.length - 1; i++) {
                // 要素をひとつ取り出して和を計算
                sum += elements[i];
            }
            // 結果を返す
            return sum;
        }
    }

    修正例
    /**
     * 「ドキュメンテーションコメント(javadoc)は必要なものだけを簡潔に 」 の良い例
     *
     * 修正済み:配列の最後の値が加算しない目的がわかる
     *
     */
    public class GoodSample {

        /**
         * ×××業務用に要素を足し合わせ、その和を返す。
         *
         * ×××業務では既存システムとの交換性のために配列の最後にダミー値を入れているため、
         * その値は加算しない 要素がnullであってはいけない
         *
         * @param elements
         *            和を求めたい数値の配列
         * @return  引数の配列に対して求めた和
         * @throws IllegalArgumentException
         *引数の要素がnullだった場合
         */
        public int sum(int[] elements) {
            if (elements == null) {
                throw new IllegalArgumentException("要素はnullであってはいけません");
            }
            int sum = 0;
            for (int i = 0; i < elements.length - 1; i++) {
                sum += elements[i];
            }
            return sum;
        }
    }

2.5 パッケージ

2.5.1 java.langパッケージはインポートしない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  java.langパッケージは、記述がなくても暗黙的にインポートされます。従って、開発者が明示的にインポートする必要はありません。

1
2
3
4
5
6
7
8
9
    違反例
    import java.lang.*;         //違反
    public class BadSample {
    }

    修正例
    //修正済
    public class GoodSample {
    }

2.5.2 import文にアスタリスクを使用しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  import文でアスタリスクを使用すると、そのパッケージ内のすべてのクラスをインポート出来ます。
  一見便利に感じますが、複数のパッケージをアスタリスクを使用してインポートしてしまうと、そのクラス内で使用しているクラスがどこのパッケージに入っているのかがわからなくなり、コードを追いづらくなります。
  また、インポートしたクラスと同名のクラスがカレントパッケージに入っている場合、アスタリスクで宣言していると、カレントパッケージのクラスが優先して利用されてしまうため、期待したクラスが利用されないことがあります。
  このため、import文にアスタリスクは使用せず、クラス名まですべて記述しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    import javarulebook.data1.*;
    import javarulebook.data2.*;    //違反

    public class BadSample {
        public void badFunction(){
            //Hoge、Fooがどのパッケージなのかひと目でわからない
            Hoge hoge = new Hoge();
            Foo hoge = new Foo();
        }
    }

    修正例
    import javarulebook.data1.Hoge;
    import javarulebook.data2.Foo;    //修正済

    public class GoodSample {
        public void goodFunction(){
            //Hoge、Fooがどのパッケージなのかわかりやすい
            Hoge hoge = new Hoge();
            Foo hoge = new Foo();
        }
    }

2.5.3 import文はIDEで管理する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  import文は、手動で記述するとクラスを追加/削除するたびに修正が発生するため、生産性が落ちます。
  最新のIDEではimport文を自動で編成してくれるため、積極的にそのような機能を使用しましょう。

2.6 クラス

2.6.1 継承させたくないクラスはfinal宣言する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  継承させたくないクラスはfinalを宣言してください。下記のメリットがあるためです。

  • 継承されないことがコンバイラで保証されるため、意図しない継承を避けることが出来る
  • 「このクラスは継承出来ない」という意図が利用者に明らかに伝わる
1
2
3
4
5
6
    違反例
    public class BadSample {//違反
    }
    修正例
    public final class GoodSample {   //修正済
    }

2.6.2 フィールドはpublic,protected,デフォルト,privateの順に宣言する

⭐ ⭐ ⭐

教育資料:[[オブジェクト指向について]]

【説明・動機】
  コードの可読性を保つため、フィールドの宣言はアクセス修飾子によって以下の順序で行ってください。

  • public
  • protected
  • デフォルト
  • private

また、staticフィールドはこれらよりも先に宣言してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    違反例
    public class BadSample {
        //違反  フィールド宣言の順序性がない。
        int phoneNumber;
        public static final int NUMBER = 1;
        public String name;
        private int age;
        protected String nickName;
    }

    修正例
    public final class GoodSample {
        //修正済
        public static final int NUMBER = 1;
        public String name;
        protected String nickName;
        int phoneNumber;
        private int age;
    }

2.7 コンストラクタ

2.7.1 public宣言していないクラスにはpublic権限のコンストラクタを作らない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  publicでないクラスのコンストラクタをpublicで宣言していませんか?
  publicでないクラスのコンストラクタは、どこからでもアクセス出来るわけではありません。例えば違反例の場合、このコンストラクタを別パッケージから呼び出すコードはコンパイルが通りません。利用側は、publicコンストラクタがあるにも関わらずコンバイルエラーとなるため、混乱します。
  このようなことを防ぐために、以下のように設計を見直してください。

  • コンストラクタからpublic修飾子を除去する
  • クラスにpublic修飾子をつけられるようにする
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    違反例
    class BadSample { //非publicなクラス
        public BadSample(){   //違反
        }
    }

    修正例
    class GoodSample {
        GoodSample(){   //修正済
        }
    }

2.7.2 コンストラクタはクラスの特性に応じて作成する

⭐ ⭐ ⭐

【説明・動機】
コンストラクタには、以下の3つの特徴があります。

  • インスタンス化するときしか利用できない
  • コンストラクタのアクセス権限により、インスタンス化出来る範囲を制限出来る
  • 異なる引数のコンストラクタを作成し、初期化状態を制御出来る

これらの特徴を考慮して、コンストラクタを作成してください。
  コンストラクタの作成は、大まかに次の5つのパターンがあります。
①インスタンスかの際に、引数も処理も必要ないパターン
  この場合、コンストラクタを実装する必要はありません。デフォルトコンストラクタが自動的に生成されます。
②引数なしでも、引数ありでもインスタンス化出来るパターン
  インスタンス生成時に必要な値を渡すことも、引数なしでインスタンス化した後にセッターなどで必要な値を渡すことも出来るような場合は、引数ありのコンストラクタに加え、引数なしのコンストラクタを実装してください。
  コンストラクタを1つでも実装すると、デフォルトコンストラクタは暗黙的には作成されません。このため、引数があるコンストラクタを実装した場合は、引数なしでインスタンス化するためのコンストラクタを明示的に実装する必要があります。
③インスタンス化の際に引数を要求するパターン
  指定の引数がないと、インスタンス化を許可しない場合です。この場合は引数ありのコンストラクタのみを作成してください。引数なしのコンストラクタを実装しないことで、引数なしではインスタンス化が行えないようにできます。
④クラスの継承はさせたいが、外部からインスタンス化させたくないパターン
  サブクラス以外には公開したくないコンストラクタがあるような場合です。この場合、アクセス権限をprotectedにすることで、そのコンストラクタが利用出来る範囲をサブクラスに限定出来ます。
⑤インスタンス化させたくないパターン
  ユーティリティクラスなど、インスタンスを生成させたくない場合です。この場合、引数なしのコンストラクタを作成し、アクセス権限をprivateにします。

すべてのクラスがこのパターンに当てはまるわけではありませんが、クラスの特性に応じてコンストラクタを使い分けることが必要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    サンプル
    // デフォルトコンストラクタのみの場合は、宣言不要
    public class GoodSample1 {
    }

    // 引数なしでも引数ありでもインスタンス化可能な場合は、両方宣言
    public class GoodSample2 {
        public GoodSample2(){
        }
        public GoodSample2(int value){
        }
    }

    // 引数ありでないとインスタンス化させない場合は、引数ありだけ宣言
    public class GoodSample3 {
        public GoodSample3(int value){
        }
    }

    // 外部に公開したくないコンストラクタがあるような場合はprotected
    public class GoodSample4 {
        protected GoodSample4(int value){
        }
    }

    // インスタンス化をさせたくない場合はprivate
    public class GoodSample5 {
        private GoodSample5(){
        }
    }

2.8 メソッド

2.8.1 オーバーライドさせたくないメソッドにはfinalを付ける

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  メソッドによっては、サブクラスによってオーバーライドされると、挙動が大幅に変わってしまうケースがあります。オーバーライドさせたくないメソッドは、final宣言してください。これにより、メソッドがオーバーライド出来ないことがコンバイラによって保証されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
違反例
    public class BadSample {
        // オーバーライドされてしまう
        public void badMethod() {
        }
    }
    修正例
    public class GoodSample {
        // 修正済みfinalがついているため、オーバーライドされない
        public final void goodMethod() {
        }
    }

2.8.2 戻り値が配列やCollectionの場合nullを返さない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  戻り値が配列やCollectionのメソッドの場合、呼び出し側はそのメソッドの戻り値から取得した配列やCollectionを使用してループを回すケースが多くあります。このような場合にnullを返してしまうと、呼び出し側でnullチェックを行わなければいけません。
  そのため、長さ0の配列や空のCollection(Collections.emptyList())を返すように徹底してください。これにより、呼び出し側でnullチェックする必要がなくなります。
  nullを返す必要性がない場合は、nullの代わりに長さ0の配列や空のCollectionを返しましょう。
  このポリシーが一貫していると、呼び出し側も利用しやすくなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    違反例
    public class BadSample {
        public String[] getArray(int value) {
            if (100 < value) {
                return null; // nullを返している。 違反
            } else {
                // 実際には何か処理をして返す
                return new String[value];
            }
        }
        public List<String> getList(int value) {
            if (100 < value) {
                return null; // nullを返している。 違反
            } else {
                // 実際には何か処理をして返す
                return new ArrayList<String>(value);
            }
        }
    }

    修正例
    public class GoodSample {
        public String[] getArray(int value) {
            if (100 < value) {
                return new String[0];
                // 修正済み.長さ0の配列を返す。
            } else {
                // 実際には何か処理をして返す
                return new String[value];
            }
        }

        public List<String> getList(int value) {
            if (100 < value) {
                return Collections.emptyList();
                // 修正済み。nullの代わりに空のListを返す。
            } else {
                // 実際には何か処理をして返す
                return new ArrayList<String>(value);
            }
        }
    }

2.8.3 publicメソッドはクラスの整合性を壊さないような設計にする

⭐ ⭐ ⭐ ⭐

【説明・動機】
  publicメソッドを作成する際は、クラスの内部的な整合性を壊さないように、慎重に設計してください。

【解説】

違反例のクラスは、stockRightSpeakerとstockLeftSpeakerの二つの数値を属性として保持しています。「Speakerは常に左右セットで売られなければならない」としたときに、以下の設計では問題があります。ここでは、それぞれのsellメソッドによってstockRightSpeakerとstockLeftSpeakerの両方の値をそれぞれ外部から変更することが出来ます。しかし、万が一どちらか一方の値のみが変更された場合、「Speakerは左右セットで販売」という前提は容易に崩れてしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    違反例
    public class BadSample {
        private int stockRightSpeaker;
        private int stockLeftSpeaker;
        //左右セット販売が前提のスピーカーの場合、
        //片方のメソッド呼び出しが行われると、不整合が起こる
        public void sellRightSpeaker(int quantityRight){
            stockRightSpeaker = stockRightSpeaker - quantityRight;
        }
        public void sellLeftSpeaker(int quantityLeft){
            stockLeftSpeaker = stockLeftSpeaker - quantityLeft;
        }
    }

【解説】

修正例では、stockRightSpeakerとstockLeftSpeakerに別々にアクセスするのではなく、一つのメソッドで一度に変更するようにしています。
  これで、呼び出し側はsellSpeakerメソッドにしかアクセスできなくなり、「Speakerは左右セットで販売する」という前提を維持することが出来ます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    修正例
    public class GoodSample {
        private int stockRightSpeaker;
        private int stockLeftSpeaker;
        //外からの呼び出しで、左右セット販売の前提が
        //崩れることがない
        public void sellSpeaker(int quantity){
            stockRightSpeaker = stockRightSpeaker - quantity;
            stockLeftSpeaker = stockLeftSpeaker - quantity;
        }
    }

2.8.4 一つのメソッドには一つの役割を担当させる

⭐ ⭐ ⭐ ⭐

【説明・動機】
  一つのメソッドで内容の異なる複数の処理を行うと、可読性・保守性・拡張性・再利用性のすべてにおいて悪影響をもたらすことになります。メソッドは機能ごとに分割してください。

【解説】

違反例では、switchXandYメソッドに、XとYを入れ替える機能と、値を表示する機能が一緒に入っています。
このメソッドを利用する際、値を表示する機能が不要でも、switchXandYメソッドを呼び出すと、値を表示されてしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    違反例
    import java.awt.Point;
    public class BadSample {
        private Point point;
        public static void main(String[] args) {
            Point point = new Point(55, 77);
            BadSample smpl = new BadSample(point);
            smpl.switchXandY();
        }
        public BadSample(Point point) {
            this.point = point;
        }
        //  違反 値の変更だけでなく表示も行なっている
        private void switchXandY() {
            //  値の表示
            StringBuilder sb = new StringBuilder();
            sb.append("X is ");
            sb.append(point.getX());
            sb.append(" and Y is ");
            sb.append(point.getY());
            System.out.println(sb);
            // 値の変更
            double x = point.getX();
            double y = point.getY();
            point.setLocation(y, x);
            // 値の表示
            sb.setLength(0);
            sb.append("X is ");
            sb.append(point.getX());
            sb.append(" and Y is ");
            sb.append(point.getY());
            System.out.println(sb);
        }
    }

【解説】

修正例では、XとYを入れ替える機能と、値を表示する機能を分けたことで、各メソッドの可読性が上がり、さらに重複した実装を削減出来ています。また値を表示する機能が不要な場合は呼び出し側でprintValue()を呼び出さなければよく、利用したい機能だけを利用することが出来るようになっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    修正例
    import java.awt.Point;

    public class GoodSample {
        private Point point;

        public static void main(String[] args) {
            Point point = new Point(55, 77);

            GoodSample smpl = new GoodSample(point);
            smpl.printValue();
            smpl.switchXandY();
            smpl.printValue();
        }

        public GoodSample(Point point) {
            this.point = point;
        }

        // 修正済み XとYを入れ替えるメソッド
        private void switchXandY() {
            double x = point.getX();
            double y = point.getY();
            point.setLocation(y, x);
        }
        // 修正済み 値を表示するメソッド
        private void printValue(){
            StringBuilder sb = new StringBuilder();
            sb.append("X is ");
            sb.append(point.getX());
            sb.append(" and Y is ");
            sb.append(point.getY());
            System.out.println(sb);
        }
    }

2.8.5 引数の数が同じメソッドはオーバーロードしない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  引数の数が同じメソッドのオーバーロードは、以下のようなユーティリティメソッドの場合に役立つケースがあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    public class ConvertUtils {
        public String toString(String s) {
            return s;
        }

        public String toString(int n) {
            String s = Integer.toString(n);
            return s;
        }
    }

しかし、ソースコードを追いかけて引数の型を確かめなければ、どのメソッドが実行されているのかわかりにくいことがあります。そのため、解説で後述するように、ソースコードの可読性が落ちてしまいます。特に、オーバーロードされたメソッドの引数が継承関係にある場合、意図しないメソッドが呼び出されたり、ユーザに思わぬ混乱を招くことがあります。
実装のテクニックとしてオーバーロードを利用する場合もありますが、使い方をしっかり把握した上で利用しなければ混乱します。必要でない限り、引数が同じメソッドはオーバーロードしないようにしてください。

【解説】

サンプルでは、例外を処理するオブジェクトの区別をするsampleMethodメソッドが3つ作成されています。mainメソッドで、インスタンスを生成し、sampleMethodメソッドを呼び出しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    サンプル
    public class BadSample {
        public static void main(String[] args) {
            Exception e1 = new Exception();
            IOException e2 = new IOException();
            // IOExceptionのサブクラス
            FileNotFoundException e3 = new FileNotFoundException();

            BadSample sample = new BadSample();
            sample.sampleMethod(e1);// ①
            sample.sampleMethod(e2);// ②
            sample.sampleMethod(e3);// ③
        }

        public void sampleMethod(Exception e) {
            System.out.println("Exception");
        }

        public void sampleMethod(IOException e) {
            System.out.println("IOException");
        }

        public void sampleMethod(Object e) {
            System.out.println("Object");
        }
    }

【解説】
この結果、①,②,③に応じて以下のように表示されます。
Exception
IOException
IOException
しかし、①,②,③の行だけを見ると、どのメソッドが実行されているかわかりづらいです。また③の場合、 FileNotFoundExceptionに対してどのメソッドが呼ばれるかを把握するのが難しくなります。

2.8.6 オーバーライドしたメソッドには、@Overrideアノテーションを付与する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  オーバーライドしたメソッドには、必ず@Overrideを付与してください。以下のようなメリットがあるためです。

  • コンバイラがメソッド名や引数などをチェックしてくれ、オーバーライドが出来ていないというミスを防ぐことが出来る
  • オーバーライドしている親クラスのメソッドが変更された場合もコンバイルエラーとして検出出来て安全
  • オーバーライドしたメソッドであることがひと目でわかる
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    違反例
    public class SuperClass {
        public String sampleMethod() {
        }
    }
    public class BadSample extends SuperClass {

        // 違反、オーバーライドしているかどうかがわからない
        // 親クラスとメソッド名やシグネチャが異なっていてもわからない
        public String sampleMethod() {
        }
    }

    修正例
    public class SuperClass {
        public String sampleMethod() {
        }
    }
    public class GoodSample extends SuperClass {

        // 修正済み、オーバーライドしている事がひと目でわかる。
        // 親クラスとメソッド名やシグネチャが異なっている場合、エラーを表示してくれる
        @Override
        public String sampleMethod() {
        }
    }

2.8.7 staticメソッドはクラス名を使って呼び出す

⭐ ⭐ ⭐ ⭐

【説明・動機】
  staticメソッドを使用する際、そのクラスのインスタンスを用いて呼び出していませんか?クラス名を用いれば、不必要なインスタンス化をしなくても済みます。また呼び出し先でstaticメソッドを呼んでいることがより明瞭になり、可読性が向上します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    違反例
    public class BadSample {
        public static void sampleClassMethod() {
            // ...
        }

        public static void main(String[] args) {
            BadSample object = new BadSample();
            object.sampleClassMethod(); // 違反
        }
    }

    修正例
    public class GoodSample {
        public static void sampleClassMethod() {
            // ...
        }

        public static void main(String[] args) {
            GoodSample.sampleClassMethod(); // 修正済み
        }
    }

2.9 変数・配列

2.9.1 一度に複数の変数を宣言しない

⭐ ⭐ ⭐

【説明・動機】
  一度の変数宣言で、複数の変数を宣言しないようにしてください。「一度の変数宣言で1変数」という規則に則って記述されたコードのほうが統一感出るうえ、各変数の型が明確になり、可読性が向します。
  コメントを付与する際も、1変数づつ宣言されているほうが意図が明示的になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    違反例
    public class BadSample {
        private String firstName, lastName;    //違反
    }

    修正例
    public class GoodSample {
        private String firstName;    // 修正済み
        private String lastName;
    }

2.9.2 リテラルは直接使用せずにリテラル定数を定義する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  リテラルとは、コード中に、数値や文字列などが定数として直接表れているものを指します。リテラルの使用は、コードの可読性・保守性を下げてしまいます。特に、「マジックナンバー」と呼ばれる値(リテラルとして実装中に出てくる定数のうち、なぜその値が利用されているか理解できないもの)は、コードの理解を大きく妨げます。
  これを防ぐために、リテラル定数(static final フィールド)を使用するようにしてください。リテラル定数を使用し、適切な変数名を付けることで、コードの意図が明確になり、理解しやすくなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample {
        private int[] sampleArray = new int[65535]; // 違反

        public void sampleMethod(String str) {
            if ("ZZZZ://".equals(str)) {
                // 処理
            }
        }
    }

    修正例
    public class GoodSample {
        private static final int MAX_MEMORY_SIZE = 65535; // 修正済み
        private int[] sampleArray = new int[MAX_MEMORY_SIZE]; //修正済み

        private static final String MY_PROTOCOL = "ZZZZ://"; // 修正済み
        public void sampleMethod(String str) {
            if (MY_PROTOCOL.equals(str)) {
                // 処理
            }
        }
    }

2.9.3 配列宣言は「型名[]」にする

⭐ ⭐ ⭐ ⭐

【説明・動機】
  「変数名[]」のような配列の宣言形式は、C言語の名残として残っているようですが、コードの一貫性を保つために、配列の宣言形式はコード聞で統一してください。
  推薦するのは「型名[]」のような形式です。

1
2
3
4
5
6
7
8
9
    違反例
    public class BadSample{
        private int sampleArray[];//違反
    }

    修正例
    public class GoodSample{
        private int[] sampleArray;//修正済み
    }

2.9.4 出来るだけローカル変数を利用する

⭐ ⭐ ⭐

【説明・動機】
  クラス変数やインスタンス変数へのアクセスが頻繁に起こっていませんか?それは本当に必要ですか?
  クラス変数やインスタンス変数へ頻繁にアクセスすると、オプジェクトの状態が壊れてしまう可能性があります。ローカル変数を使って、クラス変数やインスタンス変数へは必要なときだけアクセスするようにしましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    違反例
    public class BadSample{
        private int result;        
        public void addNumber(int[] numbers){
            if(numbers==null){
                throw new IllegalArgumentException
                ("引数の数値はnullであってはいけません." );
            }
            for (int n : numbers) {
                result +=n;//違反
            }
        }
    }

    修正例
    public class GoodSample{
        private int result;
        public void addNumber(int[] numbers){
            if(numbers==null){
                throw new IllegalArgumentException
                ("引数の数値はnullであってはいけません." );
            }
            //修正済み 一時なローカル変数
            int tempSum=0;
            for (int n : numbers) {
                tempSum +=n;
            }
            result=result+tempSum;
        }
    }

2.9.5 定数はstatic finalで宣言する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  定数はクラス全体は通して変化しない値のことです。定数に対しstatic final を宣言することによって、この値が変わらないことを明示出来、コードの可能性が向上します。
  また、Java5からenumを使用出来るようになりました。区分やグループを表現するような場合は、そちらの利用も検討してください。詳細は「2-13-8 グループの固定値を表す場合はenumを使う」を参照してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    違反例
    public class BadSample{
        private int constant=5;//違反
        private int getSize(int number){
            return number * constant;
        }
    }

    修正例
    public class GoodSample{
        private static final int CONSTANT=5;//修正済み
        private int getSize(int number){
                return number * CONSTANT;
        }
    }

2.9.6 ローカル変数とインスタンス変数を使い分ける

⭐ ⭐ ⭐

【説明・動機】
  ローカル変数で事足りるものをインスタンス変数として利用していませんか?
  必要のないインスタンス変数は、パフォーマンスや可読性が低下する大きな要因となる上、マルチスレッドを意義した処理を行う際に不整合が起きる可能性もあります。インスタンス変数は必要性を十分に考慮してから使ってください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    違反例
    public class BadSample{
        //違反 ひとつのメソッド内でしか使われない変数
        private int value;
        //valueはこのメソッドでしか使われない
        private int calcValue(SomeObj inValue){
            //インスタンス変数である必要がない
            value=inValue.getData();
            for (int i = 0; i < value; i++) {
                //処理
            }
        }
    }

    修正例
    public class GoodSample{
        private int calcValue(SomeObj inValue){
            //修正ずみ メソッド内で宣言
            int value=inValue.getData();
            for (int i = 0; i < value; i++) {
                //処理
            }
        }
    }

2.9.7 インスタンス変数はprivate もしくはprotectedにする

⭐ ⭐ ⭐

【説明・動機】
  インスタンス変数をpublicまたはデフォルト(パッケージプライベート)にし、直接操作するようなコードを書いていませんか?   オブジェクト指向のカプセル化の考え方として、クラスの内部状態に誰でもアクセス出来るということは好ましくありません。適切なアクセサメソッドを定義し、そのメソッドを介してのみインスタンス変数にアクセス出来るようにしましょう。
  また、インスタンス変数自体はprivateまたはprotectedにして、直接アクセスするのは極力避けるようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    違反例
    public class BadObject {
        public int value = 10;// 違反
    }

    public class BadSample {
        public static void main(String[] args) {
            Bad0bject bj = new Bad0bject();
            obj.value += 10; //直接アクセスしている
            System.out.println(obj.value);
        }
    }

    修正例
    public class GoodObject {
        private int value = 10;// 修正済み
        public int getValue(){
            return value;
        }
    }

    public class GoodSample {
        public static void main(String[] args) {
            GoodObject sample = new GoodObject();
            int result = sample.getValue();
            result += 10;
            System.out.println(result);
        }
    }

2.9.8 public static final 宣言した配列を利用しない

⭐ ⭐ ⭐

【説明・動機】
  final 宣言した配列の要素は変更出来ないと思っていませんか?
  違反例にあるとおり、finalで配列を宣言していても、不変なのは配列のサイズのみであり、配列の要素は変更可能です。保持している要素を変えられたくない場合は、以下のことは検討してください。
1. 配列ではなく、CollectionsクラスのunmodifiableList()メソッドなどを使って、変更不可能なListやSetで公開出来ないか(配列を変更不可能なListやSetに変換する場合には、変換元の配列の変更の影響を回避するために配列をcloneする)
1. 配列として宣言する必要がある場合、publicではなくprotectedやprivateで公開出来ないか

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    違反例
    public class BadSample {
        public static final int[] SAMPLE_ARRAY=
            { 0, 1, 2, 3 };//違反
        public static void main(String[] args) {
            System.out.println(SAMPLE_ARRAY);
            SAMPLE_ARRAY[0]=1;//値を変えられる
            System.out.println(SAMPLE_ARRAY[0]);
        }
    }

    修正例
    public class GoodSample {
        public static final List<Integer> IMMUTABLE_ARRAY = Collections
                .unmodifiableList(Arrays.asList(0, 1, 2, 3));// 修正済み
        // privateにして他者からアクセス不可能にする
        private static final Integer[] SAMPLE_ARRAY = { 0, 1, 2, 3 };
        // 別案、配列をcloneして参照元の変更の影響を回避する
        public static final List<Integer> IMMUTALBE_ARRAY_ALT = Collections
                .unmodifiableList(Arrays.asList(SAMPLE_ARRAY.clone()));

        public static void main(String[] args) {
            System.out.println(IMMUTABLE_ARRAY.get(1));
            // 変更不可能なため、UnsupportedOperationExceptionが発生する
            IMMUTABLE_ARRAY.set(0, 123);
        }
    }

2.9.9 クラス変数はクラス名を使って呼び出す

⭐ ⭐ ⭐ ⭐

【説明・動機】
  クラス変数を使用する際、インスタンス変数の参照を用いてアクセスしていませんか?
  クラス変数を使用する際は、クラス名を用いるようにしてください。これにより、呼び出している変数がクラス変数かどうかがわかりやすくなり、可読性が向上します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    違反例
    public class BadSample {
        public static final int  STATIC_VALUE=10;
        public void sampleMethod(){
            BadSample object=new BadSample();
            int localValue=object.STATIC_VALUE;//違反
            System.out.println(localValue);
        }
    }

    修正例
    public class GoodSample {
        public static final int STATIC_VALUE=10;
        public void sampleMethod(){
            int localValue=GoodSample.STATIC_VALUE;//修正済み
            System.out.println(localValue);
        }
    }

2.9.10 ローカル変数は利用する直前で宣言する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  「ローカル変数は、とりあえずコードのはじめにまとめて宣言しておく」というようなことをしていませんか?
  ローカル変数を宣言する場所と使用する場所が離れている場合、コードの可読性、保守性を低下させる要因となります。このようなことを防ぐため、ローカル変数は利用する直前で宣言するようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
違反例
    public class BadSample {
        public static void main(String[] args){
            String localValue1="javarulebook";
            String localValue2="javaルールブック";
            String localValue3="全面改訂java6対応版";//違反
            LocalVarTarget target=new LocalVarTarget();
            target.execute1(localValue1);
            //...
            target.execute2(localValue2);
            //...
            target.execute3(localValue3);
            //...
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args){
            LocalVarTarget target=new LocalVarTarget();
            //変数が呼び出すメソッドの直前にあるので、わかりやすい
            String localValue1="javarulebook";//修正済み
            target.execute1(localValue1);
            //...
            String localValue2="javaルールブック";//修正済み
            target.execute2(localValue2);
            //...
            String localValue3="全面改訂java6対応版";//修正済み
            target.execute3(localValue3);
            //...
        }
    }

2.9.11 ローカル変数.メソッド引数は安易に再利用しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  一度宣言したローカル変数を、別の目的のために安易に使いまわしていませんか?
  ローカル変数は、スコープを最小限に、そして役割ごとに新しいものを宣言して初期化してください。これにより、コードの可読性・保守性が向上します。同様に、メソッド引数の使い回も避けましょう。
  なお、再利用や意図しない再代入を防ぐために、ローカル変数・メソッド引数にfinalをつける方法もあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    違反例
    public class BadSample {
        public static final int LENGTH1 = 10;
        public static final int LENGTH2 = 5;

        public void print(int no) {
            int i = 0;
            for (i = 0; i < LENGTH1; i++) {
                System.out.println("length1:" + no);
            }
            // 違反:すでに使った変数iを再利用している
            for (i = 0; i < LENGTH2; i++) {
                System.out.println("length2:" + no);
            }
            // 違反:別目的で変数を再利用している
            no = LENGTH1 * LENGTH2;
            System.out.println("length1 * length2:" + no);
        }
    }

    修正例
    public class GoodSample1 {
        public static final int LENGTH1 = 10;
        public static final int LENGTH2 = 5;

        public void print(int no) {
            //このfor文内スコープでのiを定義
            for (int i = 0; i < LENGTH1; i++) {
                System.out.println("length1:" + no);
            }
            // 修正済み.また別個のfor文内スコープでのiを定義
            for (int i = 0; i < LENGTH2; i++) {
                System.out.println("length2:" + no);
            }
            //修正済み.適切な名前を持つ変数を定義
            int total = LENGTH1 * LENGTH2;
            System.out.println("length1 * length2:" + total);
        }
    }

    public class GoodSample2 {
        public static final int LENGTH1 = 10;
        public static final int LENGTH2 = 5;
        //final宣言すると、再代入される可能性がない
        public void print(final int no) {
            //このfor文内スコープでのiを定義
            for (int i = 0; i < LENGTH1; i++) {
                System.out.println("length1:" + no);
            }
            // 修正済み.また別個のfor文内スコープでのiを定義
            for (int i = 0; i < LENGTH2; i++) {
                System.out.println("length2:" + no);
            }
            //修正済み.適切な名前を持つ変数を定義
            final int total = LENGTH1 * LENGTH2;
            System.out.println("length1 * length2:" + total);
        }
    }

2.10 文字列操作

2.10.1 文字列リテラルはnewしない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  文字列定数を保持するためのStringオブジェクトを生成する際、newを使用していませんか?
  文字列定数にはnewを使用する必要がありません。シンプルに文字列リテラルを定数してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    違反例
    public class BadSample {
        protected String name = new String("YonebaYasrh");// 違反

        public void print() {
            System.out.println(this.nane);
        }
    }

    修正例
    public class GoodSample {
        protected String name = "YonebaYasrh";// 修正済み

        public void print() {
            System.out.println(this.nane);
        }
    }

2.10.2 文字同士が同じ値が比較するときは、equals()メソッドを利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  文字列同士の比較には「」や「!=」演算子ではなく、Stringクラスのequals()メソッドを使用してください。
これらの演算子を使用した場合、「同じ文字列かどうか」を比較するのではなく、「同じインスタンスがどうか」をチェックすることになってしまいます。そのため、例えば同じ文字列を「
」で比較しても結果がfalseになることがあり、バグの原因になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    違反例
    public class BadSample {
        public static void main(String[] args) {
            String str1 = "hoge";
            String str2 = "hoge";
            System.out.println(str1 == str2);
            // 違反:正しいように思えるが、インスタンスの比較しかしていない
            str1 = str1 + "fuga";// Stringの新しいインスタンスを生成
            str2 = str2 + "fuga";// Stringの新しいインスタンスを生成
            System.out.println(str1 == str2);
            // 違反:同一インスタンスではないと中身が同じでもfalseを返す
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            String str1 = "hoge";
            String str2 = "hoge";
            System.out.println(str1.equals(str2));
            // 修正済み:中身で比較している
            str1 = str1 + "fuga";// Stringの新しいインスタンスを生成
            str2 = str2 + "fuga";// Stringの新しいインスタンスを生成
            System.out.println(str1.equals(str2));
            // 修正済み:中身で比較している
        }
    }

2.10.3文字列の連結にはStringBuliderカラスを利用する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  文字列を連結する際、「+」演算子を用いていませんか?
  Stringは変更不可能なオブジェクトであり、連結するとその都度オブジェクトを作り直すことになるため、パフォーマンスが低下します。
  以下のようにしてください。

  • 連結を前提とした文字列はStringBuliderで定義する
  • 文字列連結にはStringBuliderのappend()メソッドを使う
  • 同期化が必要な文字列連結ではStringBufferを使う

なお、「String name ="a"+"b"」のように、1ステートメントで「+」演算子を使う場合、パフォーマンスが低下しません。1ステートメントだけ記述する場合は、「+」演算子のほうが可読性が高くなる場合が多いため、「+」演算子を利用してもよいでしょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    違反例
    public class BadSample {
        public static void main(String[] arqs) {
            String s = "";
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                s += Integer.valueOf(i);// 違反
            }
            System.out.println("所要時間(ミリ秒)):"
      + (System.currentTimeMillis() - start));
            System.out.println(s);
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] arqs) {
            StringBuilder builder = new StringBuilder();
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                builder.append(i);// 修正済み
            }
            System.out.println("所要時間(ミリ秒)):"
      + (System.currentTimeMillis() - start));
            System.out.println(new StringBuilder(builder));
        }
    }

2.10.4文字列リテラルと変数を比較するときは、文字列リテラルのequals()メソッドを利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  文字列を比較するためにequals()メソッドを使用する場合、以下の2とおりの記述が出来ます。

  • Stringオブジェクト.equals([文字列リテラル])
  • [文字列リテラル].equals(Stringオブジェクト)

前者ではequals()メソッドを呼ぶ前に、文字列がnullではないことを確認する必要があります。一方後者ではnullを確認する必要がありません。したがって、文字列の比較には[文字列リテラル].equals(string)を用いるようにしてください。これによりnullチェックのコード漏れによりバグが混在するリスクを防ぎ、コードの可読性・保守性を向上させることが出来ます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample {
        public boolean validate(String userInput) {
            return (userInput != null && userInput.equals("y"));// 違反
        }
        public static void main(String[] args) {
            System.out.println("nullのケース" + new BadSample().validate(null));
            System.out.println("相違のケース" + new BadSample().validate("no"));
            System.out.println("一致のケース" + new BadSample().validate("y"));
        }
    }

    修正例
    public class GoodSample {
        public boolean validate(String userInput) {
            return "y".equals(userInput);// 修正済み
        }
        public static void main(String[] args) {
            System.out.println("nullのケース" + new GoodSample().validate(null));
            System.out.println("相違のケース" + new GoodSample().validate("no"));
            System.out.println("一致のケース" + new GoodSample().validate("y"));
        }
    }

2.10.5プリミティブ型とStringの変換には、用意された変換用のメソッドを利用する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  String型とプリミティブ型を変換する方法が種々ありますが、用意されている変換用のメソッドを使用するのが最もわかりやすく、処理も効率的です。出来るだけvalueOf/parselntなどの変換用メソッドを使いましょう。
  また、Stringからintなどに変換するときにvalueOf/parselntを使うときと、数値として適切でない値が入る可能性があります。その場合、NumberFormatExecptionが発生します。NumberFormatExecptionはランタイム例外なので、本来キャッチしなくても良いはずです。しかし、型変換の場合は、デフォルト値を返すケースや、例外としてログ出力するケースなどもあるので、そのような状況下ではキャッチして適切な処理を行ってください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    違反例
    public class BadSample {
        public static void main(String[] args) {
            int i = 123;
            String s1 = "" + i;// 違反
            String s2 = new Integer(i).toString();// 違反
            System.out.println(s1);
            System.out.println(s2);

            int i1 = new Integer(s1).intValue();// 違反
            int i2 = Integer.valueOf(s2).intValue();// 違反
            System.out.println(i1);
            System.out.println(i2);
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            int i = 123;
            String s1 = String.valueOf(i);// 修正済み
            System.out.println(s1);

            try {
                Integer i1 = Integer.valueOf(s1);// 修正済み
                int i2 = Integer.parseInt(s1);// 修正済み
                System.out.println(i1);
                System.out.println(i2);
            } catch (NumberFormatException e) {
                /**
                 * NumberFormatExceptionはランタイム例外だが、 
                 * 数値として適切ではない値が渡ってくる可能性がある場合は
                 * ここで適切な処理を施す
                 */
                throw e;
            }
        }
    }

2.10.6システム依存記号(\n、\rなど)は使用しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  「\n」や「\r」などを改行コードとしてコードにリテラルで利用していませんか?
  OSが異なると、改行コードとして利用される文字や文字列も異なります。そのため、「\n」や「\r」などを改行コードとしてリテラルで利用してしまうと、可搬性が悪くなります(Javaの特徴である「Write Once,Run Anywhere」という可搬性が失われます)。
  改行コードやファイルセパレータなどを利用したい時は、システム固有のコードに依存した作りにしないように、System.getProperty()などを使用し、システムに応じた改行コード・ファイルセパレータを取得して利用するようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    違反例
    public class BadSample {
        public void displayMessage(String message) {
            StringBuilder result = new StringBuilder("Message:\n");// 違反
            result.append(message);
            System.out.println(result);
        }

        public static void main(String[] args) {
            new BadSample().displayMessage("Hello");
        }
    }

    修正例
    public class GoodSample {
        public void displayMessage(String message) {
            StringBuilder result = new StringBuilder("Message");// 修正済み
            String lineSeparator = System.getProperty("line.separator");
            result.append(lineSeparator);
            result.append(message);
            System.out.println(result);
        }

        public static void main(String[] args) {
            new GoodSample().displayMessage("Hello");
        }
    }

2.11 数値

2.11.1 誤差なく計算したいときは、BigDecimalクラスを使う

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  float、doubleなどの浮動小数点演算は科学技術計算などに利用できますが、内部で数値を2進数の形で保持するため、10進小数を正確に計算することが出来ません。これに対して、数値を整数値と小数点以下の桁数に分けて保持する「BigDecimal」クラスは10進小数を正確に計算出来るため、金額などの計算に適しています。
  なお、BigDecimalについては、doubleを引数に取るコンストラクタは使用すべきではありません。数値がdoubleになった時点で、誤差が生じるためです。精度が必要な場合は、int、long、String、char[]のコンストラクタを使用してBigDecimalをインスタンス化し、演算してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample {
        public static void nain(String[] args) {
            double d = 0.0;
            for (int i = 0; i < 100; i++) {
                d += 0.1;
            }
            // 10になる想定だが、9.99999999999998となる
            System.out.println(" double:" + d);
        }
    }
    修正例
    public class GoodSample {
        public static void nain(String[] args) {
            BigDecimal value = new BigDecimal("0.0");
            BigDecimal addValue = new BigDecimal("0.1");
            for (int i = 0; i < 100; i++) {
                value = value.add(addValue);
            }
            // 正しく10になる
            System.out.println(" BigDecimal:" + value.toPlainString());
        }
    }

2.11.2 数値を比較するときは精度に気をつける

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  doubleやfloatなどの浮動小数点の数値には、計算によって丸め誤差が生じます。従って、比較演算子を用いて数値同士を比較した場合、想定している結果が得られるかどうか保証されません。
  丸め誤差が生じますことによって問題が起こるようなアプリケーションの場合はdoubleやfloatの代わりにBigDecimalを使ってください。
  また、ある程度の誤差を許容出来る場合は、修正例のような比較メソッドを作成し、許容する誤差の範囲で等しいかどうか比較するようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    違反例
    public class BadSample {
        public static void main(String[] args) {
            new BadSample().sampleMethod();
        }

        private void sampleMethod() {
            double d = 0;
            // 違反.0.1を10回足しても1にならないため、永久ループになる
            while (d != 1) {
                System.out.println("value:" + d);
                d += 0.1;
            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            new GoodSample().sampleMethod();
        }
        private void sampleMethod() {
            double d = 0;
            // 修正済み.10e-8の誤差は許容する
            while (equalsDouble(d, 1, 10e-8) == false) {
                System.out.println("value:" + d);
                d += 0.1;
            }
        }
        private boolean equalsDouble(double value1, int value2, double delta) {
            double diff = Math.abs(value1 - value2);
            if (diff <= delta) {
                return true;
            } else {
                return false;
            }
        }
    }

2.11.3 低精度プリミティブへのキャストを避ける

⭐ ⭐ ⭐ ⭐

【説明・動機】
  数値を表すプリミティブ型の変数には数値の精度が定義されています。高い精度の変数を低い精度の型でキャストを行うと、キャストした型の精度に値が変更されてしまいます。そのため、計算によっては誤差が生じてしまう可能性があります。出来るだけ低精度の型へのキャストは避けてください。

【解説】
以下のプログラムでは、long型の数値をint型にキャストします。実行すると、以下が出力されます。
int > long

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    サンプル
    public class Sample {
        private void sampleMethod() {
            int inValue = 100;
            long longValue = 1000000000000L;
            int longToValue = (int) longValue;
            if (inValue > longToValue) {
                System.out.println("int > long");
            }
        }
    }

2.12 日付

2.12.1 Date、Calendarの代わりにlongの利用を検討する

⭐ ⭐

【説明・動機】
  「日付を扱う際、CalendarやDateを使用しなければならない」と思っていませんか?単純に日付のデータだけを扱いたい場合はlongでも代用が出来ます。
  現在時刻を取得したい場合は、System#currentTimeMillis()(またはSystem#nanoTime())を利用し、戻り値のlongを保持するのがもっとも低コストです。大量の日付を保持する場合で、リソース利用量を抑えたいときは、longを使用することを検討しましょう。
  なお、DateやCalendarは後からも値の変更が可能なオブジェクトです。DateやCalendar宣言した日付をいくつかのオブジェクトから共有する場合、値の変更に注意してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    サンプル
    public class GoodSample {
        private void sampleMethod() {
            // longからCalendar、Dateに変換
            long longTime = System.currentTimeMillis();
            Calendar longToCalender = Calendar.getInstance();
            longToCalender.setTimeInMillis(longTime);
            Date longDate = new Date(longTime);

            // Calendarからlongに変換
            Calendar calendar = Calendar.getInstance();
            long calendarToLongTime = calendar.getTimeInMillis();

            // Dateからlongに変換
            Date date = new Date();
            long dateToLongTime = date.getTime();
        }
    }

2.13 Java5以後に追加された機能

2.13.1 出来るだけ拡張for文を使う

⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5以降でfor文を使うときは、出来るだけ拡張for文を使ってください。拡張for文を使えば、従来のfor文によりシンプルに繰り返し処理が実現できます。例えば今まであったインデックス値によるアクセスやイテレータを使わずに記述出来、より処理の内容を特化したコードを記述できるようになります。積極的に活用しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    違反例
    public class BadSample {
        public static void main(String[] args) {
            String[] strs = { "a", "b", "c", "d", "e" };
            // 違反.インデックス値は特に必要ない。
            for (int i = 0; i < strs.length; i++) {
                String s = strs[i];
                System.out.println("array string:" + s);
            }

            List<String> list = new ArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            //違反.イテレータは特に必要ない。
            for (Iterator<String> itr= list.iterator(); itr.hasNext(); ) {
                String s=itr.next();
                System.out.println("interator string" + s);
            }

            Map<Integer, String> map = new HashMap<Integer, String>();
            map.put(1, "AAA");
            map.put(2, "BBB");
            map.put(3, "CCC");
            map.put(4, "DDD");
            //違反.イテレータは特に必要ない。
            Iterator<Integer> iterator= map.keySet().iterator();
            while (iterator.hasNext()) {
                int key = iterator.next();
                String value = map.get(key);
                System.out.println("key==" + key + "..value==" + value);
            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            String[] strs = { "a", "b", "c", "d", "e" };
            // 修正済み 拡張for文で出来るだけシンプルに記述しましょう。
            for (String s : strs) {
                System.out.println("array string:" + s);
            }

            List<String> list = new ArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");

            // 修正済み 拡張for文で出来るだけシンプルに記述しましょう。
            for (String s : list) {
                System.out.println("interator string" + s);
            }


            Map<Integer, String> map = new HashMap<Integer, String>();
            map.put(1, "AAA");
            map.put(2, "BBB");
            map.put(3, "CCC");
            map.put(4, "DDD");
           //修正済み 拡張for文で出来るだけシンプルに記述しましょう。
            for (Entry<Integer, String> entry : map.entrySet()) {
                int key = entry.getKey();
                String value = entry.getValue();
                System.out.println("key==" + key + "..value==" + value);
            }
        }
    }

2.13.2 Collectionを積極的に使って配列を減らす

⭐ ⭐ ⭐ ⭐

教育資料:[[Generics・ジェネリクス]]

【説明・動機】
  Java5以降でfor文とジェネリスクを使うことでわかりやすく保守性の高いコードを簡潔に書くこと出来ます。これを使われない手はありません。
  そのためには出来るだけ配列ではなく、コレクションを使いましょう。なぜなら配列はジェネリクスの型が効果的に使われないためです。コレクションを使い、型をジェネリクスで正しく指定し、その上で拡張for文を使いましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    違反例
    public class BadSample {
        public static void main(String[] args) {
            List<String> list1 = Arrays.asList("a", "b");
            List<String> list2 = Arrays.asList("c", "d");
            // コンバイルエラー
            // List<String>[] allList = new List<String>[] { list1, list2 };
            List<?>[] allList = new List<?>[] { list1, list2 };
            for (List<?> list : allList) {
                for (Object object : list) {
                    // 型情報か欠落してしまう。
                    System.out.println(object);
                }

            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            List<String> list1 = Arrays.asList("a", "b");
            List<String> list2 = Arrays.asList("c", "d");

            List<List<String>> allList = new ArrayList<List<String>>();
            allList.add(list1);
            allList.add(list2);

            for (List<String> list : allList) {
                for (String string : list) {
                    // ジェネリクスの型があるのでタイプセーフに拡張for文で記述可能
                    System.out.println(string);
                }
            }
        }
    }

2.13.3 アノテーションでコードの状態をわかりやすくする

⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5以降はアノテーション(注釈)をコードにつけることが出来ます。アノテーションをつけることで、「そのコードが何の処理を行うのか」「どういう意味があるのか」ということを表現出来ます。アノテーションそれ自体は処理を持ちませんが、以下のように活用することが出来ます。

  • アノテーションを手がかりにしてプログラムで処理をする
  • アノテーションの型を指定して、アノテーションを付与した部分を検索する

またアノテーションは、プログラムだけでなく、コンパイラやIDEからも利用されます。
  例えば標準のアノテーションである@Overrideは、コンパイルする際に正しくメソッドをオーバーライド出来ているかどうかをチェックしてくれます。また、@Deprecatedは対象の変数・メソッド・クラスが非推奨であることをIDEが視覚的に示す手がかりとなります。
  いずれも開発をする際に役に立つため、積極的に利用しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    サンプル
    public interface AnntationUseSample {
        String excute(String[] args);

        /**
         * このメソッドは廃止します。バージョン×××までには廃止の予定です。
         * 代わりに{@link #excute(String[])} を使用してください
         */
        @Deprecated   // このメソッド今後廃止を検討していることを明記
        String excuteOther(String arg);
    }
    public class AnnotationUseSampleImpl implements AnntationUseSample {
        @Override
        // インタフェースをオーバライドしていることを明記
        public String excute(String[] args) {
            if (args == null) {
                return "";
            } else {
                StringBuilder builder = new StringBuilder();
                for (String s : args) {
                    builder.append(s);
                    builder.append(":");
                }
                if (0 < builder.length()) {
                    builder.setLength(builder.length() - 1);
                }
                return new String(builder);
            }
        }

        /**
         * このメソッドは廃止します。バージョン×××までには廃止の予定です。
         * 代わりに{@link #excute(String[])} を使用してください
         */
        @Override
        @Deprecated
        public String excuteOther(String arg) {
            return excute(new String[]{arg});
        }
    }

2.13.4 アノテーションの基本的な作り方を把握する

⭐ ⭐

【説明・動機】
  Java5以降のアノテーションは自分で作成することも可能です。基本的な作り方を学んでおくと、JavaのAPIやフレームワーク上でカスタマイズした処理を作成するときに便利です。
  Javaが提供する標準メタアノテーション(アノテーションのアノテーション)の意味を把握し、正しく使い分けましょう。

【解説】

Javaでアノテーションを書くときには特別なインタフェース定義である@interfaceを使います。
@interfaceは通常のインタフェースとは違う2つの特徴があります。

  • メソッドの戻り値としては、String、プリミティブ、Class、アノテーション、enumまたはその1次元配列
  • アノテーションを使う側が定義を省略したときのデフォルト値をdefaultで定義できる。

例えばアノテーション定義は以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
サンプル
    /**
     * クラスだけにこのアノテーションをつける事が出来る。
     */
    @Target(ElementType.TYPE)
    /**
     * 実行時に使用される。
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnnotationSample {

        /**
         * 値は必須で指定する。
         */
        String value();

        /**
         * priorityの指定がない場合は、-1をデフォルト値として使用する。
         */
        int priority() default -1;

        /**
         * targetTypeの指定がない場合、Stringをデフォルト値として使用する。
         */
        Class<?> targetType() default String.class;

        /**
         * LogOptionの指定がない場合、DEBUGをデフォルト値として使用する。
         */
        LogOption logOption() default logOption().DEBUG;
    }

【解説】

一方、利用側は下記のようになります。

1
2
3
4
5
    サンプル
    @AnnotationSample(value="アノテーションサンプルです。",
    logOption=LonOption.FATAL);
    public class Hoge {
    }

【解説】

Javeが提供されている標準メタアノテーションには以下のものがあります。

アノテーション名説明

@Target : クラスやメソッドなどアノテーションの対象とするElementTypeを指定する
@Retention : アノテーションの有効期間をRetentionPolicyで指定する
@Inherited : 子クラスまでアノテーションの効果を維持する
@DocumentedJavadoc : でドキュメントにアノテーション内容が維持される

アノテーションの要素指定と有効期間の設定を行うenumには以下のものがあります。

クラス名説明

ElementTypeアノテーションの対象要素を指定します。
2.13.4-1
RetentionPolicyアノテーションの有効期間を指定します。
2.13.4-2

2.13.5 ListやMapにはジェネリクスを使う

⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5以降ではジェネりクスによって、型をより厳密に宣言出来るようになりました。ジェネりクスを使えば、以下のようなことが出来ます。

  • 今まで行っていたListやMapでのキャストでの型の相違を避ける(修正例の1、3)
  • 問違った型の値を投入したりえするリスク(修正例の2)を減らす

何より、そのListやMapに入る型が厳密にわかるので、より堅守で保守性の高いコードを書くことが出来ます。ListやMapなどコレクションを扱う場合には、ジェネりクスで正しく型を宣言しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    違反例
    public class GenericsBadSample {
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");

            for (int i = 0; i < list.size(); i++) {
                // ClassCastのリスク
                Integer num = (Integer) list.get(i);
            }

            Map map = new HashMap();
            map.put("a", new Integer(1));
            map.put("b", "mogemoge");

            for (Iterator itr = map.keySet().iterator(); itr.hasNext();) {
                String key = (String) itr.next();
                // ClassCastのリスク
                System.out.println(key);
            }
        }
    }

    修正例
    public class GenericsSample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");

            for (int i = 0; i < list.size(); i++) {
                Integer num = (Integer) list.get(i);
                // 1.コンパイルエラー。コンパイル時に間違いを教えてくれる
                String s = list.get(i);// 正確にはこちら
            }

            Map<String, String> map = new HashMap<String, String>();
            map.put("a", new Integer(1));
            // 2.コンパイルエラー。コンパイル時にput出来ないことを教えてくれる
            map.put("b", "mogemoge");

            for (Iterator<String> itr = map.keySet().iterator();
                itr.hasNext();) {
                String key = (String) itr.next();
                Integer i = (Integer) map.get(key);
                // 3.コンパイルエラー。コンパイル時に間違いを教えてくれる
                String s = map.get(key);
                System.out.println(s);
            }
        }
    }

2.13.6 オートボクシングは使用しないか、null変換を必ず行う

⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5から、プリミティプ型とラッパー型を自動的に変換してくれるオートボクシング機能が利用出来るようになりますた。プリミティプ型からラッパー型への変換をボクシングといい、その逆をアンボクシングと言います。
  一見非常に便利を機能に思えますが、ラッパー型でnullの値をアンボクシングしてプリミティプ型に変換するときが問題になります。ラッパー型で値がnullの時にアンボクシングすると、その場でNullPointerExcepionが発生して、非常にわかりにくいバグとなる可能性がまります。
  そのため、オートボクシングはプロジェクト全体として使わないとするか、使うときにはnull値変換ユーティリティを通してからアンボクシングするなどの対策を施してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    違反例
    public class BadSample {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(null);

            // 違反.オートボクシングによるキャスト時に
            // NullPointerException発生のリスク
            for (int i : list) {
                System.out.println(i);
            }
        }
    }


    修正例
    public class GoodSample {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            integer num = null;

            /*
             * 修正済み。
             *  オートボクシングによるキャスト時のNullによる例外発生を例えば
             *  以下のユーティリティメソッドなどデフォルト値を設定し、防ぐ。
             */
            final int defaultValue = getDefaultValueIfNull(int.class, num);
            list.add(defaultValue);
            for (int i : list) {
                System.out.println(i);
            }
        }

        /**
             * オートボクシングでラッパー型からプリミティブ型に変換する際に
             * nullの場合に、あらかじめ決めたデフォルト値に変換する
             * ユーティリティサンプル。
             *
             * @param <T>
             *            ラッパー型の型名
             * @param type
             *            ラッパー型のクラス
             * @param value
             *            実際の変換したい値
             * @return プリミティブ型に変換された値
             */
        @SuppressWarnings("unchecked")
        private static <T> T getDefaultValueIfNull(Class<T> type, T value) {
            if (value != null) {
                return value;
            }
            // 内容省略
        }
    }

2.13.7 ジェネリクスを代入する場合は境界ワイルドカードを使う

⭐ ⭐

【説明・動機】
  AクラスがBクラスの親クラスの場合、Aクラスの配列に対して、Bクラスの配列を代入することが出来ます。例えばNumberはDoubleの親クラスなので、Numberの配列の変数に対して、Doubleの配列を代入することが出来ます
  しかしジェネリクスを使用する場合、この代入がうまく行かないケースがあります。
  例えばList<Number>に対して、Listを代入することは出来ません。ListとListは異なるクラスとして取り扱われるためです。
  このような場合、境界ワイルドカードを使用してください。境界ワイルドカードは、「?extends T」という形でジェネリクス型を指定する方法です。前述の例は「Tクラスを継承していればどのようなクラスでもよい」と宣言しているのと同じ意味になります。從って、List<?extends Number>のように宣言することで、Listを代入することが出来ます。またListやListも代入することが出来ます。
  なお、「?super T」という宣言も可能ですが、利用ケースが少ないため、説明を割愛します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    違反例
    public class BadSample {
        public static void main(String[] args) {
            Double[] doubleArray = new Double[] { Double.valueOf(1.11),
            Double.valueOf(2.22) };
            //Numberの配列にDoubleの配列を代入出来る
            Number[] numberArray = doubleArray;

            List<Double> doubleList = new ArrayList<Double>();
            doubleList.add(Double.valueOf(1.11));
            doubleList.add(Double.valueOf(2.22));
            //以下はコンパイルエラー
            List<Number> numberList = doubleList;
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            Double[] doubleArray = new Double[] { Double.valueOf(1.11),
            Double.valueOf(2.22) };
            // Numberの配列にDoubleの配列を代入出来る
            Number[] numberArray = doubleArray;

            List<Double> doubleList = new ArrayList<Double>();
            doubleList.add(Double.valueOf(1.11));
            doubleList.add(Double.valueOf(2.22));

            // 境界ワイルドカードを使用
            List<? extends Number> numberList = doubleList;
        }
    }

2.13.8 グループの固定値を表す場合はenumを使う

⭐ ⭐ ⭐

教育資料:[[Enumの使い方]]

【説明・動機】
  Java5から固定値のグループを表現するenumが使えるようになりました。enumを使うと、あるグループ内に含まれる項目を簡潔に表現出来ます。あるグループを宣言し、その中から選択肢を与えるようなコードを書く状況では、enumを使いましょう。
  なお、enumの値を追加や削除した場合は、そのenumを参照している部分の再コンパイルが必要になります。この点には注意しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    サンプル
    //職業種別を表すenum
    public enum JobType {
        PROGRAMMER, ARCHITECT, SALES, STAFF, MANAGER, PRESIDENT;
    }
    //利用するコード
    public class EnumSample {
        protected final JobType jobType;
        public EnumSample(JobType jobType) {
            this.jobType = jobType;
        }
        public JobType getJobType() {
            return this.jobType;
        }
        public static void main() {
            //enumで、インスタンスに確実にグループ範囲内の選択肢を
            //渡すことが出来る
            EnumSample sample = new EnumSample(JobType.PROGRAMMER);
            JobType job = sample.getJobType();
            System.out.println(job);
            //文字列からもvalueOf()で取得可能
            JobType programmer = JobType.valueOf("PROGRAMMER");
            if (programmer == job) {
                System.out.println("JobTypeの取得に成功:" + job);
            }
            //Enum#values()でenumの全要素を取得可能
            for (JobType type : JobType.values()) {
                printJobType(type);
            }
        }
        private static void printJobType(JobType job) {
            //enumでスイッチ文の記述が可能
            switch (job) {
            case ARCHITECT:
                System.out.println("アーキテクト");
                break;
            case PROGRAMMER:
                System.out.println("プログラマ");
                break;
            case MANAGER:
                System.out.println("マネージャ");
                break;
            case PRESIDENT:
                System.out.println("プレジデント");
                break;
            case SALES:
                System.out.println("セールス");
                break;
            case STAFF:
                System.out.println("スタッフ");
                break;
            default:
                System.out.println("どれにも当てはまりませんでした。");
                break;
            }
        }
    }

2.13.9 存在しない値についてEnum#valueOfを呼び出さない

⭐ ⭐ ⭐

教育資料:[[Enumの使い方]]

【説明・動機】
  文字列からenumへ変換するにはEnum#valueOf()を使うのが便利です。しかし、valueOfメソッドの入力にenumに存在しない値が入ってくる可能性はありませんか?
  Enum#valueOf()で存在しない文字列を入力すると、illegalArgumentExceptionがスローされます。例えばDBからプルダウン項目をとってきて画面に表示するような場合、それよりはnullが返って来るほうがベターです。このような場合は自分で変換ロジックを書くことをお奨めします。以下のように使い分けるといいでしょう。

  • enumの値を完全に自分でコントロールしている場合  ⇒  Enum#valueOf()
  • そうでない場合⇒変換ロジックを書く
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    違反例
    public class BadSample {
        public static void main(String[] args) {
            //DBから取ってきた言語コードをenumに変換する
            String s = getFromDatabase(-1);
            Lanuage lang = Language.valueOf(s);//違反
            System.out.println(lang);
        }

        (省略)
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            //DBから取ってきた言語コードをenumに変換する
            String s = getFromDatabase(-1);
            Lanuage lang = GoodSample.toEnum(s);//修正済み
            System.out.println(lang);
            Lanuage ja = toEnum("JAPANESE");
            System.out.println(ja);
        }

        /**
         * IlleaglArgumentExceptionを避けるには、独自の変換ロジックを作成する
         */
        private static Lanuage toEnum(String s) {
            for (Lanuage lang : Lanuage.values()) {
                if (lang.name().equals(s)) {
                    return lang;
                }
            }
            return null;
        }
        (省略)
    }

2.13.10 可変長引数は極力使わない

⭐ ⭐ ⭐

【説明・動機】
  Java5からは可変長引数が導入され、メソッドで任意の個数の引数を受け取ることが出来ます。例えばhoge(String・・・args)と記述があった場合、引数がゼロからいくつても受け取れます。内部的にはコンパイル時に配列として変換されます。 一見便利に思えますが、以下の点で実装上の不都合やバグを引き起こす可能性があります。

  • 呼び出し側で明示的にどのメソッドを呼んでいるかわかりにくい
  • 可変長引数はメソッドの最後の引数にのみ適用可能なので、Javaの都合に縛られ、本来するべきメソッド設計にならない
  • 特に型が曖昧な可変長引数だと想定しない引数を呼んでしまう

これらの理由から、可変長引数は使わずに、コレクションまたは通常の配列を使うことを検討してみてください
(詳細は「2-13-2Collectionを積極的に使って配列を減らす」を参照してください)。コレクションで要求を満たさない場合に配列を使うことを検討するようにしましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    違反例
    public class BadSample {
        public static String append(String header, String... args) {
            StringBuilder builder = new StringBuilder();
            builder.append(header);
            builder.append("/");
            for (String s : args) {
                builder.append(s);
            }
            return new String(builder);
        }

        public static String append(String header) {
            throw new UnsupportedOperationException("まだ実装されていません。");
        }

        public static void main(String[] args) {
            // 可変長引数3つでappend(String header, String... args)
            // をコール
            String ret = BadSample.append("HEADER", "a", "b", "c");
            System.out.println(ret);

            // 可変長引数2つでappend(String header, String... args)
            // をコール
            ret = BadSample.append("HEADER", "a", "b");
            System.out.println(ret);

            // 可変長引数1つでappend(String header, String... args)
            // をコール
            ret = BadSample.append("HEADER", "a");
            System.out.println(ret);

            // 可変長引数なしでappend(String header, String... args)
            // をコール、しているつもりが、実は意図せずappend(String header)
            // を呼び出している。
            ret = BadSample.append("HEADER");//違反
            System.out.println(ret);
        }
    }

    修正例
    public class GoodSample {
        public static String append(String header, String[] args) {
            StringBuilder builder = new StringBuilder();
            builder.append(header);
            builder.append("/");
            if (args != null) {
                for (String s : args) {
                    builder.append(s);
                }
            }
            return new String(builder);
        }

        public static String append(String header) {
            throw new UnsupportedOperationException("まだ実装されていません。");
        }

        public static void main(String[] args) {
            String ret = GoodSample.append("HEADER", new String[] { "a", "b", "c" });
            System.out.println(ret);

            ret = GoodSample.append("HEADER", new String[] { "a", "b" });
            System.out.println(ret);

            ret = GoodSample.append("HEADER", new String[] { "a" });
            System.out.println(ret);

            ret = GoodSample.append("HEADER",new String[0]);// 修正済み
            //とはいえ、バグ混在の可能性になる
            System.out.println(ret);
        }
    }

2.13.11 非推奨のクラス・メソッド・フィールドには@Deprecatedをつけて代替手段を提示する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  @DeprecatedはJavaが提供する標準アノテーションの1つで、非推奨のクラス・メソッド・フィールドに対して付与するものです。@Deprecatedが付与されたクラス・メソッド・フィールド他をのコードから使用した場合、コンパイル時に警告を出すことが出来ます。またIDEによっては、非推奨であることを視覚的に確認出来ます。これを@deprecated javadocタグと併用することにより、ドキュメンテーションコメントとして代替手段を提示することが出来ます。両者を組み合わせて非推奨を強調するようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    サンプル
    public class DeprecatedSample {
        /**
         * 幅を設定します
         * @param width 幅
         * @deprecated このメソッドは置き換えられました。
         * {@link #setSize(int, int)}
         */
        @Deprecated
        public void setWidth(int width) {
        }

        /**
         * サイズを設定します
         *
         * @param width 幅
         * @param height 高さ
         */
        public void setSize(int width, int height) {
        }
    }

2.13.12 補助文字を使用する場合は、String#length()とString#charAt()を使わない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5からUnicode4.0がサポートされ、補助文字(SupplementaryCharacter)が利用出来るようになりました。
  補助文字とはUnicode規格で定義される文字のうち、コードポイントがU+FFFFを超えているもののことです。補助文字を表すには、1文字に対して2つのコード単位(char2つ)が必要です。String#length()は、String内のchar数をカウントするメソッドのため、補助文字の入ったStringの場合、文字数とString#length()の戻り値は一致しなくなります。
  この不一致を解消するため、StringクラスにcodePointCount()というメソッドが追加されています。補助文字が入ったStringの文字数を数える場合は、codePointCount()を使用するようにしましょう。
  また逆に、補助文字をStringから取得する際にcharAt()を使用すると、補助文字を表す2つのコードのうち片方しか取得出来ないため、意図しないcharが取得されてしまいます。この場合は、codePointAt()を使用するようにしましょう。
  なお、codePointCount()メソッドを使用しても、給合文字(濁点や半濁点だけの文字など、直前の文字と組み合わせるための文字)は1コードとしてカウントされてしまいます。本書では詳細は説明しませんが、こういったことも念頭に置き、アプリケーションで許容する文字集合範囲を決めて、適切に実装してください。
  補助文字や文字コードの詳細については、「プログラマのための文字コード技術入門」(矢野啓介 著/技術評論社 刊)を参照すると良いでしょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    //違反例
    public class BadSample {
        public static void main(String[] args) {
            String text = " ![2.13.12](/uploads/37c517f131a599df80f031317774becb/2.13.12.jpg) ";//U+200DB
            System.out.println(text.length());//2が出力される
            //?が出力される
            System.out.println(Character.toChars(text.charAt(0)));
        }

    //修正例
    public class GoodSample {
        public static void main(String[] args) {
            String text = " ![2.13.12](/uploads/37c517f131a599df80f031317774becb/2.13.12.jpg) ";// U+200DB
            // 1が出力される
            System.out.println(text.codePointCount(0, text.length()));
            //?が出力される
            System.out.println(Character.toChars(text.codePointAt(0)));
        }
    }

2.13.13 BigDecimalのフォーマットにはDecimalFormatかBigDecimal#toPlainStringを使う

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5以降ではBigDecimalのtoString()の挙動が変わり、旧来の方法でtoStringしても数値表記になりません。これはJDK1.4から移行するときにバグとなる可能性があります。
  BigDecimal値をフォーマットする場合には2つの選択肢があります。DecimalFormatと、BigDecimalで新たに追加されたBigDecimal#toPlainString()です。以下のように使い分けでください。

  • 整数、固定少数値、パーセント、金額など詳細にフォーマットしたい場合⇒DecimalFormat
  • 簡易的な数値表記形式でかまわない場合⇒toPlainString
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    違反例
    public class BadSample {
        public static void main(String[] args) {
            // 1230000000と表示されるのを期待、
            // しかしJava5以降ではそうならない
            BigDecimal decimal = new BigDecimal("1.23E+9");
            System.out.println(decimal.toString());// 違反

            // 12500000と表示されるのを期待、しかしJava5以降ではそうならない
            BigDecimal decimal2 = new BigDecimal(new Double(12500000).toString());
            System.out.println(decimal2);// 違反
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) throws ParseException {
            DecimalFormat formatter = new DecimalFormat();
            formatter.applyPattern("############.######");

            // 1230000000と表示されるのを期待
            BigDecimal decimal = new BigDecimal("1.23E+9");
            String formatResult = formatter.format(decimal);// 修正済み
            System.out.println("DecimalFormatでのフォーマット結果:"+formatResult);
            String plainStringResult = decimal.toPlainString();// 修正済み
            System.out.println("BigDecimal#toPlainStringでのフォーマット結果:"+ plainStringResult);

            // 12500000と表示されるのを期待
            BigDecimal decimal2 = new BigDecimal(new Double(12500000).toString());
            String formatResult2 = formatter.format(decimal2);// 修正済み
            System.out.println("DecimalFormatでのフォーマット結果:"+formatResult2);
            String plainStringResult2 = decimal2.toPlainString();// 修正済み
            System.out.println("BigDecimal#toPlainStringでのフォーマット結果:"+ plainStringResult2);
        }
    }

2.13.14 マルチスレッドプログラミングをする場合はJava5標準のExecutorフレームワークを利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Java5以降で利用可能になったjava.util.concurrentパッケージとExecutorフレームワークを利用すると、マルチスレッド処理を制御するのが容易になります。java.util.concurrent.ExecutorServiceやjava.util.concurrent.Executorsを中心に、様々な機能が提供されています。
  Executorフレームワークは、スレッドの再利用、スレッド数の管理、処理状態の監視、処理の中断などを行ってくれるため、利用者はスレッド内での処理を実装することに注力出来ます。
  スレッド再利用やスレッド管理などは、不具合なく実装するのがとても困難です。スレッド処理を行う場合は、求める機能がjava.util.concurrentパッケージですでに提供されていないか、Executorフレームワーク上で実現出来るかどうかをまず確認しましょう。詳細なテクニックについては、「3.7 マルチスレッド」を参照してください。

1
shutdown()とshutdownNow()の違い:ExecutorService は、新規タスクを拒否するシャットダウンが可能です。    ExecutorService をシャットダウンするための 2 つの異なるメソッドが提供されています。shutdown() メソッドは以前に送信したタスクを終了前に実行することができ、shutdownNow() メソッドは待機中のタスクが開始されないようにし、現在実行中のタスクを停止しようとします。終了時、executor には、実行中のアクティブなタスクや実行を待機中のタスクは存在せず、新規タスクを送信することもできません。未使用の ExecutorService は、そのリソースを再生可能にするにはシャットダウンされなければいけません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    サンプル
    public class ExecutorSample {
        public static void name(String[] args) {
            System.out.println("[" + Thread.currentThread().getName() + "] Executorを使ったサンプルを開始します。");
            RunnableTask task1 = new RunnableTask();
            CallableTask task2 = new CallableTask();

            ExecutorService executor = Executors.newFixedThreadPool(2);

            // スレッド実行単位のRunnableを実行
            executor.execute(task1);

            // java5から追加されたCallableタスクの実行
            // 戻り値は非同期処理の計算結果オブジェクトであるFuture
            Future<String> future = executor.submit(task2);

            // Futureオブジェクトから結果が取れるまでしばらく待つ。
            String futureResult = null;
            while (futureResult == null) {
                try {
                    futureResult = future.get();
                } catch (InterruptedException e) {
                    System.out.println("スレッドがインタラブトされました");
                } catch (ExecutionException e) {
                    System.out.println("実行時に例外が発生しました");
                }
            }
            System.out.println("Futureからの結果:" + futureResult);

            System.out.println("[" + Thread.currentThread().getName() + "] Executorを停止します。");

            // Executorの停止
            executor.shutdown();
            try {
                boolean awaitTermination = executor.awaitTermination(10, TimeUnit.SECONDS);
                if (awaitTermination == false) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                System.out.println("スレッドがインタラブトされました");
            }

            System.out.println("[" + Thread.currentThread().getName() + "] Executorを停止します。");
        }
    }

    public class RunnableTask implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("[" + Thread.currentThread().getName() + "] GoodTask1の処理を開始しました。");
                TimeUnit.SECONDS.sleep(2);
                System.out.println("[" + Thread.currentThread().getName() + "] GoodTask1の処理を終了しました。");
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    public class CallableTask implements Callable<String> {
        @Override
        public String call() {
            String ret = "FAIL";
            try {
                System.out.println("[" + Thread.currentThread().getName() + "] GoodTask2の処理を開始しました。");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("[" + Thread.currentThread().getName() + "] GoodTask2の処理を終了しました。");
                ret = "SUCCESS";
            } catch (Exception e) {
                e.printStackTrace();
            }
            return ret;
        }
    }

2.13.15 時間の単位変換にはTimeUnitを使う

⭐ ⭐

【説明・動機】
  Java5から導入されたTimeUnit(java.util.concurrent.TimeUnit)には、時間単位の変換に便利なメソッドが多数用意されています。
  時間単位を変換するには、適切な値の乗算や除算が必要ですが、例えば秒からミリ秒への変換などは掛ける値を間違える場合もあります。TimeUnitのtoXXXメソッド(XXXは変換後の時間単位)を使用すれば、変換前の時間単位からわかりやすく変換出来ます。また、TimeUnitの各定数には、sleepメソッドが定義されており、各時間単位指定でThread.sleep()を呼び出せます。こちらも活用するとよいでしょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    違反例
    public class BadSample {
        public static void name(String[] args) throws Exception {
            // ミリ秒への変換
            long millis = 24 * 60 * 60 * 1000;
            System.out.println("24時間は" + millis + "msです");

            long start = System.currentTimeMillis();
            // 10秒停止。Sleepの引数はミリ秒のため変換
            Thread.sleep(10 * 1000);
            long end = System.currentTimeMillis();
            long timeSec = (end - start) / 1000;

            System.out.println("処理時間は" + timeSec + "秒です");
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) throws Exception {
            // 60秒をミリ秒に変換
            long millis = TimeUnit.SECONDS.toMillis(60);
            System.out.println("60秒は" + millis + "msです");

            long start = System.currentTimeMillis();
            // 10秒停止
            TimeUnit.SECONDS.sleep(10);
            long end = System.currentTimeMillis();
            long timeSec = TimeUnit.MILLISECONDS.toSeconds(end - start);

            System.out.println("処理時間は" + timeSec + "秒です");
        }
    }

2.13.16 staticインポートは利用するかどうかを統一する

⭐ ⭐ ⭐

【説明・動機】
  Java5の新機能であるstaticインポートは、staticメソッドの呼び出し部分がシンプルになる反面、以下のデメリットがあります。

  • 呼び出し先がひと目でわからないことがある
  • 從来の呼び出し方(クラス名・static変数・メソッド)と混在すると可読性が落ちる場合がある。
    そのため、プロジェクトで以下のどれかになるように利用規約を定めるようにしましょう。
  • staticインポートは利用しない規約とする
  • テストクラス内のみ利用可能とする(JunitのAssertクラスの各メソッド)
  • 定数値のみ利用可能とする
  • 全般的に利用する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    違反例
    public class StaticClass {
        public static final int STATIC_NO = 1;
        public static final String STATIC_TEXT = "text";

        public static void staticMethod(int n, String t) {
        }
    }

    import static sample.StaticClass.STATIC_NO;
    import static sample.StaticClass.staticMethod;

    public class BadSample {
        public void sampleMethod() {
            int n = STATIC_NO;
            // 一部分だけstaticインポートを使用していない
            String s = StaticClass.STATIC_TEXT;
            staticMethod(n, s);
        }
    }
    修正例
    public class StaticClass {
        public static final int STATIC_NO = 1;
        public static final String STATIC_TEXT = "text";

        public static void staticMethod(int n, String t) {
        }
    }

    import static sample.StaticClass.STATIC_NO;
    import static sample.StaticClass.STATIC_TEXT;
    import static sample.StaticClass.staticMethod;

    public class GoodSample {
        public void sampleMethod() {
            int n = STATIC_NO;
            // 修正済み
            String s = STATIC_TEXT;
            staticMethod(n, s);
        }
    }

3.プログラミングルール/テクニック編

3.1 継承

3.1.1 継承の前に委譲を検討する

⭐ ⭐ ⭐

【説明・動機】
  同じ処理が複数のクラスで出てきた際に、安易に継承しようと考えていませんか?
  親クラスはユーティリティメソッドや共有フィールドの置き場ではありません。役割の重複が判明した場合、まずは処理の委譲を検討しましょう。一般に、継承関係が深くなっていくにつれ、どこで何の処理が行われているのかわからなくなり、バグの温床となります。委譲の形で実装すれば、継承が深くなるのを防ぐことが出来ます。
  また、Javaではextendsを使った継承は1つの親クラスからしか出来ません。そのため、本来継承関係にあるものだけをextendsを使った継承で実装するようにしましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    違反例
    public abstract class AbstractProducerLogicBase {
        // ...
    }

    public abstract class AbstractProducerLogicExtBase extends
            AbstractProducerLogicBase {
        // ...
    }

    // 違反、安易に継承構造を深くしている
    public class BadProducerLogicImpl extends AbstractProducerLogicExtBase {
        // ...
    }

    修正例
    public class GoodProducerLogicImpl extends AbstractProducerLogicBase {
        protected AbstractProducerLogicExtBase delegate;

        public GoodProducerLogicImpl(AbstractProducerLogicExtBase delegate) {
            this.delegate = delegate;
        }
        @Override
        public Status execute(String[] args) {
            // 修正済み、適切に委譲を使う。
            Status status = this.delegate.execute(args);
        }
    }

3.1.2 スーパークラスのメンバ変数と同じ変数名をサブクラスで宣言しない

⭐ ⭐ ⭐ ⭐

教育資料:[[オブジェクト指向について]]

【説明・動機】
  スーパークラスで宣言しているメンバ変数があり、その変数と同じ名前の変数をサブクラスで宣言すると、スーパークラスの変数はサブクラスの変数によって隠れてしまいます。変数が隠れてしまうと、「同じ変数にアクセスしているつもりでも、実際は異なる変数にアクセスしている」というケースが起こりうるため、不具合の温床になりがちです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    違反例
    public class BadSample {
        protected int number = 4;

        public void print() {
            System.out.println("BadSample の number変数の値は、" + number);
        }
    }

    public class ChildBadSample extends BadSample {
        protected int number = 5; // 違反

        @Override
        public void print() {
            super.print();
            System.out.println("ChildBadSample の number変数の値は、" + number);
        }

        public static void main(String[] args) {
            new ChildBadSample().print();
        }
    }

【解説】

上記プログラムの実行結果は以下のとおりです。
BadSampleのnumber 変数の値は、4
ChildBadSampleのnumber 変数の値は、5
同一変数にアクセスしているつもりでも、親クラスと子クラスで実際は異なる変数(BadSampleのnumber変数の値は4、ChildBadSample のnumber変数の値は5)にアクセスしてしまっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    修正例
    public class GoodSample {
        protected int number = 4;

        public void print() {
            System.out.println("number is " + number);
        }
    }
    public class ChildGoodSample extends GoodSample {
        protected int childNumber = 5; // 修正済み
        @Override
        public void print() {
            super.print();
            System.out.println("number is " + number);
            System.out.println("childNumber is " + childNumber);
        }
        public static void main(String[] args) {
            new ChildGoodSample().print();
        }
    }

【解説】

上記プログラムの実行結果は以下のとおりです。
GoodSampleのnumber 変数の値は、4
ChildGoodSampleのnumber 変数の値は、4
ChildGoodSampleのchildNumber 変数の値は、5
親クラスと子クラスで確実に違う変数名をつけることで、どちらにアクセスしているのかがはっきりわかります。

3.1.3 equals()メソッドとhashCode()はIDEで自動生成する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  equals()をオーバーライドする場合、hashCode()もオーバーライドしましょう。equals()が等しい2つのオブジェクトは必ずhashCode()が等しくなるように、またその逆も成り立つように記述する必要があるためです。このような漏れを防ぐためには、なるべく手作業を介さないことが重要です。equals()とhashCode()はEclipseなどのIDEを活用して自動生成しましょう。Eclipseでは、equals()と同時にhashCode()も生成してくれるため、記述漏れが少なくなります。equals()をオーバーライドしたのにhashCode()をオーバーライドしていないと、HashMap、HashSet、HashTableを含むすべてのハッシュに基づくコレクションで使用された場合に正しく機能しません。
  どうしても手作業になる場合も、equals()とhashCode()のどちらかをオーバーライドした場合は、必ずもう一方もオーバーライドしてください。

【解説】

以下はIDNumberクラスを使ってequals()メソッドとhashCode()メソッドを実装した例です。以下のequals()メソッドは、id属性の値が等しければtrueを返すことになっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    サンプル
    public class IDNumber {
        final int num;
        public IDNumber(int num) {
            this.num = num;
        }

        @Override
        public boolean equals(Object object) {
            boolean isEqual = false;
            if (object == this) {
                isEqual = true;
            } else if (object instanceof IDNumber) {
                IDNumber idNum = (IDNumber) object;
                if (idNum.num == this.num) {
                    isEqual = true;
                }
            }
            return isEqual;
        }
    }

【解説】
  上記サンプルのIDNumberクラスはまだequals()メソッドしかオーバーライドされていません。この状態で、IDNumberクラスをHashMapで使用してみます。

1
2
    Map<IDNumber,String> map = new HashMap<IDNumber,String>();
    map.put(new IDNumber(123),"Hanako");

【解説】

一見、このHashMapに対してmap.get(new IDNumber(123) )を実行すれば、"Hanako"が取得できるように見えます。しかし、返ってくる値をnullです。

デフォルトのhashCode()メソッドのままではidの数値が同値でも、新たにインスタンスが作られれば、そのインスタンスに対して新たなハッシュコードを与えます。上記サンプルでも、>HashMapの中身をget()する際に新しいインスタンスを生成しているので、idの値は同じでも別のハッシュコードが与えられ、異なるIDNumberインスタンスとして認識されたのです。
  これを回避するためには、hashCode()メソッドが思い通りの振る舞いをするようにオーバーライドする必要があります。
  なお、hashCode()メソッドを独自にオーバーライドする場合、慎重に行ってください。同じインスタンスが同じhashCodeを返さなくてはいけません。
  下記は、今回のサンプルにおけるhashCode()をEclipseで自動生成したものです。

1
2
3
4
5
6
7
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + num;
        return result;
    }

ここでhashCode()メソッドの実装方法を紹介しましたが、他にも方法はたくさんあります。このように、hashCode()をオーバーライドして正しい結果が返るよう>に実装すれば、ハッシュに基づくコレクションクラスを使用した際でも正しい結果を得ることが出来ます。

3.1.4 可能な限りtoString()メソッドを実装する

⭐ ⭐ ⭐

【説明・動機】
  デバッグを容易にするために、toString()メソッドをなるべく実装しましょう。デフォルトのObject#toString()メソッドはhashCode()で得た値を返すだけです。ハッシュコードよりもわかりやすい値を出力するためにはメソッドをオーバーライドする必要があります。
  また、JUnitでオブジェクト同士を比較した際にassertに失敗すると、スローされる例外の中に比較したオブジェクトからtoString()で取得した値が含まれるため、失敗した内容がわかりやすくなります。テストの観点からも、toString()を実装することが望ましいでしょう。
  EclipseなどのIDEではtoString()を自動生成する機能があるので、積極的に活用しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    違反例
    public class BadSample {
        private int sampleValue;

        public BadSample(int value) {
            this.sampleValue = value;
        }

        public static void main(String[] args) {
            System.out.println(new BadSample(100));
        }
    }

【解説】
上記を実行した場合、例えば以下のような結果が得られます。
BadSample@ca0b6

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    修正例
    public class GoodSample {
        private int sampleValue;
        private String hoge;

        public GoodSample(int value, String hoge) {
            this.sampleValue = value;
            this.hoge = hoge;
        }

        // Eclipseで自動生成したtoString
        @Override
        public String toString() {
            return "GoodSample [hoge=" + hoge + ",sampleValue=" 
                                     + sampleValue + "]";
        }

        public static void main(String[] args) {
            // 修正済み。インスタンスの中身がはっきりしやすく、
            // デバッグ時にも良い。
            System.out.println(new GoodSample(100, "hello"));
        }
    }

【解説】
  toString()をオーバーライドした上記を実行した場合、結果は以下のようになります。オブジェクトの内部状態を正しく表示しており、標準のものに比べてとてもわかりやすくなります。
     GoodSample [hoge=hello,sampleValue=100]

3.1.5 Cloneable#clone()を使わず、自前のコピーメソッドを利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  オブジェクトのコピーを作る場合にCloneable#cloneメソッドを使おうとしていませんか?
  Cloneable#cloneメソッドを使った単純なコピーでは、フィールドに持つオブジェクトについては参照がコピーされるだけで中身はコピーされないため(シャローコピー)、参照先が共有され、思わぬところで不具合につながります。オブジェクトの状態をコピー(ディープコピー)したいのであれば、Cloneable#cloneを使わず、自前のコピーメソッドを準備し、適切に状態をコピーしましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
    違反例
    public class BadSample implements Cloneable {
        private String name;

        private List<String> addresses;

        public BadSample(String name, List<String> addresses) {
            this.name = name;
            this.addresses = addresses;
        }

        public String getName() {
            return name;
        }

        public List<String> getAddresses() {
            return addresses;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

        public static void main(String[] args) 
                                  throws CloneNotSupportedException {
            BadSample sample = new BadSample("Yone", Arrays.asList("Tokyo",
                    "Minato-ku", "Hoge"));
            BadSample clone = (BadSample) sample.clone();// 違反

            System.out.println("clone前の住所:" + clone.getAddresses());

            // Listへのオブジェクト参照はオリジナルとクローンで共有しているため、
            // オリジナルを変更すると、クローンも変更されてしまう
            sample.getAddresses().set(0, "Toyama");
            System.out.println("オリジナルに修正を入れると、
                                 クローンの中身も変わってしまっている");
            System.out.println("clone後の住所(オリジナル):"
                                     + sample.getAddresses());
            System.out.println("clone後の住所(コピー):" + clone.getAddresses());
        }
    }

    修正例
    public class GoodSample {
        private String name;

        private List<String> addresses;

        public GoodSample(String name, List<String> addresses) {
            this.name = name;
            this.addresses = addresses;
        }

        public String getName() {
            return name;
        }

        public List<String> getAddresses() {
            return addresses;
        }

        public static GoodSample copy(GoodSample original) {
            return new GoodSample(original.name, new ArrayList<String>(original.addresses));
        }

        public static void main(String[] args)
                     throws CloneNotSupportedException {
            GoodSample sample = new GoodSample("Yone", Arrays.asList("Tokyo",
                    "Minato-ku", "Hoge"));
            GoodSample copy = GoodSample.copy(sample);// 修正済み

            System.out.println("clone前の住所:" + copy.getAddresses());

            sample.getAddresses().set(0, "Toyama");
            System.out.println("オリジナルに修正を入れても、
                                         コピーの中身は変わっていない。");
            System.out.println("clone後の住所(オリジナル):" 
                                    + sample.getAddresses());
            System.out.println("clone後の住所(コピー):" + copy.getAddresses());
        }
    }

【解説】

違反例の実行結果は以下のとおりです。
  clone前の住所:[Tokyo, Minato-ku, Hoge]
  オリジナルに修正を入れると、クローンの中身も変わってしまっている
  clone後の住所(オリジナル):[Toyama, Minato-ku, Hoge]
  clone後の住所(コピー):[Toyama, Minato-ku, Hoge]
修正例の実行結果は以下のとおりです。
  clone前の住所:[Tokyo, Minato-ku, Hoge]
  オリジナルに修正を入れても、コピーの中身は変わっていない。
  clone後の住所(オリジナル):[Toyama, Minato-ku, Hoge]
  clone後の住所(コピー):[Tokyo, Minato-ku, Hoge]|

3.2 インスタンス

3.2.1 オブジェクト同士の比較方法の違いを意識する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  オブジェクト同士を比較する方法には、以下の2つがあります。
 ・「」を使用する
 ・equalsメソッドを使用する
  「
」を使った場合、「比較するインスタンス同士が同一のインスタンスかどうか」を比較していることになります。
  一方equalsメソッドで比較する場合、equalsメソッド内の実装を使用して、オブジェクト同士を比較します。equals()メソッドのデフォルト実装はただの「」比較ですが、たいていはそれぞれのオブジェクトを等値比較するのにふさわしい実装となっています。例えば、Stringクラスのequals()メソッドは「Stringに格納されている文字列が等しいかどうか」を比較するように実装されています。
  実装する際、「同一のインスタンスかどうかを調べたいのか」それとも「同じ値を持ったインスタンスかどうかを調べたいのか」を明確にして、「
」とequalsメソッドを適切に使い分けましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    サンプル
    public class SampleBean {
        private int code;
        public SampleBean(int code) {
            this.code = code;
        }
        public int getCode() {
            return code;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof SampleBean) {
                SampleBean bean = (SampleBean) obj;
                // codeが同じならイコールとみなす
                if (this.code == bean.getCode()) {
                    return true;
                }
            }
            return false;
        }
    }

    public class Sample {
        public static void main(String[] args) {
            SampleBean bean1 = new SampleBean(100);
            SampleBean bean2 = new SampleBean(100);

            // '=='ではインスタンス比較のためfalse
            System.out.println(bean1 == bean2);
            // equalsでは内部のcode値で比較のため true
            System.out.println(bean1.equals(bean2));
        }
    }

3.2.2 Classオブジェクトを比較する場合、文字列で比較しない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Classオブジェクトを比較する場合、クラス名の文字列を使用して比較すると、クラス名の文字列を打ち間違えたり、クラス名を変更したときなどに不具合になりやすく、適切ではありません。また比較する時にサブクラスも許容する場合、実装が困難です。
クラスで比較する場合、以下のようにしてください。
  ・完全一致である場合 ⇒ equals()または==を使う
  ・サブクラスも許容する場合 ⇒ isAssignableFrom()を使う

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
違反例
    public class BadSample {
        /**
         * 引数で与えられたクラスがArrayListであるかどうか
         */
        public boolean isArrayList(Class<?> list) {
            String listName = list.getName();
            // 違反 文字列を間違える可能性がある
            boolean result = listName.equals("java.util.ArrayList");
            return result;
        }

        /**
         * 引数で与えられたクラスがListのサブクラスであるかどうか
         */
        public boolean isList(Class<?> list) {
            String listName = list.getName();
            // 違反 ArrayList以外はfalseになる
            boolean result = listName.equals("java.util.ArrayList");
            return result;
        }
    }

    修正例
    public class GoodSample {
        /**
         * 引数で与えられたクラスがArrayListであるかどうか
         */
        public boolean isArrayList(Class<?> list) {
            String listName = list.getName();
            // 修正済み クラスと比較
            boolean result = (list == ArrayList.class);
            return result;
        }
        /**
         * 引数で与えられたクラスがListのサブクラスであるかどうか
         */
        public boolean isList(Class<?> list) {
            // 修正済み
            boolean result = List.class.isAssignableFrom(listCandidate);
            return result;
        }
    }

3.2.3 キャスト処理はinstanceofで囲む

⭐ ⭐ ⭐ ⭐

【説明・動機】
  キャスト処理は継承関係にあるもの同士しか行えません。継承関係にないものもキャスト処理の対象となりうる場合は、必ずinstanceofを用いてチャックするようにしてください。または、ジェネリクスで対象の引数の型を絞るようにしましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    違反例
    public class BadSample {
        public void executeBadSampleMethod(Object o) {
            /**
             * 違反 instanceofまたはClass#isInstanceメソッドなどでチェックしていない
             */
            Employee employee = (Employee) o;
            System.out.println(employee.getName());
        }
        ...
    }

    修正例
    public class GoodSample {
        public void executeGoodSampleMethod(Object o) {
            // 修正済み。 インスタンスの型を事前にチェックする
            if (o instanceof Employee) {
                Employee employee = (Employee) o;
                System.out.println(employee.getName());
            } else {
                throw new IllegalArgumentException(
                       "引数の型はEmployeeかその子クラスである必要があります。");
            }
        }
    }

    /**
     * 「キャスト処理はinstanceofで囲む」の良い例.<br/>
     * <br/>
     * ジェネリクスによって、そもそも引数で入ってくる型パラメータを絞っている。
     */
    public class GoodSample2<T extends Employee> {
        public void executeGoodSampleMethod(T o) {
                Employee employee = (Employee) o;
                System.out.println(employee.getName());
        }
        public static void main(String[] args){
            GoodSample2<Engineer> goodSample = new GoodSample2<Engineer>();
            Engineer e= new Engineer("Shinpei Ohtani",198111);
            goodSample.executeGoodSampleMethod(e);
            // コンバイルエラーになる。
            // Object o = "ジェネリクスによる対処";
            // goodSample.executeGoodSampleMethod(o);
        }
    }

3.3 制御構造

3.3.1 制御文(if,else,while,for,do while)の{}は省略しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  制御文の{}を省略していませんか?たとえ処理が1行で完結していても{}省略しないでください。文内の処理がどこで完結しているかがわかりにくくなったり、新たに処理を加えた際に{}の付け忘れでバグが発生する可能性があるためです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    違反例
    public class BadSample {
        public void badSampleMethod(boolean flag){
            if(flag)//違反
                System.out.println("ifの中");
                System.out.println("ifの外");
            }
    }

    修正例
    public class GoodSample {
        public void goodSampleMethod(boolean flag){
            if(flag){//修正済み
                System.out.println("ifの中");
                System.out.println("ifの中");
            }
        }
    }

3.3.2 空の{}プロックを利用しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  if文やfor文などに空の{}プロックを利用するのは止めてください。
  空の{}プロックがあった場合、「コードが不要だから空」なのか「開発途中でコードをまだ書いていないから空」なのかがわかりにくくなるためです。以下のようにしてわかりやすくしておきましょう。

  • 開発途中で空にしておく場合=>Eclipseであればタスクビューで確認出来るように「TODO」キーワードを付けたコメントを書いておく
  • コードが不要で空にする場合=>その旨コメントをつける
1
2
3
4
5
6
7
8
    違反例
    for (int i = 0; i < 10; i++) {
    }

    修正例
    for (int i = 0; i < 10; i++) {
        //TODO処理を実装する。
    }

3.3.3 forとwhileの使い分けを意識する

⭐ ⭐ ⭐

【説明・動機】
  forとwhileの遠いを意識して使っていますか?
  プログラマの好みにもよりますが、配列やコレクションなど複数要素の集合から、各要素を取り出して処理する場合は、基本的にfor文を使用してください。特にJava5以降では拡張for文が使えますので、まずはそちらを検討してください(詳細は「2-13-1出来るだけ拡張for文を使う」を参照)。
  次に通常のfor文を使います。通常for文の括弧の中は、以下の3つの条件を記述します。

  • カウンタの宣言
  • ループ条件
  • 次のカウンタを求める式

そして、これらの条件を省略する場合、または必要ない場合に、while文の適用を検討してください。状況に応じて、拡張for文、for文、while文の中から適切な繰り返し構文を使うことで、コードの可読性を高く保てます。

【解説】

要素の集まりのそれぞれの要素を処理する場合は、原則としてwhile文を使わないでください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    違反例
    public class BadSample{
        void method(String[] lines){
            //違反
            //配列のそれぞれの要素を処理する場合はforを使うべき
            int i=0;
            while (i<lines.length) {
                //処理
                i++;
            }
        }
    }

【解説】

要素の集まりのそれぞれについて処理を行う場合、原則としてfor文を使うようにしましょう。

1
2
3
4
5
6
7
8
    修正例
    public class GoodSample{
        void method(String[] lines){
            for (String line : lines) {
                //処理
            }
        }
    }

【解説】

拡張for文で書けない場合は通常のfor文を用います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    サンプル
    public class ForSample{
        void method(String[] lines){
            StringBuilder sb =new StringBuilder();
            for (int i = 0; i < lines.length; i++) {
                if(i>0){
                    sb.append(", ");
                }
                sb.append(lines[i]);
            }
        }
    }

【解説】

そうでない場合はwhileループを使います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    サンプル
    public class WhileSample{
        void method(BufferedReader reader) throws
        IOException{
            String line;
            while ((line=reader.readLine())!=null) {
                System.out.println(line);
            }
        }
    }

3.3.4 for文を利用した繰り返し処理中でループ変数の値を変更しない

⭐ ⭐ ⭐

【説明・動機】
  for文のカウンタをループ内のステートメントで変更していませんか?
  for文のカウンタは条件式以外で操作されるべきではありません。制御構造がわかりにくく間違いやすくなるうえ、間違った場合に誤りを見つけ出すことが難しくなるためです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample{
        int badSampleMethod(){
            int result=0;
            for (int i = 0; i < 100; i++) {
                i+=1;//違反
                result+=i;
            }
            return result;
        }
    }

    修正例
    public class GoodSample{
        int goodSampleMethod(){
            int result=0;
            //修正済み
            for (int i = 0; i < 50; i++) {
                result+=(2*i-1);
            }
            return result;
        }
    }

3.3.5 for文のカウンタは0から始める

⭐ ⭐ ⭐ ⭐

【説明・動機】
  for文内で使用するカウンタの初期値は、特に理由がなければ0としてください。例えば、for文内で配列の要素にカウンタを用いてアクセスする場合などにiを0以外の値から始めてしまうと、可読性が落ちます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    違反例
    public class BadSample{
        public static void main(String[] args){
            int[] testArray=new int[10];
            for (int i = 1; i < 10; i++) {//違反
                testArray[i-1]=i;
            }
        }
    }

    修正例
    public class GoodSample{
        public static void main(String[] args){
            int[] testArray=new int[10];
            for (int i = 0; i < 10; i++) {//修正済み
                testArray[i]=i+1;
            }
        }
    }

3.3.6 breakやcontinueを不用意に使わはい

⭐ ⭐ ⭐

【説明・動機】
  ループ内での処理の制御に不用意にbreakやcontinueを使用すると、制御構造が複雑化し、可読性が低下してしまいます。以下のようにするといいでしょう。

  • ループの主な継続条件はwhileやfor文に記述する
  • breakやcontinueでは例外のな処理フローの制御だけを行う
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    違反例
    public class BadSample{
        public static void main(String[] args){
            BadSample sample=new BadSample();
            sample.useContinue();
        }
        private void useContinue(){
            for (int i = 0; i < 10; i++) {
                if((i+1)%2==0){
                    System.out.println(
                            (i+1)+"は偶数です。");
                    continue;//違反
                }
                System.out.println(
                        (i+1)+"は奇数です。");
            }
        }
    }

    修正例
    public class GoodSample{
        public static void main(String[] args){
            GoodSample sample=new GoodSample();
            sample.unUseContinue();
        }
        private void unUseContinue(){
            for (int i = 0; i < 10; i++) {
                if((i+1)%2==0){//修正済み
                    System.out.println(
                            (i+1)+"は偶数です。");
                }else{
                    System.out.println(
                            (i+1)+"は奇数です。");
                }
            }
        }
    }

3.3.7 単純な配列をコビーするときはclone()など既存のメソッドを利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  単純な配列をコピーするのに、わざわざループ文などで処理を書いていませんか?場合に応じてJavaの便利なメソッドを利用しましょう。

  • 新たにコピーを作成する場合=>コピー元の配列オブジェクトのclone()メソッドを使う
  • 大きさの違う配列にコピーしたい場合=>Arrays.copyOf()メソッド(Java6以上)やSystem.arraycopy()メソッドを使う
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    違反例
    public class BadSample{
        String[] copy(String[] orgArray){
            if(orgArray==null){
                return null;
            }
            int length=orgArray.length;
            String[] copyArray=new String[length];
            for (int i = 0; i < length; i++) {
                copyArray[i]=orgArray[i];//違反
            }
            return copyArray;
        }
    }

    修正例
    public class GoodSample{
        String[] copy(String[] orgArray){
            if(orgArray==null){
                return null;
            }
            return orgArray.clone();//修正済み
        }
    }

    public class GoodSample2{
        String[] copy(String[] orgArray){
            if(orgArray==null){
                return null;
            }
            return Arrays.copyOf(
                    orgArray, orgArray.length)//修正済み
        }
    }

3.3.8 繰り返し処理中のオブジェクトの生成は最小限にする

⭐ ⭐ ⭐

【説明・動機】
  オブジェクトの生成には時間がかかるため、繰り返し回数の多いループの内部でオブジェクトを生成するとパフォーマンスが大きく低下することがあります。パフォーマンス上の懸念があらかじめわかっている場合には、繰り返し処理中のオブジェクトの生成は最小限にするようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    違反例
    public class BadSample{
        public void badSampleMethod(String[] array){
            for (int i = 0; i < array.length; i++) {
                StringBuilder sb=new StringBuilder();
                //違反
                sb.append("log: ");
                sb.append(array[i]);
                System.out.println(sb.toString());
            }
        }
    }

    修正例
    public class GoodSample{
        public void goodSampleMethod(String[] array){
            StringBuilder sb=new StringBuilder();
            //修正済み
            for (int i = 0; i < array.length; i++) {
                sb.append("log: ");
                sb.append(array[i]);
                System.out.println(sb.toString());
                sb.setLength(0);//初期状態に戻す
            }
        }
    }

3.3.9 if文とelse文の繰り返しやswitch文の利用はなるべく避け、オブジェクト指向の手法を利用する

⭐ ⭐ ⭐

【説明・動機】
  メソッドの中にたくさんの分岐が含まれていませんか?Switch文やif/else文が何度も繰り返されると可読性が落ち、メンテナンスも大変になります。これらは設計の問題です。ポリモーフィズムや統一したインタフェースの設計など、分岐をなくすための手法を利用出来ないが検討してください。

【解説】

下記はボリモーフィズムを使わないサンプルです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    違反例
    public class BadSample{
        public static void main(String[] args){
            Object[] animals=new Object[2];
            animals[0]=new BadDog();
            animals[1]=new BadCat();
            int size=animals.length;
            for (int i = 0; i < size; i++) {
                //if文で分岐をしなくてはならない
                //チェックする型が増えるとif文も増加
                if(animals[i] instanceof BadDog){
                    ((BadDog)animals[i]).bark();
                }else if(animals[i] instanceof BadCat){
                    ((BadCat)animals[i]).meow();
                }
            }
        }
    }

    public class BadDog{
        //BadDogクラス独自の鳴くメソッド
        public void bark(){
            System.out.println("わんわん");
        }
    }

    public class BadCat{
        //BadCatクラス独自の鳴くメソッド
        public void meow(){
            System.out.println("にゃー");
        }
    }

【解説】

上記のサンプルをボリモーフィズムを使って修正したものです。コードがシンプルになりました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    修正例
    public class GoodSample{
        public static void main(String[] args){
            Animal[] animals=new Animal[2];
            animals[0]=new Dog();
            animals[1]=new Cat();
            int size=animals.length;
            for (int i = 0; i < size; i++) {
                //インスタンスを判別する分岐が必要ない
                animals[i].cry();
            }
        }
    }

    public interface Animal{
        //共通の鳴くメソッド
        void cry();
    }

    public class Dog implements Animal{
        //Animalのメソッドを実装
        public void cry(){
            System.out.println("わんわん");
        }
    }

    public class Cat implements Animal{
        //Animalのメソッドを実装
        public void meow(){
            System.out.println("にゃー");
        }
    }

3.3.10 繰り返し処理の内部でtryプロックを利用しない

⭐ ⭐ ⭐

【説明・動機】
  ループの中には出来るだけtry/catchプロックを置かないでください。パフォーマンスの低下につながり、ループ内の処理も見にくくなります。特に理由がない場合はループの外でtry/catchを行ってください。
  例外として、途中でExceptionが発生しても、適宜例外処理をしてループ処理を最後まで実行したい場合は、この限りではありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    違反例
    public class BadSample {
        public void method(String[] str){
            int size=str.length;
            //違反ルールの中にtry/catchプロック
            for (int i = 0; i < size; i++) {
                try {
                    int num=Integer.parseInt(str[i]);
                    someOtherMethod(num);
                } catch (NumberFormatException e) {
                    //数値でない文字列の場合発生
                    throw new IllegalArgumentExctption("不正な値が見つかりました",e);
                }
            }
        }
        private void someOtherMethod(int i){
            //処理
        }
    }

    修正例
    public class GoodSample {
        public void method(String[] str){
            int size=str.length;
            //修正済み.ループの外にtry/catchプロック
            try {
                for (int i = 0; i < size; i++) {
                    int num=Integer.parseInt(str[i]);
                    someOtherMethod(num);
                }
            } catch (NumberFormatException e) {
                //数値でない文字列の場合発生
                throw new IllegalArgumentExctption("不正な値が見つかりました",e);
            }
        }
        private void someOtherMethod(int i){
            //処理
        }
    }

3.3.11 if/whileの条件式では=を使用しない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  ifあるいはwhile文の条件内に代入演算子(=)を使用するケースは、メソッドの返リ値を代入する場合を除いて、ほとんど考えられません。多くの場合、以下のどちらかです。

  • 「==」と間違えている
  • 条件文の外で行うべき代入を条件文内で行なっている

なお、「=」で代入する変数がboolean型であればコンパイルは通りますが、それ以外の型ではコンパイル時にエラーとして検出出来ます。下記の違反例では、terminateの値にかかわらずterminateにtrueが代入され、条件の結果が常にtrueになってしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    違反例
    public class BadSample {
        public String execute(boolean terminate) {
            String value = "none";
            if (terminate = true) {// 違反。バグの可能性も
                value = "terminate";
            }
            return value;
        }

        public static void main(String[] args) {
            String s = new BadSample().execute(true);
            System.out.println(s);

            String s2 = new BadSample().execute(false);
            System.out.println(s2);
        }
    }
    修正例
    public class GoodSample {
        public String execute(boolean terminate) {
            String value = "none";
            if (terminate == true) {// 修正済み
                value = "terminate";
            }
            return value;
        }

        public static void main(String[] args) {
            String s = new GoodSample().execute(true);
            System.out.println(s);

            String s2 = new GoodSample().execute(false);
            System.out.println(s2);
        }
    }

3.4 コレクション

3.4.1 Java2以降のコレクションクラスを利用する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  Vectorクラス、Hashtableクラス、Enumerationクラスを使っていませんか?
  特にこれらを利用する理由がなければ、インタフェースを統一する目的で、これらの代わりに(ListArrayListクラス)、Map(HashMapクラス)、Iteratorを使用するようにしてください。Listなどのインタフェースを利用することでJava2で整理されたわかりやすいメソッドを利用出来ますし、インタフェースの特性から呼び出し元を変更せずに実装クラスを変更することが出来るためです。
  また、Java5から導入されたジェネリクスを併用することで、より型安全で保守しやすいコードを書くことが出来ます。(詳細は「[[2.13 Java5以後に追加された機能]] 」を参照)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    違反例
    import java.util.Enumeration;
    import java.util.Vector;
    public class BadSample {
        public static void main(String[] args) {
            Vector<String> sampleVector = new Vector<String>();// 違反
            sampleVector.addElement("1");
            sampleVector.addElement("2");
            sampleVector.addElement("3");

            Enumeration<String> sampleEnumeration = sampleVector.elements();
            while (sampleEnumeration.hasMoreElements()) {
                System.out.println(sampleEnumeration.nextElement());
            }
            sampleVector.removeElement("1");
            int length = sampleVector.size();
            for (int i = 0; i < length; i++) {
                System.out.println(sampleVector.elementAt(i));
            }
        }
    }

    修正例
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;

    public class GoodSample {
        public static void main(String[] args) {
            List<String> sampleList = new ArrayList<String>();// 修正済み
            sampleList.add("1");
            sampleList.add("2");
            sampleList.add("3");

            Iterator<String> sampleIterator = sampleList.iterator();
            while (sampleIterator.hasNext()) {
                System.out.println(sampleIterator.next());
            }
            sampleList.remove("1");
            int length = sampleList.size();
            for (int i = 0; i < length; i++) {
                System.out.println(sampleList.get(i));
            }
        }
    }

3.5 終了処理

3.5.1 リソースを扱うAPIを利用するときは、finallyブロックで後処理をする

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  リソースを開いたら、そのリソースをクローズするメソッドが確実に実行されるようになっていますか?リソースのリークを回避するためにリソースのクローズは必須です。
  例えばストリームの場合、途中で例外が発生した場合でも必ずクローズされるように、close()メソッドをfinallyブロッグに記述してください。ストリーム以外でも、Java5から追加されたjava.io.Closeableインタフェースを実装しているクラスのオブジェクトのclose()メソッドなど、リソースを閉じる必要があるものは忘れずに呼び出すようにしてください。

【解説】

違反例では例外が発生した場合にストリームがクローズされません。そのため、リソースのリークが発生して、障害を引き起こす可能性があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    違反例
    public class BadSample {
        public void badSampleMethod(File file) {
            if (file == null) {
                throw new IllegalArgumentException("ファイルはnullであってはいけません。");
            }
            Reader reader = null;
            try {
                reader = new FileReader(file);
                BufferedReader bufReader = new BufferedReader(reader);
                // ……Other to do
                bufReader.close();
                //違反。リソースの開放漏れの危険性。
            } catch (Exception ioe) {
                throw new RuntimeException(ioe);
            }
        }

        // ……Other to do
    }

【解説】

finallyブロッグにclose()メソッドを記述すれば、例外発生した場合にも確実にクローズされます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    修正例
    public class GoodSample {
        public void goodSampleMethod(File file) {
            if (file == null) {
                throw new IllegalArgumentException("ファイルはnullであってはいけません。");
            }
            Reader reader = null;
            try {
                reader = new FileReader(file);
                BufferedReader bufReader = new BufferedReader(reader);
                // ……Other to do
                bufReader.close();
            } catch (Exception ioe1) {
                throw new RuntimeException(ioe1);
            } finally {
                if (reader != null) {

                    try {
                        reader.close();
                        // 修正済み。確実にクローズ処理が実行される。
                    } catch (Exception ioe2) {
                        System.out.println("クローズ時に例外発生:"+
                             ioe2.getMessage());
                    }
                }
            }
        }

        // ……Other to do
    }

3.6 例外処理

3.6.1 catch文で受け取った例外を処理しない場合は適切なエラー情報を付与する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  catch文を受け取った例外を処理しない場合、そのままthrowしてしまっていませんか? 呼び出し先で発生した例外を呼び出し元にそのままthrowしてしまうと、処理に失敗した際にエラーの原因を調査しづらくなってしまうことがあります。
  Catchした例外を処理せずに呼び出し元にthrowする場合は、どんなエラーが発生したのかわかるような適切なエラー情報を付与するようにしてください。

【解説】

例えば、発生したエラーがログに出力されるようになっているとします。違反例では、configure()の内部のparseDate()でエラーが発生した場合、エラーログにはまずParseExceptionが出力されますが、それだとエラーログをひと目見ただけでは何かのパーズに失敗したことしかわかりません。エラーログを詳細に見ていけばconfigure()の処理に失敗したことがわかりますが、調査に時間がかかってしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
違反例
    public class BadSample {
        public void configure(Properties prop) throws ParseException {// 違反
            Date date = parseDate(prop.getProperty("processStartDate"));
            // ……other to do
            System.out.println("開始日付をセットしました:" + date.toString());
        }

        private Date parseDate(String dateString) throws ParseException {
            return new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
        }

        public static void main(String[] args) {
            BadSample badSample = new BadSample();
            Properties prop = new Properties();
            prop.setProperty("processStartDate", "no_such_date");
            try {
                badSample.configure(prop);
            } catch (ParseException e) {
                e.printStackTrace();
                System.err.println(e);
            }
        }
    }

【解説】

修正例ではエラーログにまず独自で作成した例外クラスであるConfigurationExceptionが出力されるため、ひと目見ただけでコンフィギュレーションに失敗したことがわかります。
また、ConfigurationExceptionのエラーメッセージから、処理の開始日時のパースに失敗したことが直ちにわかります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    修正例
    public class GoodSample {
        public void configure(Properties prop) throws ConfigurationException {// 修正済み
            Date date = null;
            try {
                date = parseDate(prop.getProperty("processStartDate"));
            } catch (Exception ex) {
                // 例外に適切なエラー情報を付加して返すことで
                // 何がおきたかわかりやすくなる。
                throw new ConfigurationException("開始日付のパースで失敗しました:", ex);
            }
            // ……other to do
            System.out.println("開始日付をセットしました:" + date.toString());
        }

        private Date parseDate(String dateString) throws ParseException {
            return new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
        }

        public static void main(String[] args) {
            GoodSample goodSample = new GoodSample();
            Properties prop = new Properties();
            prop.setProperty("processStartDate", "no_such_date");
            try {
                goodSample.configure(prop);
            } catch (ConfigurationException e) {
            // 呼び出し元も例外によって失敗がわかりやすい
                e.printStackTrace();
                System.err.println(e);
            }
        }
    }

3.6.2 例外を再スローする時は、元の例外をスローする例外に入れる

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  例外をキャッチして再スローする場合、キャッチした例外を必ずスローする例外に入れるようにしてください。キャッチした例外を入れずに再スローした場合、再スロー前のスタックトレースが消えてしまい、エラー発生時に原因までたどり着くことが出来るなくなります。
  また、Javeクラスライブラリの一部の例外では、元の例外をコンストラクタで入れることが出来ないものあります。その場合は、initCauseメソッドを使用して元の例外をセットしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    違反例
    public class BadSample {
        public void sampleMethod1(String s) {
            try {
                printMethod(s);
            } catch (Exception e) {
                // 違反 キャッチした例外を入れていない
                throw new RuntimeException("Error");
            }
        }

        private void printMethod(String s) throws Exception {
            // ここまでNullPointerExceptionが発生
            String message = s.toLowerCase() + "message";
            System.out.println(message);
        }

        public static void main(String[] args) {
            BadSample sample = new BadSample();
            try {
                sample.sampleMethod1(null);
            } catch (Exception e) {
                // 本体の例外が入っていないため。
                // printMethodで例外発生したことがわからない
                e.printStackTrace();
            }
        }
    }

    修正例
    public class GoodSample {
        public void sampleMethod1(String s) {
            try {
                printMethod(s);
            } catch (Exception e) {
                // 修正サンプル キャッチした例外を入れる
                throw new RuntimeException("Error", e);
            }
        }

        private void printMethod(String s) throws Exception {
            // ここまでNullPointerExceptionが発生
            String message = s.toLowerCase() + "message";
            System.out.println(message);
        }

        public static void main(String[] args) {
            GoodSample sample = new GoodSample();
            try {
                sample.sampleMethod1(null);
            } catch (Exception e) {
                // printMethodで例外発生したことがわかんる
                e.printStackTrace();
            }
        }
    }

【解説】

違反例の実行結果では、例外が発生したものの、それ以前に何が起きているのかがスタックトレースからは読み取れません。
これはRuntimeExceptionをスローする時に発生した例外クラスをセットしていないためです。
違反例の実行結果は以下になります。
 Java.lang.RuntimeException:Error
 at jp.co.gihyo.javarulebook.chater3_6_2.
 BadSample.sampleMethod1(BadSmaple.java:15)
 at jp.co.giho.javarulebook.chapter3_6_2.
 BadSample.sampleMethod1(BadSmaple.java:28)
一方修正例の実行結果を見ると、RuntimeExceptionに適切に発生した例外をセットしているため、例外のスタックトレースが追いやすくなります。
この例ではNullPointerExceptionが原因となった例外クラスです。
修正例の実行結果は以下になります。
 Java.lang.RuntimeException:Error
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:15)
 Java.lang.RuntimeException:Error
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:28)
 Caused by:java.lang.NullPointerExcetion
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:21)
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:12)
 ……1 more
このように、スタックレース上から問題点が発生した原因まで追いやすくしておくことはとても重要です。

3.6.3 Exceptionクラスのオブジェクトを生成してスローしない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Exceptionクラスのオブジェクトを生成して、例外をスローしていませんか?
Exceptionクラスはすべての例外のスーパークラスです。Exceptionクラスのインスタンスが例外としてスローされた場合、どのような例外が発生したかのかが、スローされた例外の型から推定できなくなります。
  発生した例外に応じた、適切な例外クラスのオブジェクトをスローしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    違反例
    public class BadSample {
        public void badSmapleMethod() throws Exception {// 違反
            throw new Exception();// 違反
        }
    }

    修正例
    public class GoodSample {
        public void goodSmapleMethod() throws NoSuchMethodException {// 修正済み
            throw new NoSuchMethodException();// 修正済み
        }
    }

3.6.4 catchブロッグでは必ず処理をする

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  catchブロッグは空にせず、必ず例外に対する処理を行なってください。
  catchブロッグで何もしないと、何が起きているのか把握出来ず、深刻なバグにもつながります。処理が出来ない場合でもログへ出力するなど、例外が発生したことを後から確認出来るようにしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    違反例
    public class BadSample {
        public void badSmapleMethod(File file) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(file));
                reader.read();
            } catch (FileNotFoundException fnfe) {
                // 違反。何が起きたか利用者側はわからない
            } catch (IOException ioe1) {
                // 違反。何が起きたか利用者側はわからない
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException ioe2) {
                    // 違反。何が起きたか利用者側はわからない
                }
            }
        }

        public static void main(String[] args) {
            BadSample badSample = new BadSample();
            File file = new File("no_such_file");
            badSample.badSmapleMethod(file);
            System.out.println("例外が発生せず、何が起きているがわからない。");
        }
    }

    修正例
    public class GoodSample {
        private Logger logger = Logger.getLogger(getClass().getName());

        public void goodSampleMethod(File file) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(file));
                reader.read();
            } catch (FileNotFoundException fnfe) {
                // 修正済み。最低限ログだけは出力する。
                logger.log(Level.WARNING, "[SKIP]File not found", fnfe);
            } catch (IOException ioe1) {
                // 修正済み。最低限ログだけは出力する。
                logger.log(Level.WARNING, "[SKIP]I/O error", ioe1);
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException ioe2) {
                    // 修正済み。最低限ログだけは出力する。
                    logger.log(Level.WARNING, "Cannot close", ioe2);
                }
            }
        }

        public static void main(String[] args) {
            GoodSample goodSample = new GoodSample();
            File file = new File("no_such_file");
            goodSample.goodSampleMethod(file);
        }
    }

3.6.5 Error、Throwwableクラスを承継しない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  Errorクラスは、アプリケーションではキャッチされるべきではない重大な問題を表す例外クラスです。そのため、アプリケーションでErrorクラスのサブクラスを定義してはいけません。アプリケーション定義する例外はExceptionクラスを継承してください。
  また、Throwableのサブクラスも定義してはいけません。ThrowableクラスはExceptionとErrorのスーパークラスであり、アプリケーションの例外で承継すると、エラーと例外の意味が曖昧になるためです。
アプリケーションで定義する例外はExceptionクラスを承継してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    違反例
    public class BadSampleException
                    extends Error {// 違反
    }


    public class AnotherBadSampleException
                    extends Throwable {// 違反
    }

    修正例
    public class GoodSampleException
                    extends Exception {// 修正済み
    }

3.6.6 finallyブロッグで戻り値に影響がある記述はしない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  finallyブロックに記述された内容は、その前で例外がキャッチされていてもいなくても必ず実行されます。Finallyブロックには、ストリームのクローズ処理など、必ず行うべき処理を記述してください。
  逆にfinallyブロックの中でreturn文を記述したり、メソッドの戻り値に影響がある記述はしてはいけません。例えば以下の違反例では、finallyブロックでreturnしているために、戻り値が必ず2になってしまいます。
  この内容と「3.5.1 リソースを扱うAPIを利用する時は、finallyブロックで後処理をする」「3.6.7 finallyから例外をthrowしない」をまとめるとfinallyブロックのルールは以下のようになります。

  • ストリームのクローズ処理など必ず行うべき処理を記述する
  • メソッドの戻り値に影響を与えてしまう処理を記述しない
  • try文の中で発生した例外を上書きまる可能性があるため、finally内では例外を起こさない
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    違反例
    public class BadSample {
        public int sampleMethod(int value) {
            int a = value;
            try {
                // ……
                if (a == 1) {
                    return a;// ここで幾ら1という値を返しても。
                }
            } catch (SampleException e) {
                throw new RuntimeException(e);
            } finally {
                a = 2;
                return a;// 違反:このメソッドの返り値は必ず2になる
            }
        }
    }

    修正例
    public class GoodSample {
        public int sampleMethod(int value) {
            int a = value;
            try {
                // ……
                if (a == 1) {
                    return a;
                }
            } catch (SampleException e) {
                throw new RuntimeException(e);
            }
            a = 2;
            return a;// 修正済み
        }
    }

3.6.7 finallyから例外をthrowしない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  finally句の中からは例外をthrowしないような設計にすべきです。
  try句の中で実行したコードで発生した例外が、finally句で発生した例外に上書きされると、本来捕捉すべき例外がわからなくなります。
  これにより、どのような例外がなぜ起こったのかわからなくなる危険性があります。
  違反例では、本来FileNotFoundExceptionが発生しているにも関わらず、finally句でthrowした例外が本来の例外を上書きしてしまっています。
  このため、本来発生した例外をキャッチする方法が残されておらず、本来何が起こったのかわからなくなってすまいます。   finally句はcatch節で例外をthrowした後にも実行されることを理解したうえで実装する必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    違反例
    public class BadSample {
        public void badSampleMethod(File file) {
            Reader r = null;
            try {
                r = new FileReader(file);
                r.read();
            } catch (IOException ioe1) {
                // 本来はこの例外をthrowしたい
                throw new RuntimeException(ioe1);
            } finally {
                // finallyで例外を投げでしまうと、
                // 本来発生した例外は上書きされてしまう
                throw new RuntimeException(
                "finallyで例外を投げでしまうと、
                             本来発生した例外は上書きされてしまう。");
            }
        }
        public static void main(String[] x) {
            BadSample sample = new BadSample();
            // 意図的にFileNotFoundExceptionを発生させる
            sample.badSampleMethod(new File("/no_such_file"));
        }
    }

    修正例
    public class GoodSample {
        public void goodSampleMethod(File file) {
            Reader r = null;
            try {
                r = new FileReader(file);
                r.read();
            } catch (IOException ioe1) {
                throw new RuntimeException(ioe1);
            } finally {
                try {
                    if (r != null) {
                        r.close();
                    }
                } catch (Exception e) {
                    // 本来はロギングライブラリで適切にログを収集する
                    e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            }
        }

        public static void main(String[] x) {
            GoodSample sample = new GoodSample();
            // 意図的にFileNotFoundExceptionを発生させる
            sample.goodSampleMethod(new File("/no_such_file"));
        }
    }

3.7 マルチスレッド

3.7.1 RunnableまたはCallableインタフェースを実装する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  スレッドで並列処理を実現するクラスの作成方法には、以下の3つがあります。

  1. 処理クラスをThreadのサブクラスとして作成する。
  2. 処理クラスをRunnableインタフェースの実装として作成する。
  3. 処理クラスをCallableインタフェースの実装として作成する。(Java5から)

マルチスレッドで働かす処理を実装するのであれば、そのクラスの役割を明確にするために2.と3.の方法を使用してください。1.方法でも、runメソッドをオーバーライドすることでマルチスレッド処理を実装出来ますが、特にrunメソッド以外のThreadクラスのメソッドを拡張/利用する必要がなければ、Threadクラスを継承する必要はありません。逆に継承することで不必要なメソッドを使用してしまったり、オーバーライドしてしまう可能性があるため、推奨しません。
  RunnableインタフェースとCallableインタフェースのどちらを実装するかは、スレッド処理から戻り値を返す必要性があるかないかによっていかのように変わります。

  • 戻り値が必要ない場合⇒Runnableインタフェース
  • 戻り値が必要な場合⇒Callableインタフェース

なおJava5からはThreadクラスを直接使う代わりにjava.util.concurrent.Executorクラスを使うことで並列処理をより簡単に実現出来るため、そちらと併用して使ってください(Executorの詳細「2-13-14 マルチスレッドプログラミングをする場合はJava5標準のExecutorフレームワークを利用する」を参照)。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
    違反例
    //Threadのコード
    public class BadSampleController extends Thread {// 違反

        private Car car;

        public BadSampleController(Car car) {
            this.car = car;
        }

        @Override
        public void run() {
            CarState state = car.startEngine;
            if (state == CarState.START_ENGINE) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                throw new RuntimeException("error");
            }
        }
    }

    public class BadSample {
        public static void main(String[] args) {
            Car car = new Car("Red car");
            BadSampleController badSampleController = new BadSampleController(car);
            System.out.println("[" + Thread.currentThread().getName() + "] スレッド開始。");

            badSampleController.start();
            while (badSampleController.isAlive()) {
                System.out.println("¥tBadSampleController スレッドの停止を待っています。");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("[" + Thread.currentThread().getName() + "] スレッド終了。");
        }
    }

    修正例
    /**
     * 「スレッドプログラミングでは「Runnable」または
     * 「Callable」インタフェース実装が原則」の良い例。
     *
     * {@link Thread}を直接継承するでのはなく、
     * {@link Runnable}インタフェースを継承。
     *
     */
    public class GoodSampleController implements Runnable {// 修正済み

        private Car car;

        public GoodSampleController(Car car) {
            this.car = car;
        }

        @Override
        public void run() {
            CarState state = car.startEngine;
            if (state == CarState.START_ENGINE) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                throw new RuntimeException("error");
            }
        }
    }

    /**
     * 「スレッドプログラミングでは「Runnable」または
     * 「インタフェース実装が原則」の良い例。
     *
     * {@link Thread}を直接継承するでのはなく、 Java5から導入された
     * {@link Runnable}インタフェースを継承。
     *
     */
    public class GoodSampleController2 implements Callable<CarState> {// 修正済み
        private Car car;

        public GoodSampleController2(Car car) {
            this.car = car;
        }

        @Override
        public CarState call() throws Exception {
            CarState state = car.startEngine;
            if (state == CarState.START_ENGINE) {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                throw new RuntimeException("error");
            }
            return state;
        }
    }

    /**
     * 「スレッドプログラミングでは「Runnable」または
     * 「Callable」インタフェース実装が原則」の良い例。
     *
     * Java5から導入された並行処理フレームワークのAPIである 
     * {@link ExecutorService}などを使用して、よりバグが入り込みにくい。
     *
     */
    public class GoodSample {
        public static void main(String[] args) {
            //スレッドプールが出来るExecutorServiceを生成。
            ExecutorService executor = Executors.newFixedThreadPool(2);
            //Runnableを実装したインスタンスを実行
            Car car = new Car("Red car");
            executor.execute(new GoodSampleController(car));
            //Callableを実装したインスタンスを実行
            Car car2 = new Car("Blue car");
            Future<Carstate> returnState = executor.submit(new GoodSampleController2(car2));
            while (true) {
                try {
                    //Callableが返す戻り値が取得出来るまで待つ。
                    CarState carstate = returnState.get();
                    System.out.println("["+ Thread.currentThread().getName()+"] CallableからCarStateの取得に成功:"+ carstate);
                    break;
                } catch (InterruptedException e) {
                    System.out.println("スレッドがインタラプトされました。" + e);
                } catch (ExecutionException e) {
                    System.out.println("スレッド実行時にエラーが発生しました。" + e);
                }
            }
            //Executorの終了
            System.out.println("["+ Thread.currentThread().getName() +"] Executorを停止します。");
            executor.shutdown();
            try {
                boolean awaitTermination = executor.awaitTermination(2,TimeUnit.SECONDS);
                if (awaitTermination==false) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("["+ Thread.currentThread().getName() + "] 全スレッド停止しました。");
        }
    }

3.7.2 待機中のスレッドを再開するにはnotifyAll()メソッドを利用する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  待機中のスレッドを再開するときは、notify()メソッドではなくnotifyAll()メソッドを使うようにしてください。notify()メソッドは、同一のオブジェクトで待機中の複数スレッドの1つだけを再開しますが、再開するスレッドは予測出来ないためです。「再開したいスレッドが再開されない」という危険を避けるため、次のような場合を除いてnotifyAll()メソッドを利用してください。

  1. 待ち状態のスレッドが1つだけの場合
  2. どのスレッドが呼ばれてもかまわない場合
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample {
        // ...
        public void refuel(int fuel) {
            synchronized (car) {
                this.fuel = fuel;
                car.notify();// 違反
            }
            // ...
        }
    }

    修正例
    public class GoodSample {
        // ...
        public void refuel(int fuel) {
            synchronized (car) {
                this.fuel = fuel;
                car.notifyAll();// 修正済み
            }
            // ...
        }
    }

3.7.3 スレッドを切り替えたい場合はThreadクラスのyield()メソッドは利用しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  Thread.yield()メソッドはあくまでもスレッドの切り替えを促すだけで、実際にスレッドが切り替えられることは保証されません。
  他のスレッドに実行権を渡したい場合はThread.yield()を使わないように実装してください。
  またyield()メソッドではスレッドが持っているロックが解放されません。ロックを持った状態でyield()メソッドを呼び出すと、予期せぬ働作をすることがあります。ロックを持った状態でスレッドの切り替えを意図する場合は、ロックしているオブジェクトのwait()メソッドを利用してください。
  wait()メソッドを利用するにあたっては3.7.5「wait()、notify()、notifyAll()メソッドは、synchronizedプロックの中で利用する」や3.7.6「wait()メソッドの後で前提条件を再確認する」もあわせて参照してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    違反例
    public class BadSample {
        private Object lock;
        public void longProcess() {
            synchronized (lock) {
                Thread.yield();// 違反
            }
        }
    }

    修正例
    public class GoodSample {
        private Object lock;
        public void longProcess() {
            synchronized (lock) {
                try {
                    lock.wait();// 修正済み
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.7.4 synchronizedブロックからsynchronizedブロックのあるメソッドを呼び出さない

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  synchronizedプロックの中からsynchronizedプロックのあるメソッド(synchronized宣言されたメソッドを含む)を呼び出していませんか?それらのデッドロック発生の可能性を考慮しましたか?デッドロックは以下の条件が満たされたときに発生する可能性があります。

  1. 複数のロック対象がある
  2. ある対象のロックを取得したまま他の対象のロックを取得するようなコードがある
  3. それらの対象へのロック順序が決まっていない

「synchronizedブロックからsynchronizedブロックのあるメソッドを呼び出す」という処理では、あるオブジェクトのロックを取得した上で、さらに別のオブジェクトを取得することになるため、1.と2.の条件を満たしています。もし、これに加えて対象へのロックの順序が決まっていなければ、デッドロックが発生し得ることになります。
  そのため、ロック対象のリソースのロック順序が厳密に定められていない限り、synchronizedブロックからsynchronizedブロックのあるメソッドを呼び出さないでください。

【解説】

デッドロックとは、2つのスレッド同士が、相手方がロックを取得しているオブジェクトの開放を待ち合ってしまう状態のことです。例えば、2人の板前がいたとします。それぞれ仕事をこなすには包丁とまな板の両方が必要ですが、包丁とまな板は1つすつしかありません。板前Aはます包丁を取り、板前Bはまな板を取りましたが、お互い包丁とまな板の両方がなければ仕事が出来ません。従って、AはBがまな板を手放すのを、BはAが包丁を手放すのを、お互い頑固に待ち、いつまでたっても仕事が出来なくなってしまう・・・というような状況です。
以下は、スレッドA(板前A)とスレッドB(板前B)がX(包丁)とY(まな板)というリソースの開放を互いに待ち合っているイメージを図式化したものです。
3.7.4-1

この問題は、ロック対象へアクセスする順序を定めておけば解決出来ます。例えば、包丁とまな板のうち「必す包丁を先にとらなければならない」というルールを定めれば、板前Aが先に包丁を取った場合板前Bは板前Aが仕事を終えて包丁を手放すまで待たなければなりませんが、板前Aが仕事を終えた後に板前Bは包丁をとることが出来るので、デッドロックは発生しません。

3.7.5 wait()、notify()、notifyAll()メソッドは、synchronizedブロックの中で利用する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  wait()、notify()、notifyAll()メソッドは、オブジェクトのロックを獲得した状態で呼び出してください。
  ロックを獲得せずに呼び出すと、「java.lang.illegalMonitorStateException」という例外が発生します。

【解説】

違反例を実行すると、以下のような例外が発生します。notify()、notifyAll()を呼んだ場合にも同じ結果となります。
Exception in thread "main" java.lang.IllegalMnitorStateException:current thread not owner
修正例では例外は発生しません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    違反例
    public class BadSample {
        public static void main(String[] args) {
            Test test = new Test();
            try {
                test.wait(); // 違反
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            Test test = new Test();
            synchronized (test) {// 修正済み
                try {
                    test.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.7.6 wait()メソッドの後で前題条件を再確認する

⭐ ⭐ ⭐ ⭐ ⭐

【説明・動機】
  wait()が呼ばれるのは、多くの場合なんらかの条件が成立するのを待つ場合です。ただし、wait()メソードを抜けたからといって、必ずしも条件が成立している保証はありません。条件が成立しているか、wait()メソードを抜けた後で再度確認しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    違反例
    synchronized (car) {
        if (furl == null) {// 違反
            try {
                test.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        car.drive();// 処理(走る)
    }

【解説】
  carオブジェクト(車)はfuel(燃料)がなければ働くことが出来ません。従って、fuelフィールドがnullの場合、スレッドはwait()するようになっています。
  この処理は一見正常に働くように思われます。しかし、以下のようなことが起こった場合はどうでしょうか。

  1. 故意に、あるいは誤ってnotifyAll()が呼び出され、fuelがまだnullなのにも関わらず、スレッドが再開してしまった
  2. この処理とは関係のない場合でnotifyAll()が呼ばれ、スレッドが再開してしまった
  3. fuelに燃料が補充され、notifyAll()が呼ばれたが、他のスレッドが先にcarのロックを獲得してfuelの状態を変えてしまった
  4. VMのなんらかの挙動により、スレッドが再開してしまった、(この現象は「スプリアスウェイタアップ」と呼ばれています)

このようなことが起こった場合、「fuelがnullでないときにcar.drive()が正常に呼ばれる(車が走る)」という保証はありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    修正例
    synchronized (car) {
        while (furl == null) {// 修正済み
            try {
                test.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        car.drive();          // 処理(走る)
    }

【解説】

違反例との違いは、ifをwhileに変更しただけです。
if文では、一度文内の処理が実行されたあとはif文から抜け、次の操作に移ってしまいますが、while文は文内の処理が実行されためとで再度条件を評価します。再度評価した結果がtrueであれば、再び文内の処理を行いますし、falseであればwhile文を抜けて次の操作へと進みます。 3.7.6-1

従って、fuelがもしnullのままスレッドが再開してしまっても、再度条件が評価されます。fuelがnullならば再びスレッドはwait()するので、car.drive()が呼ばれるときにはfuelがnullでないことが保証されます。

3.7.7 処理の待ち合わせにはポーリングループを利用せずにCountDownLatchなどの既存の仕組みを利用する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  他のスレッドで実行中の処理を待ち合わせるための手法としてボーリングループがあります。ボーリングループとは、「ある条件が満たされたときに処理を続行する場合に、その条件が満たされたかどうか自身で問い合わせ続けるスープ」のことです。   ポーリングループにより頻繁に問い合わせすると、それだけプロセッササイクルを多く消費します。また、一定間隔を置いて問い合わせした場合でも。一度目の問い合わせの直後に条件が満たされた場合でも、次の問い合わせが行われるまでは処理に移れないので、プロセスの占有時間が長くなってしまいます。
  これとは逆に、条件が満たされたことを相手側から通知させれば、リソールを無駄に消費するのを防ぐことが出来ます。Java5で導入されたコンカレントAPIであるjava.util.concurrentのCountDownLatchには、いかの2つのメソッドが用意されています。

  • 処理が終わったことを通知するcountDownメソッド
  • countDownが呼ばれるまで持つawaitメソッド

例えば、条件がそろうのを持つ側をawaitメソッドで持ち状態にしておき、別スレッドで条件を整えた後にcountDownメソッドを呼ぶことで、待っている側の処理が再開されるようにします。このようにすることで、無駄な問い合わせをする必要がなく、大変スマートになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    違反例
    //違反 ボーリングループするRunnable
    public class BadRobot implements Runnable {

        private byte[] commands;

        private RobotController controller;

        public BadRobot(RobotController controller) {
            this.controller = controller;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            synchronized (controller) {
                while (commands == null) {
                    try {
                        System.out.println("waiting...");
                        TimeUnit.SECONDS.sleep(10);// 違反
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("go!");
                int size = commands.length;
                for (int i = 0; i < size; i++) {
                    this.processCommand(commands[i]);
                }
                commands = null;
            }
            long end = System.currentTimeMillis();
            System.out.println("所要時間:" + (end - start) + "msec");
        }
        public synchronized void storeCommands(byte[] commands){
            this.commands=commands;
        }
        private void processCommand(byte[] command){
            controller.execute(command);
        }
    }

    //実行コード
    public class BadSample {
        public static void main(String[] args) {
            BadRobot robot=new BadRobot(new RobotController);
            new Thread(robot).start();
            try {
                //wait a bit
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
            robot.storeCommands(new byte[]{1,2,3});
        }
    }

【解説】

違反例のwhile文は、以下の処理が行われると次の処理へ移る条件が満たされます。
 badRobot.storeCommands(commands)
しかし、この処理が行われるまでは10000ミリ秒(10秒)ごとにwhileの条件が判定され、その都度sleep()をすることになります。また、条件が満たされても、sleep()が終わるまで待たなければ次の処理へ進めません。 java.util.concurrent.CountDownLatchを使って実現すると、以下のようにシンプルにステッドの待ち合わせ処理が実現出来ます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    修正例
    //修正済み ラッチを使って実装。
    public class BadRobot implements Runnable {
        private byte[] commands;
        private RobotController controller;
        private CountDownLatch startLatch;
        private CountDownLatch endLatch;
        public GoodRobot(RobotController controller,
                               CountDownLatch startLatch,
                CountDownLatch endLatch) {
            this.controller = controller;
            this.startLatch = startLatch;
            this.endLatch = endLatch;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("waiting...");
            try {
                startLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }

            synchronized (controller) {
                System.out.println("go!");
                int size = commands.length;
                for (int i = 0; i < size; i++) {
                    this.processCommand(commands[i]);
                }
                commands = null;
            }
            long end = System.currentTimeMillis();
            System.out.println("所要時間:" + (end - start) + "msec");

            // 終了のパリアを外す。
            endLatch.countDown();
        }

        public synchronized void storeCommands(byte[] commands) {
            this.commands = commands;
            //ラッチをカウントダウンし、パリアを外す。
            startLatch.countDown();
        }

        private void processCommand(byte[] command) {
            this.controller.execute(command);
        }
    }

【解説】

呼び出し元は以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    サンプル
    public class GoodSample {

        public static void main(String[] args) {
            RobotController controller = new RobotController();

            // 開始条件が整うまで待たせるラッチ
            CountDownLatch startLatch = new CountDownLatch(1);

            // 終了条件が整うまで待たせるラッチ
            CountDownLatch endLatch = new CountDownLatch(1);
            GoodRobot goodRobot = new GoodRobot(controller, startLatch, endLatch);
            new Thread(goodRobot).start();

            // コマンドの設定時にGoodRobotrunで
            // startLatchで実現したパリアを外す。
            goodRobot.storeCommands(new byte[] { 7, 8, 9 });

            // 終了のバリア
            try {
                endLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("終了。");
        }
    }

3.7.8 同期化(synchronized)の適用は必要な部分だけにする

⭐ ⭐ ⭐ ⭐

【説明・動機】
  同期化を行うには、以下の2つの方法があります。

  1. メソッドをsynchronized宣言する
  2. synchronizedブロックを利用する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    ●1
    synchronized void method () {
        ...
    }
    ●2
    void method () {
      synchronized(<インスタンス>){
        ...
      }
    }

2、のsynchronizedブロックの<インスタンス>部分にはロックをとるインスタンスを記述します。では、1、のsynchronizedメソッドは何に対してロックをとっているのでしょうか。そのメソッドは以下の記述と同等の意味になります。

1
2
3
4
5
    void method () {
       synchronized(this){
            ...
       }
    }

ご覧のように、synchronizedメソッドはthisに対してロックをとっています(static synchronizedメソッドの場合、そのクラスのjava.lang.Classインスタンスがロックされます)。ロックされているオブジェクトは他からsynchronizedアクセスすることは出来ませんので、thisに対してロックした場合、ロックが取られている間(メソッドが実行されている間)は同じオブジェクトのsynchronizedメソッドを呼び出すことが出来ません。
  これは、スレッドセーフではあるかもしれませんが、そのメソッドとは同期をとらなくてもよいメソッドまでもがロックが解除されるまで待たされてしまうかもしれません。Thisに対するロックは渡って影響を及ぼすことがあることを念頭に置いてください。
  従って、以下のようにしてください。

  • まずはsynchronizedブロックで部分的なロックが実現出来るかどうかを考慮する
  • メソッド全体に同期が必要と判断された場合のみ、メソッドsynchronized宣言する

synchronizedブロックを利用する場合も、同期化の適用範囲は必要最小限に「同期化しなければならない部分だけ同期化する」ことが原則です。synchronizedの適用範囲を最小限にとどめることによって、以下のリスクをすることが軽減する出来ます。

  • デッドロック発生可能性
  • パフォーマンスの低下
  • デッドロック発生時のデバッグのコスト(可読性の低下)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    違反例
    public class GoodSample {

        private Map<String, String> names = new Map<String, String>();
        private Map<String, Long> birthdays = new Map<String, Long>();

        public synchronized void setName(String user, String name) {//違反
            names.put(user, name);
        }

        public synchronized void setBirthDay(String user, Lond birthday) {//違反
            birthdays.put(user, birthday);
        }

        // 必要以上に同期をとっているため、setName()メソッド実行中には関係のない
        // setBirthDay()メソッドも呼べません
    }
    修正例
    public class GoodSample {

        private Map<String, String> names = new Map<String, String>();
        private Map<String, Long> birthdays = new Map<String, Long>();

        public void setName(String user, String name) {// 修正済み
            synchronized (names) {// 修正済み
                names.put(user, name);
            }
        }

        public void setBirthDay(String user, Lond birthday) {// 修正済み
            synchronized (birthdays) {// 修正済み
                birthdays.put(user, birthday);
            }
        }
    }

3.7.9 マルチスレッド環境下ではConcurrentHashMapやCopyOnWriteArrayListを使う

⭐ ⭐ ⭐ ⭐

【説明・動機】
  マルチスレッド環境下で動くアプリケーションを書くなら、HashMapやArryListを同期化する Collections#synchronizedMap()#Collections#synchronizedList()ではなく、Java5から追加されたConcurrentHashMapやCopyOnWriteArryListを使いましょう。Java5から追加されたこれらのクラスは、使い勝手は今までとほとんど変わらないままで、より優れた並行アクセス性・パフォーマンスをもたらしてくれます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    違反例
    public class BadSample {
        public static void main(String[] args){
            Map<String, String> map=
                Collections.synchronizedMap(new HashMap<String,String>);//違反 全てのメソッドが同期される
            map.put("日本", "Japan");
            map.put("アメリカ", "US");
            map.put("ヨーロッパ", "EU");
            System.out.println(map);
            List<String> list=
                Collections.synchronizedList(new ArrayList<String>());//違反 全てのメソッドが同期される
            list.add("Tokyo");
            list.add("Kanagawa");
            list.add("Chiba");
            System.out.println(list);
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();// 修正済み
            map.putIfAbsent("日本", "Japan");// 新APIメソッド
            map.put("アメリカ", "US");
            map.put("ヨーロッパ", "EU");
            System.out.println(map);

            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();//修正済み
            list.addIfAbsent("Tokyo");
            list.add("Kanagawa");
            list.add("Chiba");
            System.out.println(list);

        }
    }

3.8 ガーベッジコレクション

3.8.1 finalize()をオーバーライドしない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  finalizeメソッドをオーバーライドすると、そのインスタンスがガーベッジコレクションされる時に呼び出しを受けることが出来ます。しかし、呼び出されるタイミングや呼び出しスレッドは制御出来ません。また、ガーベッジコレクションの対象とならない場合は呼び出し自体が行わないてめ、finalizeの呼び出しに依存したコードを実装すると予期せぬバグの原因となることがあります。
  そのため、例えばDBの接続やFileのクローズ処理、参照先の開放などはfinalizeで実装せず、リソースや参照先が不要になった時点で処理を行うような実装を心がけてください。
  finalizeメソッドは、ほかに代替手段がないかどうかを考え、finalizeでしか処理が出来ない場合のみオーバーライドしてください。ほとんどのケースでfinalizeでアプリケーションの処理を置く必要はありません。

【MEMEO】構成管理ツールを使い
  Javaに限らず、複数人で開発するときにはソースコードの構成管理が重要になってきます。そのためのツールが構成管理ツールです。よく使われるものでいえばSVNやCVS。最近ではGitと呼ばれるツールもよく使われています。
  どのツールを使うかはプロジェクトの状況や個人の嗜好によるですが、まずは構成管理ツールを使ってソースコードを適切に管理することを心がけましょう。
  Eclipseでの本稿執筆時点でのサポート状況を考えると、CVSはEclipseにCVSクライアントが標準ではいっています。SVNも、SubversiveやSubclipseなどのEclipseプラグインが提供されており、強力にサポートされています。
  一方Gitのサポートはまだ始めったばかりのようです。EclipseプラグインとしてEGitというEclipseプラグインが出てきたので、これからに期待が持てそうです。
※1、Subversion http://subversion.apache.org
※2、CVS http://www.nongnu.org/cvs/
※3、Git http://git-scm.com/
※4、Subversive http://www.eclipse.org/subversive
※5、Subclipse http:/subclipse.tigris.org
※6、EGit http:///www.eclipse.org/egit/

3.9 その他

3.9.1 匿名クラスにメソッド引数を渡す場合、finalを使用する

⭐ ⭐ ⭐ ⭐

【説明・動機】
  匿名クラスに対してメソッド引数を渡す場合、メンバ変数や一時的なローカル変数を使用せず、メソッド引数自体をfinalにしてください。可読性が向上します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    違反例
    public class BadSample {
        private String tempValue1;

        public Runnable sampleMethod(String value1, String value2) {
            tempValue1 = value1;

            // 違反 メンバ変数や一時変数を使用して値を渡す
            final String tempValue2 = value2;

            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    System.out.println(tempValue1 + tempValue2);
                }
            };
            return runnable;
        }
    }

    修正例
    public class GoodSample {

        private String tempValue1;

        public Runnable sampleMethod(final String value1, final String value2) {
            //修正サンプル
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    System.out.println(value1 + value2);
                }
            };
            return runnable;
        }
    }

【MEMO】 状況を理解して使うツール・戦略を変える
  本書ではJavaプロジェクトにおいて比較的一般的なツールや、間違いを起こしにくい言われるTipsを記しています。そして出来るだけ汎用的に使えるルールとなるように努めました。しかし、すべてのケースでこのルールだけが唯一絶対というわけではありません。ルールというものは本来は状況状況によって少しずつ自分たちで修正して使うべきものです。
  具体例を挙げると、Javaといってもモバイルアプリケーション、昨今では、Androidでの開発には本書のルールのいくつかはあてはまらないかもしれません。なぜならAndroidアプリケーションなどではリソースの御約が厳しかったり、そもそもAndroidフレームワークの制御によって本書に掲載した機能が使えなかったり、使い方が異なったりする可能性があるためです。
  このように、プロジェクトで採用しているテクノロジやプロジェクト固有の事情があります。それらを考慮してプロジェクトに適したルールブックを作ることが健全な開発プロジェクトには必要です。そのような場合にも何かしらの形で本書が役に立てれば幸いです。
※ Googleが開発した、おもにモバイル向けのプラットフォーム。
http://www.android.com/

3.9.2 複数の値を返すのに、配列やMapを使用しない

⭐ ⭐ ⭐ ⭐

【説明・動機】
  メソッドの戻り値で複数の値を返したい場合、安易に配列やMapなどを使用してはいけません。戻り値のどこに何が入っているのかがわからなくなり、可読性が落ち、バグが発生しやすくなるためです。特に計算結果やDBから取得した値などを返すときにこういった実装をしてしまうケースが多いため、注意してください。
  複数の値を返したい場合は、戻り値となるクラスを作成して、そのクラスのインスタンスを返すようにしてください。
  複数の値をまとめて返す場合には、クラスを使って適切なデータ構造を作成して、どのようなデータを持っているのか、どうやってデータを取り出すのかを、コードの利用者にも出来るだけわかりやすくしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    違反例
    public class BadSample {
        public String[] createPersonArray(String name, String address) {
            String[] result = { name, address };
            return result;
        }

        public Map<String, String> getPersonMap(String name, String address) {
            Map<String, String> person = new HashMap<String, String>();
            person.put("name", name);
            person.put("address", address);

            return person;
        }

        public static void main(String[] args) {
            final String name = "山田";
            final String address = "東京都";
            BadSample sample = new BadSample();

            //違反 0番目に名前、1番目に住所が入っているが
            //メソッドを呼び出した側はわからない
            String[] personArry = sample.createPersonArray(name, address);
            System.out.println("配列の型ではどこに何が入っているかわからない"+ personArry);

            //違反 名前と住所が入っているがメソッドを呼び出した側はわからない
            Map<String, String> personMap = sample.getPersonMap(name, address);
            System.out.println("Mapでもどのキーで値が入っているかわからない。"+ personArry);
        }
    }

    修正例
    public class GoodSample {
        public Person createPerson(String name, String address) {
            Person person=new Person(name,address);
            return Person;
        }

        public static void main(String[] args) {
            final String name = "山田";
            final String address = "東京都";
            GoodSample sample = new GoodSample();

            //修正済み 複数の値をまとめた適切なデータ構造としてクラスを使う
            Person person =sample.createPerson(name, address);

            //修正済み IDE等を使えば、どのような項目があるかが補完可能。
            System.out.println("名前は" + person.getName());
            System.out.println("住所は" + person.getAddress());
        }
    }

4.補足

4.1 ネーミング総括

変数/メソッド

  • 先頭を 小文字 にして後ろの各単語の先頭(区切り)を大文字にする
    • lineNumber
    • radioGroupIPAddress
    • doSomeAction() (メソッド:動詞で始まる)
    • executeSample()
    • createObjectName() オブジェクト生成メソッド
  • Class[File]を使って変数を定義するときには、フォルダーになる場合は、後ろ「 Dir 」を追加する
    • imagesDir
    • resDirなど

クラス/インタフェース/enumタイプ

  • 先頭を大文字にして後ろの各単語の先頭(区切り)を大文字にする
    • SampleCalss
    • LoginAction
  • 例外クラス: -Exception
    • SampleException   (例外クラス)
  • テストクラス: -Test
    • SampleTest   (テストクラス)
  • 抽象クラス: Abstract-
    • AbstractSampleClass (抽象クラス)

パッケージ

  • 小文字統一
    • jp.co.casio.caios.xxx.xxx

定数/enum定数

  • 全て大文字
  • 定数は全てstatic final宣言する
  • 区切り:「_」(アンダースコア)
1
2
3
4
5
6
    public static final int SAMPLE_VALUE = 1;
    public enum MultiLanguage{
        JAPANESE,
        ENGLISH,
        CHINESE;
    }

リソースファイル名

  • 全て小文字にする
  • 区切りは[_]となる
1
2
3
4
5
    main.xml
    row_item.xml
    table_name_list.xml
    strings.xml
    style.xml

リソース/(コントロール)

  • ①と同じように先頭を 小文字 にして後ろの各単語の先頭(区切り)を 大文字 にする
  • コントロールのデフォルト名を省略しないまま、後ろに役割の言葉などを追加する
  • Javaソース内でリソースと関連するときに、コントロールの名称定義はリソースと一致にする
1
2
3
    android:id="@+id/textViewTableTitle" 
    android:id="@+id/listViewTablesName" 
    android:id="@+id/gridViewTable" 
1
    ListView listViewTablesName = (ListView) findViewById(R.id.listViewTablesName);

リソース(コントロール以外)

  • 全て小文字にする
  • 区切りは[_]となる
1
2
    <string name="button_cancel">いいえ</string>
    <string name="dialog_message_unchecked_confirm">ファイルを選んでください</string>

Strings.xmlでリソースの名称定義は基本的に下記の単語で始める

  • dialog_title_
  • dialog_message_
  • title_
  • headline_
  • text_
  • button_
1
2
3
    <string name="diaog_title_unchecked_confirm_title">確認</string>
    <string name="button_record_add">追加</string>
    <string name="button_record_modify">修正</string>

Styleリソースの名称定義は基本的に「style」で始める。

1
2
3
4
5
6
7
8
    <style name="style_text">
            <item name="android:layout_width">wrap_content</item>
           ……
     </style>
    <style name="style_button">
            <item name="android:layout_height">36px</item>
           ……
    </style>

イベントメソッドの名称定義

  • 基本的に、各コントロールのイベントをクラスで実装する。
  • 同じクラスで同じイベントメソッドをアクセスできるコントロールが多数がある場合はコントロールのリソースIDで区別する
  • 各コントロールのイベントメソッドに対して他のメソッドを拡張する場合は、名称の中でコントロール名称を入れる
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    onClick(View ){ 
    switch(v.getId()){
     case buttonAppend:
               onButtonAppendClick();
     case buttonRemove:
               onButtonRemoveClick();
     case buttonClose:
               onButtonCloseClick();
    }
    onKey(...){
               ...
               onEnterKey();
               onXxxxKey();
    }

他のイベントメソッドは上記の例に参照して定義する

メソッド名称と順番

Activityのメソッドの名称定義規則と順番です。

  1. receiveParameter()
  2. initialize()
  3. onLoad()
  4. onClick()
  5. onItemClick()
  6. onAction()
  7. changeFunctionButtonStatus()
  8. showDialog()
  9. onDialogResultClick()
  10. call○○Activity()
  11. onActivityResult()
  12. update()

Activityクラスの名称

Activityの名称の後ろで「Activity」をつけます。
MainActivity
CustomerBasicActivity

カラーの使用

xmlファイルとJavaファイルでカラーを使用したい場合、Androidシステムのカラーを使用する。color.xmlを使用しないでください。
xmlファイル:

1
2
    android:textColor="@android:color/white" 
    android:textColor="@android:color/black" 

Javaファイル:

1
2
    Color.BLACK
    Color.WHITE

名称定義簡潔化

1.名称定義をできるだけ簡潔にしてください。できるだけわかりやすく、見やすくなるようにしてください

  • LinearLayout
  • RelativeLayout
  • AbsoluteLayout
    • +id/layoutXxx
  • FinderHorizontalScrollView
  • FinderScrollView
    • +id/scrollViewXxx
  • PageImageView
    • +id/imageViewXxx
  • IconButton
    • +id/buttonXxx

2.コントロールの名称を定義するときに、レイアウトのファイル名を反映する必要がないので、簡潔するために、追加しないでください

page.xml    relativeLayoutPageFunction -> layoutFunction    buttonPageCancel  -> buttonCancel    buttonPageShowMode -> buttonShowMode list.xml buttonListCancel    -> buttonCancel

3.コントロール機能の役割に従って、名称の始めを決めてください。
背景イメージがある場合は、何かボタンとか、透明なTextView,Layoutなど使っていた場合は多くになっていますが、その場合は、textView○○,layout○○などの規則より、定義されていますが理解し易くないので役割通り、変更するようにしてください。上記の場合は、同じ「button~」にしてください。

Activityのメソッドの名称定義規則と順番です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    receiveParameter();
    initialize()
    initButton()
    initTextView()
        initListView()
        ・・
    onLoad()
    onClick()
    onButtonXxxxClick()
     ・・・
    onItemClick()
    onXxxxxItemClick()
     ・・・
    onItemSelected()
    onScrollChanged()
    onScroll()
    onTouch()
    ・・
    onAction()
    onActionAdd()
    onActionModify()
    onActionDelete()
    onActionShow()
    onActionEdit()
    ・・・
    changeFunctionButtonStatus()
    changeFunctionButtonVisibleStatus()
    changeFunctionButtonEnableStatus()
    changeFunctionButtonBackgroundColorStatus()
    changeFunctionButton○○Status()
    ・・・
    showDialog()
    showXxxxDialog()
    ・・・
    onDialogResultClick()
    onXxxxxDialogResult()
    callXxxxxActivity()
    onActivityResult)
    update()
        //update(int id,string text);
        //update(int id);//R.id.listViewLeft
    updateXxxxx()

4.2 その他総括

2つ数値を比較する

メソッドMath.maxとMath.minを使ってください。
・2つ数値を比較って、大きさな数値を取得したい場合、Math.maxを使う
・2つ数値を比較って、小さいな数値を取得したい場合、Math.minを使う

1
2
3
4
    int a=10;
    int b=12;
    //Math.max(a,b)の戻り値は12
    //Math.min(a,b)の戻り値は10

文字列の連結

3以上文字列を連結する場合、StringBuilderを使ってください。

1
2
3
    StringBuilder stringBuilderSql = new StringBuilder();
    stringBuilderSql.append("SELECT ").append(TABLE_CMA001)
    //……

Mapの遍歴

○○.entrySet()を使ってください

1
2
3
4
    KeyValues kvs = new KeyValues(keyValueMap.size());
    for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
       kvs.put(entry.getKey(), entry.getValue());
    }

部品のKeyValues、ContentValuesを使う場合、値を取得する

○○.get(key)を使わないでください。下記のメソッドを使ってください。

  • ○○.getAsString(key)
  • ○○.getAsBoolean(key)
1
2
3
4
    ContentValues cvs = new ContentValues();
    String contentValue=cvs.getAsString(key);
    KeyValues kvs = new KeyValues();
    String keyValue=kvs.getAsString(key);

配列のコピー

配列をコピーしたい場合、System.arraycopyを使ってください

1
2
    columns = new String[columns.length];
    System.arraycopy(columns, 0, queryColumns, 0, columns.length);
領域 自分クラス 同じバッケージ サブクラス その他バッケージ
public
protected ×
friendly × ×
private × × ×
  1. public 公開メンバー、すべてのクラスとパッケージで使用することができます
  2. private 私有メンバー、自分のクラスのみ使用することできます。
  3. protected 自分のクラスや継承されたクラスや同一パッケージで使用することできます。
  4. friendly 自分のクラスと同じパッケージで使用することできます。