はじめに
自分用のJavaコーディング規約です。
自身で普段気を付けていること、書籍やネットでためになったこと、を忘れないようにまとめます。
一般
インポートに「*
」は使わない
そのクラスがどのパッケージのものなのか分かりやすくするため。
ただしimport文が大量になって逆に可読性が下がる場合は利用も考える。
ちなみにJavaでは「*
」を指定してもパフォーマンスに影響したり、クラスファイルのサイズが大きくなったりすることはない。
booleanメソッド名はどちらを返すのかわかるようにする
OK exists(), hasData() //存在してればtrueが返るのが明白
NG check() //どういう条件でtrueが返るのかわからない
equals()とhashCode()はセットでオーバーライドする
基本的なことだが、hashCode()もオーバーライドし忘れるとHashMapなどHashXXXでそのクラスのインスタンスが使えなくなるためオーバーライドする。
ラッパークラスを乱用しない
メモリ消費やパフォーマンスを考えてなるべくプリミティブ型を使う。
toString()のオーバーライドを検討する
例えば頻繁にログ出力するインスタンスは
毎回getXxx()を複数並べるよりもobj.toString()だけで決まった文字列をとれるようにする。
定数にひと処理入れたい場合はstatic初期化子の利用を検討する
public class Main {
public final static String OUTPUT_DATE;
public final static String OUTPUT_FILENAME;
static {
OUTPUT_DATE = getDateStr();
OUTPUT_FILENAME = "output_" + OUTPUT_DATE;
}
private static String getDateStr() {
DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
return sdf.format(new Date());
}
}
インスタンスの初期化をなるべくする
しなかった場合デフォルトの初期化が行われるが、初期化は明確にしておいた方がいい。
初期化しない場合のデフォルトの初期化
型 | デフォルトの初期値 |
---|---|
整数(short, int, long) | 0 |
不動小数点数(float, double) | 0.0 |
char | '\u0000' |
boolean | false |
その他参照型 | null |
配列の定数を作成しない
配列をstatic finalにしても個々の要素にfinalは適用されない。
かといって単純に可変なpublic変数にしたくない、という場合はunmodifiableListを利用する。
public static final List fruits = Collections.unmodifiableList( Arrays.asList({"apple","orange","lemon"}) );
引数にnullが渡るとヌルぽが発生するので注意。
また、このListに指定する要素は文字列や数値などのラッパークラスだけとする。
その他のオブジェクトを指定するとやはりオブジェクトが持つ中身(フィールド)は書き換えられる(finalが適用されない)ため。
文字列系
文字列をStringのまま編集しない
コストを考えて、StringBuilderもしくはStringBufferを使う。
StringBuilderの方が高速だがスレッドセーフではないので注意。
インスタンス変数にはStringBufferを使う。
ローカル変数にはStringBuilderを使う。
ちなみに以下のコードについてはJavaコンパイラが内部的にStringBuilderに変換するのでこのままで問題ない。(わざわざStringBuilderに書き換える必要はない)
String str = "(" + id + ")";
以下のコードはオブジェクトの生成・破棄が多くなるのでしてはいけない。(コスト面でStringBuilder、StringBufferを使う意味が無くなる)
sb.append("(" + id + ")");
文字列変換の方法を統一する
Javaには数値⇔文字列変換の方法がいくつかあるが、統一しておいた方がどれ使おうかな?と迷わなくてすむ。
以下のように変換先のクラスメソッドで統一すると決めてしまえば迷わない。
String intStr = String.valueOf(i);
int i2 = Integer.perseInt(intStr);
数値系
料金計算にはBigDecimalを使う
小数点を扱い、かつ消費税や外貨などで誤差を生じさせたくない場合は
Doubleだと誤差が生じる可能性があるためBigDecimaiを使う。
数値型の桁数を意識する
個人的にあまり負の値は使わないので正の値だけに着目。
型 | サイズ | 自由に使える範囲 |
---|---|---|
byte | 8bit | 2桁(十の位)までの数値 |
short | 16bit | 4桁(千の位)までの数値 |
int | 32bit | 9桁(億の位)までの数値 |
long | 64bit | 18桁(十京の位)までの数値 |
実際は記載の1つ上の桁まで扱える(shortならMAXが32767なのでこの値までなら大丈夫)が、危ない橋を渡る必要はない。
また、現代の一般的なコンピュータではメモリ節約の観点でbyteやshortを使う理由はないらしいので整数の変数を作りたい場合はだいたいintでよさそう。
(変数定義で確保されるメモリの最小単位がint(32bit)だからshortで定義しても物理的な利用サイズは変わらないとか?)
コレクション系
Iteratorの代わりに拡張forを利用する
コレクションをループさせる場合で、ループの中で要素を削除する、などループ対象に変更を加えない場合は拡張for文を利用する。
ちなみにforで要素削除するとConcurrentModificationExceptionが発生する。
Java8以上が使えるならstreamのforeachもあると思うが、例外処理を考えたりすると意外に可読性が下がる気がするのでネストが深くなる場合は拡張forにする。
try~catchはなるべくループの中に書かない
try~catchはコストの大きい処理なので可能ならループの外に出す。
// できればこっち
try {
for(){
// 省略
}
} catch () {
// 省略
}
// こっちはコストが大きい
for(){
try {
// 省略
} catch () {
// 省略
}
}
コレクション・配列をreturnするメソッドでnullを返さない
nullを返すと呼び出し元でnullチェックが必須になるため。
nullの代わりに要素0の配列や初期化のみしたコレクションを返す。
java.util.Collections.emptyList()
やemptyMap()を使うと定数を返すので初期化コストもかからない。
ファイル入出力系
大きいテキストファイルの入出力
GB単位の大きいテキストファイルの入出力には
コスト優先でバッファリングを行う。
つまり昔ながらのBufferedXXX系のクラスを利用する。
その際Java7以上が使える場合は、try-with-resourceでclose処理を省略する。
小さいテキストファイルの入出力
Java7以上が使える場合は、可読性優先でjava.nio.file.Files
を利用する。
使えないなら昔ながらのBufferedXXX系。
その他
循環的複雑度を確認してみる
EclipseやIntelliJなど各IDEで計測が可能。
20以上ならリファクタを考える。
循環的複雑度とはThomas McCabeという人が開発したプログラムの複雑度を測る方法の一種。
10以下なら読みやすく、20以上は複雑と判断されるらしい。