不要なオブジェクトを作成する場合
new String()の使用
文字列a、b、cはすべて「hi」という文字列を持つようになります。しかし、これらの3つの文字列が参照するアドレスはすべて異なるため、同じデータに対して異なるメモリを割り当てるという無駄が発生します。
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa53d14aa-6abf-40f0-8441-435647d172fa%2FUntitled.png?table=block&id=f168859c-367c-4924-a4c6-5e04788fab67&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1146;" width="2000" height="1146"></span>
そのため、文字列を宣言する際には、newキーワードを使用するのではなく、リテラルで宣言する必要があります。
上記のソースコードは、1つのインスタンスのみを使用します。さらに、この方法を使用すると、同じJVM内で「hi」文字列リテラルを使用するすべてのコードが、同じオブジェクトを再利用することを保証します。これは、Java定数プールの特性によるものです。
new Boolean()の使用
上記のコードは、文字列をパラメータとして受け取るコンストラクタを通じてBooleanインスタンスを作成しています。Booleanはtrueまたはfalseのみが存在しますが、毎回インスタンスを作成するのはメモリの無駄になります。そのため、Boolean.valueOf()という静的ファクトリメソッドを使用する方が良いでしょう。
String.matches()の使用
作成コストが高い場合は、キャッシュして再利用するのが良いですが、常に作成するオブジェクトのコストを知ることはできません。たとえば、与えられた文字列が有効なローマ数字かどうかを確認するメソッドを作成したい場合、次のように正規表現を利用するのが最も簡単です。
しかし、String.matches()はパフォーマンス的に問題のあるメソッドです。このメソッドが内部で作成する正規表現用のPatternインスタンスは、一度使用して破棄され、すぐにガベージコレクションの対象となりますが、当該正規表現が繰り返し使用される頻度が高くなるほど、同じPatternインスタンスが作成および破棄されるコストが大きくなります。そのため、Patternインスタンスを事前にキャッシュしておき、後でisRomanNumeral()メソッドが呼び出されるたびに、このインスタンスを再利用するのが良いでしょう。
注意事項
上記のすべての例で、不要なオブジェクトをキャッシュする際に、すべて不変オブジェクトにしました。再利用しても安全であるためです。しかし、不変オブジェクトで再利用するという直感に反するケースがあります。
アダプタ(ビュー)は、実際の作業はバックエンドオブジェクトに委譲し、自身は第2のインターフェースの役割を果たすオブジェクトです。アダプタはバックエンドオブジェクトのみを管理すればよいため、バックエンドオブジェクト1つにつきアダプタを1つだけ作成すれば済みます。
たとえば、MapインターフェースのkeySet()メソッドは、Mapオブジェクト内のすべてのキーを含むSetビューを返します。ユーザーは、keySet()メソッドを呼び出すたびに新しいSetインスタンスが作成されると考えるかもしれませんが、実際のJDKの実装内容を見ると、毎回同じ可変Setインスタンスを返しています。
これは、返されたSetインスタンスは可変であっても、実行する機能はすべて同じであり、すべてのSetインスタンスがMapインスタンスを表しているためです。そのため、keySet()がビューオブジェクトを複数作成しても問題はありませんが、そうする必要もメリットもありません。
したがって、上記のようにnames1インスタンスを変更すると、names2インスタンスも一緒に影響を受けます。
しかし、個人的には、keySet()メソッドの戻り値は、防御的コピーを使用して毎回新しいオブジェクトを返す方法が正しいと考えています。もし、keySet()メソッドで受け取ったSetインスタンスが他の場所でも使用中で、このインスタンスの状態を変更するコードがある場合、現在使用中のSetインスタンスとMapインスタンスの値に確信を持つことができなくなります。
また、keySet()を過度に多く使用している環境でない限り、Setインターフェースが毎回生成されることでパフォーマンスに致命的な影響を与えることはありません。むしろ、Setインターフェースを不変オブジェクトにして、安定して保守できるようにした方が良いと考えています。
オートボクシング
オートボクシングは、プログラマーが基本型とラッパー型を混在して使用する場合に、自動的に相互に変換してくれる技術です。しかし、オートボクシングは基本型とラッパー型の区別を曖昧にするだけで、完全に無くしてくれるわけではありません。
ロジック上は問題ありませんが、パフォーマンス的に非常に非効率的なコードです。その原因は、sumの型とfor文内のiの型にあります。
sumの型はLong型で、iはlong型です。つまり、long型のiは、繰り返し文を回ってsumに加算されるたびに、新しいLongインスタンスを作成します。結果として、ラッパー型よりも基本型を使用し、意図しないオートボクシングが使用されないように注意する必要があります。
誤解してはいけない部分
不要なオブジェクトの作成を避けるべきだということは、単にオブジェクトの作成コストが高いので避けるべきだと誤解してはいけません。
特に、最近のJVMでは、不要に作成された小さなオブジェクトの作成と回収は、それほど負担になる作業ではありません。したがって、データベース接続など、コストが非常に大きなオブジェクトでない限り、カスタムオブジェクトプールを作成しないようにしましょう。
さらに、防御的コピーが必要な状況でオブジェクトを再利用した際の被害は、不要なオブジェクトを繰り返し作成した際の被害よりもはるかに大きいことを覚えておきましょう。繰り返し作成による副作用は、コードの形式とパフォーマンスにのみ影響を与えますが、防御的コピーが失敗すると、バグとセキュリティ問題に直結します。
コメント0