なぜこの記事を書こうとしたのか?
どうも、ラクーンホールディングス(https://www.raccoon.ne.jp/) のシュウヤです。
私は普段Javaで開発をしていますが、美しくパフォーマンスの高い書き方を知らずに、保守性もパフォーマンスも悪いコードを書いていました。このままではいけないと思い、美しくパフォーマンスの高いコードの書き方を調べたところ、「イディオム」という概念にたどり着きました。イディオムとは、ソフトウェア工学におけるアルゴリズムやプログラミングのノウハウ、ティップス(tips)を集めたもので、要するにベストプラクティスに基づいてコードを書くためのヒントのようなものだそうです。これらをまとめることで、少しでも美しくパフォーマンスの良いJavaのコードが書けるのではないかと思いました。
目的
- 美しくパフォーマンスの良いJavaの書き方を学べる
目次
- Stringリテラルまたは既知のオブジェクトでequals()を呼び出す
- HashMapをループする際のentrySetの使用
- Enumをシングルトンとして使用
- Collectionの初期化にArrays.asList()を使用
- 依存性注入を考慮してコードを書く
- 拡張forループでリストは回す
- streamAPIを使う
- 委譲を使う
Stringリテラルまたは既知のオブジェクトでequals()を呼び出す
NullPointExceptionが出ないようにできます。うれしすぎる
バッドプラクティス
// NullPointの可能性がある
if (givenString.equals("YES")) {
// do something
}
ベストプラクティス
// NullPointの可能性がない
if ("YES".equals(givenString)) {
// do something
}
HashMapをループする際にentrySetを使用しよう
key,valueをまとめて処理できるのでおすすめです。
バッドプラクティスだと見づらいですしね。
バッドプラクティス
// 可読性が悪い map.get(k)を繰り返し呼び出すため、パフォーマンスが悪くなる可能性がある
Set<Key> keySet = map.keySet();
for (Key k : keySet) {
Value v = map.get(k);
System.out.println(k + ": " + v);
}
ベストプラクティス
// entrySetで効率的にキーとvalueを取得できる
for (Map.Entry<Key, Value> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
Enumはシングルトンとして使用
シングルトン実装しようと思っても面倒くさいと思っていたのですが、解決できそうです。
バッドプラクティス
// 冗長なコードで可読性が悪くなっている
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
ベストプラクティス
// Enumを使うことで、シングルトンの実装が簡潔でスレッドセーフにな
public enum Singleton {
INSTANCE;
}
Collectionの初期化にArrays.asList()を使用
最初からリストに必要なデータを入れておくのが吉ですね!
バッドプラクティス
// 上長
List<String> listOfCurrencies = new ArrayList<>();
listOfCurrencies.add("USD/AUD");
listOfCurrencies.add("USD/JPY");
listOfCurrencies.add("USD/INR");
ベストプラクティス
// リストの初期化はArrays.asListを使うと
List<String> listOfCurrencies = Arrays.asList("USD/AUD", "USD/JPY", "USD/INR");
※arraylistとarray.aslistの違いについてです。
ArrrayListは可変長でasListは固定長です。可変性という意味ではArrayListの方があり、asListはきちんと書くときには使わないことが多いそうです。
依存性注入を考慮したコード
コンストラクタを設定しておくと良いですね!
バッドプラクティス
// クラスが密結合になっており、テストが困難
public class Game {
private HighScoreService service = HighScoreService.getInstance();
public void showLeaderBoard() {
List<String> listOfTopPlayers = service.getLeaderBoard();
System.out.println(listOfTopPlayers);
}
}
ベストプラクティス
// Game(mockService) みたいに書けるし、他のサービスクラスにも変えられる
public class Game {
private HighScoreService service;
public Game(HighScoreService service) {
this.service = service;
}
public void showLeaderBoard() {
List<String> listOfTopPlayers = service.getLeaderBoard();
System.out.println(listOfTopPlayers);
}
}
streamAPIを使う
forをわざわざ作らなくても書けますね。
バッドプラクティス
// コードが冗長
List<String> upperCaseNames = new ArrayList<>();
for (String name : names) {
upperCaseNames.add(name.toUpperCase());
}
ベストプラクティス
// 見やすい
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
委譲を使う
継承ではなく委譲を使えるようにしましょう
バッドプラクティス
// 継承で共通の機能を受け継ぐことができるが、クラスは親クラスに強く結合されるため、親クラスの変更が子クラスに予期せぬ影響を与えてしまう
public class Animal {
public void makeSound() {
System.out.println("なんかの動物の鳴き声");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("ワン");
}
}
ベストプラクティス
public class Animal {
public void makeSound() {
System.out.println("なんかの動物の鳴き声");
}
}
// 変更に強い構造にするため、委譲を使用
public class Dog {
private Animal animal;
public Dog(Animal animal) {
this.animal = animal;
}
public void makeDogSound() {
System.out.println("ワン");
}
public void makeGenericSound() {
animal.makeSound();
}
}
// 実行例
public class Main {
public static void main(String[] args) {
Animal genericAnimal = new Animal();
Dog dog = new Dog(genericAnimal);
// 特定の犬の鳴き声
dog.makeDogSound();
// 一般的な動物の鳴き声
dog.makeGenericSound();
}
}
まとめ
いかがでしたか?
意外と私は知らないことが多くて、勉強になりました。
誰かのためになればうれしいです。
参照した記事
https://saltmarch.com/insight/8-game-changing-java-8-idioms-for-robust-code
https://javarevisited.blogspot.com/2022/12/top-10-java-idioms-i-wish-id-learned.html#axzz8c9fhDWbw
https://stackabuse.com/exception-handling-in-java-a-complete-guide-with-best-and-worst-practices/