2-6:不必要なオブジェクトの生成を避ける
なぜ避けるべきか
-
オブジェクト生成は GC 負荷とメモリ使用量を増やす
→ 頻繁だとアプリ全体のスループット低下や遅延を招く。 -
一部のオブジェクトは作成コストが高い(I/O、暗黙のコピー、内部配列確保など)。
-
ただし、現代 JVM は多くの最適化(短命オブジェクトの高速割当て・逃逸解析など)を行うので 盲目的な最適化は避ける。
よくあるケースと改善
①ループ内で不要なインスタンスを作る — String連結の例
悪い例:
String s = "";
for (String part : parts) {
s += part; // 毎回新しい String を生成(非効率)
}
良い例:
StringBuilder sb = new StringBuilder();
for (String part : parts) {
sb.append(part);
}
String s = sb.toString();
理由:+ はコンパイラが StringBuilder に変換するが、ループ内だと毎回新しい StringBuilder/String が作られがち。
②ボクシング/アンボクシングの無駄(プリミティブ vs ラッパー)
悪い例:
List<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(i); // ボクシング発生
}
改善案:
-
必要ならプリミティブ専用コレクション(TIntArrayList 等サードパーティ)を検討。
-
ループ内で頻繁にオブジェクトを生成するなら、プリミティブを使って処理してから最後に一度だけボクシングする。
注意:自動ボクシングは便利だが大量に使うとオブジェクトが増える。
⓷ラッパークラスの明示的生成を避ける(キャッシュを使う)
悪い例:
Integer a = new Integer(42); // 毎回新しいインスタンス
良い例:
Integer a = Integer.valueOf(42); // キャッシュを利用(-128〜127 等)
同様に Boolean.valueOf, Long.valueOf を使う。
④定数や再利用可能オブジェクトは共有する
悪い例:
public List<String> getEmpty() {
return new ArrayList<>();
}
良い例:
public List<String> getEmpty() {
return Collections.emptyList(); // 共有イミュータブル
}
また BigInteger.ONE のような既存の定数を使う。
⑤不必要な防御的コピーを避ける(ただし安全性重視)
悪い例:
public void setData(byte[] data) {
this.data = Arrays.copyOf(data, data.length); // 毎回コピー(必要ない場合は無駄)
}
改善:API 契約で「呼び出し側はもうその配列を使わない」など明確にできるならコピーを省く。ただしセキュリティや不変性の観点でコピーが必要ならやめない。
⑥使い回し可能なオブジェクトを再利用する(例:DateFormat)
悪い例:
for (Record r : records) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String s = df.format(r.getDate()); // 毎回生成
}
良い例
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
for (Record r : records) {
String s = df.format(r.getDate());
}
並列環境なら ThreadLocal や DateTimeFormatter(java.time のスレッド安全なもの)を使う。
⑦一時的なオブジェクトを減らすための API 選択
-
String.join / Collectors.joining 等、効率的な標準 API を活用。
-
EnumSet / EnumMap は小さな enum 集合に対して HashSet より効率的。
⑧不必要な同期やプロキシの生成を避ける
- フレームワークが動的プロキシやラムダで多数のインスタンスを作る場合、生成頻度を把握して適切にキャッシュする。
注意点・バランス(重要)
-
可読性 vs 最適化:過度に最適化してコードが読みにくくなるのは避ける。まずは正しい設計と実装。
-
測定を先に:ホットスポット(実際にボトルネックになっている)を JMH 等で計測してから最適化する。
-
JVM の最適化を理解する:短命オブジェクトは高速に割り当てられる(Eden、TLAB)。逃避解析でオブジェクトを割り当てない最適化も行われる。従って「オブジェクト作成=必ず遅い」と単純に考えない。
-
スレッド安全性:再利用のためにオブジェクトを共有する場合はスレッド安全を忘れない(volatile、同期、スレッドローカル、または不変オブジェクトにする)。
- 過度なプールは逆効果:オブジェクトプーリングは多くの場合逆にパフォーマンスを悪化させる(ロック競合、複雑さ)。JVM が最適化してくれるケースが多い。