言語を選択
durumis AIが要約した文章
- newキーワードを使用して文字列またはBooleanインスタンスを作成することは、メモリ浪費であるため、リテラルで宣言するか、 Boolean.valueOf()メソッドを使用するのが最適です。
- String.matches()メソッドは正規表現を使用するため、パフォーマンス上の問題が発生する可能性があり、Patternインスタンスをキャッシュして 再利用するのが最適です。
- keySet()メソッドなど、ビューオブジェクトを返す場合は、防御的コピーを使用して新しいオブジェクトを返す方が安全です。
不要なオブジェクトを作成する場合
new String() の使用
String a = new String("hi");
String b = new String("hi");
文字列 a、b、c はいずれも "hi" という文字列を持つようになります。しかし、この3つの文字列が参照するアドレスはすべて異なるため、同じ データに対して別々のメモリを割り当てているという無駄が発生します。
そのため、文字列を宣言する際には、new キーワードを使用するのではなく、リテラルで宣言する必要があります。
String a = "hi";
String b = "hi";
上記のソースコードは、1つのインスタンスのみを使用しています。さらに、この方法を使用すると、同じ JVM 内で "hi" 文字列リテラルを使用する すべてのコードが同じオブジェクトを再利用することを保証します。これは、Java の定数プールの特徴によるものです。
new Boolean() の使用
上記のコードは、文字列をパラメータとして受け取るコンストラクタを使用して、Boolean インスタンスを作成しています。Boolean は true または false のみが存在しますが、毎回インスタンスを作成することはメモリ浪費になります。そのため、Boolean.valueOf() という静的ファクトリメソッドを使用する方が良いでしょう。
String.matches() の使用
生成コストが高い場合は、キャッシュして再利用する方が良いですが、常に私たちが作成するオブジェクトのコストを知ることはできません。たとえば、与えられた文字列が 有効なローマ数字であるかどうかを確認するメソッドを作成したい場合は、次のように正規表現を使用するのが最も簡単です。
public static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
しかし、String.matches() はパフォーマンス的に問題のあるメソッドです。このメソッドが内部で作成する正規表現用の Pattern インスタンスは、一度使用したら破棄され、すぐにガベージコレクションの対象となります。この正規表現が繰り返し使用される頻度が 高くなるほど、同じ Pattern インスタンスが生成され、破棄されるコストが高くなります。そのため、Pattern インスタンスを事前にキャッシュしておき、 後で isRomanNumeral() メソッドが呼び出されるたびに、このインスタンスを再利用する方が良いでしょう。
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
public static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
注意事項
上記のすべての例では、不要なオブジェクトをキャッシュする際に、すべて不変オブジェクトとして作成しました。これは、再利用しても安全にするためです。しかし、不変 オブジェクトとして再利用するという直感に反するケースがあります。
アダプター(ビュー)は、実際の作業はバックエンドのオブジェクトに委譲し、自身は第2のインターフェースとしての役割を果たすオブジェクトです。アダプターは、バックエンドのオブジェクトのみを 管理すればよいので、バックエンドのオブジェクト1つにつきアダプターを1つだけ作成すればよいのです。
たとえば、Map インターフェースの keySet() メソッドは、Map オブジェクト内のすべてのキーを含む Set ビューを返します。ユーザーは、 keySet() メソッドを呼び出すたびに、新しい Set インスタンスが作成されると考えるかもしれません。しかし、実際の JDK の実装を見ると、 毎回同じ可変 Set インスタンスが返されます。
これは、返される Set インスタンスは可変であっても、実行する機能はすべて同じであり、すべての Set インスタンスが Map インスタンスを代表しているためです。したがって、keySet() がビューオブジェクトを複数作成しても問題はありませんが、そうする必要も利点もありません。
public class UsingKeySet {
public static void main(String[] args) {
Map menu = new HashMap<>();
menu.put("Burger", 8);
menu.put("Pizza", 9);
Set names1 = menu.keySet();
Set names2 = menu.keySet();
names1.remove("Burger");
System.out.println(names1.size()); // 1
System.out.println(names2.size()); // 1
}
そのため、上記のように names1 インスタンスを変更すると、names2 インスタンスにも影響が及ぶことになります。
しかし、個人的には、keySet() メソッドの戻り値は、防御的コピーを使用して、毎回新しいオブジェクトを返す方法が正しいと考えています。 もし、keySet() メソッドから受け取った Set インスタンスが、他の場所でも使用されており、このインスタンスの状態を変更するコードがある場合、 現在使用している Set インスタンスと Map インスタンスの値について確信が持てなくなります。
また、keySet() を過度に頻繁に使用している環境でない限り、Set インターフェースが毎回生成されたとしても、パフォーマンスに致命的な影響を与えることはありません。むしろ、Set インターフェースを不変オブジェクトとして作成し、安定して保守管理を行う方が良いと考えています。
オートボクシング
オートボクシングは、プログラマーが基本型とラッパー型を混在して使用する場合に、自動的に相互に変換する技術です。しかし、オートボクシングは基本型とラッパー型の違いをぼかすだけで、完全に消してしまうわけではありません。
public static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
ロジック上は問題ありませんが、パフォーマンス的に非常に非効率的なコードです。これは、sum の型と for 文内の i の型によるものです。
sum の型は Long 型で、i は long 型です。つまり、long 型の i は、繰り返し文を回って sum に加算されるたびに、 新しい Long インスタンスが作成されます。結果として、ラッパー型よりも基本型を使用し、意図しないオートボクシングが行われないように注意する必要があります。
誤解しないように
不要なオブジェクトの作成を避けるべきだというのは、単にオブジェクト作成のコストが大きいので避けるべきだという誤解をしてはいけません。
特に、最近の JVM では、不要に生成された小さなオブジェクトを生成および回収することは、それほど負担のかかる作業ではありません。そのため、データベース 接続など、コストのかかるオブジェクトでない限り、カスタムオブジェクトプールを作成しないようにしましょう。
さらに、防御的コピーが必要な状況でオブジェクトを再利用した場合の被害は、不要なオブジェクトを繰り返し生成した場合の被害よりもはるかに大きいということを覚えておきましょう。繰り返し生成の副作用は、コードの形式とパフォーマンスにのみ影響しますが、防御的コピーが失敗すると、バグとセキュリティ問題につながります。