なぜ不変オブジェクトとfinalを使用するのか


なぜ「不変オブジェクト」(Immutable Object)と「final」を使うのか


不変オブジェクトとは?


不変オブジェクト(Immutable Object)とは、オブジェクトの作成後に内部状態が変化しないオブジェクトのことです.
オブジェクトの内部状態を提供する方法を提供したり提供したりしないread-onlyメソッドのみが提供される場合は、防御コピーで提供されます.
Javaの代表不変オブジェクトにStringがあります.
String name = "Old";
name = "New";
name.toCharrArray()[2] = 'o';
JavaのStringは不変クラスなので、nameという変数に「New」を割り当てるのは内部charを変更するのではなく、新しい値を持つオブジェクトを作成します.また,String内部のchar型アレイが修正されても反映されない.Javaは、配列やオブジェクトなどの参照を渡します.したがって、値を参照して修正すると内部の状態が変化するので、内部をコピーして伝達しています.これを防御コピーと呼びます.JavaのStringは、toCharArrayをコピーし、次のように転送します.
public char[] toCharArray(){
	// Cannot use Arrays.copyOf because of class initialization order issues
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}

なぜ不変オブジェクトとfinalを使用するのか

  • Thread-Safeは、同期を考慮することなく並列プログラミングに使用されます.
  • マルチスレッド環境で同期の問題が発生したのは、共有リソースを同時に書き込むためです.
    ただし、共有リソースが不変のリソースであれば、同期を考慮する必要はありません.
    常に同じ値を返すため、安定性が保証されるだけでなく、非同期によるパフォーマンスのメリットもあります.
  • で失敗した原子法を作成できます.
  • 可変オブジェクトによる操作中に異常が発生した場合、オブジェクトは不安定な状態になる可能性があります.また、不安定な状態にあるオブジェクトは、他のエラーを引き起こす可能性があります.ただし、オブジェクトが変わらない場合は、どのような例外が発生しても、メソッド呼び出し前の状態を維持できます.また,異常が発生しても,エラーが発生しないように次のような論理を扱うことができる.
  • キャッシュ、Map、またはSetなどの要素として使用するのに適しています.
  • キャッシュ、Map、Setなどのオブジェクトが変更されている場合は、リフレッシュなどの他の操作が必要です.ただし、オブジェクトが変わらない場合は、1回のデータを格納した後に他の追加操作を考慮する必要はありません.これにより、キャッシュや他のデータ構造の使用に役立ちます.
  • の副作用を回避し、エラーの可能性を最小限に抑えることができます.
  • 負の効果とは、変数の値が変更されたり、フィールド値が設定されたりするなど変化する効果です.オブジェクトの「モディファイヤ」(Setter)が実装され、オブジェクトの値がさまざまな方法で変更された場合、オブジェクトは予測しにくくなります.オブジェクトの変更ステータスを理解するには、これらのメソッドを表示する必要があります.これらのメソッドは、メンテナンス性を大幅に低下させます.したがって、これらの負の効果のない純粋な関数を作成することは非常に重要であり、オブジェクトが変わらない場合、値を変更することは基本的に不可能であるため、変更の可能性は小さく、オブジェクトの作成と使用は大きく制限されています.したがって、これらの方法は自然に純粋な関数で構成され、他の方法が呼び出されてもオブジェクトの状態を維持するため、オブジェクトを安全に再利用することができる.これらの変更されていないオブジェクトは、エラーを減らし、メンテナンス可能なコードを作成するのに役立ちます.
  • は、他の人が作成した関数を予測し、安全に使用することができる.
  • 一般的に、開発を行うと他の人と協力します.不変性(Immutability)は、コラボレーションの過程でも役立ち、不変性のある関数であれば、他人が開発した関数をリスクなく利用することができます.同様に、他の人が私の作成方法を呼び出しても、値が変わらないことを保証することができます.したがって,他人のコードを遠慮なく変更することができる.
  • ゴミ収集の性能を向上させることができます.
  • 不変性を利用してGCの性能を向上させた.
    不変オブジェクトは、一度作成した後では変更できないオブジェクトであり、Javaはfinalキーワードを使用して不変オブジェクトを作成することができます.つまり、このようにオブジェクトを作成するためには、オブジェクトを持つコンテナも存在します.もちろん、コンテナは、不変のオブジェクトを作成してからオブジェクトを参照することができます.
    つまり、コンテナはコンテナが参照する最も若いオブジェクトよりも若い(作成が遅い).これにより、ゴミ収集器がGCを実行する際にコンテナサブアイテムの不変オブジェクトをSkipするのに役立ちます.このコンテナがまだ生きていることは、サブオブジェクトの不変オブジェクトも最初に割り当てられた状態で参照されることを意味するからである.
    最終的には,不変のオブジェクトを利用すると,ゴミ収集器がスキャンする必要があるオブジェクトの数が減少し,スキャンする必要があるメモリ領域や周波数も減少し,GCが実行されても遅延時間が減少する.

    不変性のあるコードレイアウト


    ex)不変性を保証しない
    @RequiredArgsConstructor 
    @Service 
    public class UserService { 
    	private final UserRepository userRepository; 
        private final UserGroupRepository userGroupRepository; 
        
        public void getUserGroup(String searchKeyword) { 
        	List<String> userIdList = userRepository.findUserIdList(searchKeyword);
            if(userIdList.isEmpty()) { 
            	return UserGroup.builder() 				.userGroupList(Collections.emptyList()).build(); 
                } 
            
            List<UserGroup> userGroupList = userGroupRepository.findAllByUserIdList(userIdList); 
            updateUserGroupLanguage(userGroupList, userIdList); 
            return UserGroup.builder() .userIdList(userIdList.isEmpty() ? Collections.emptyList() : userGroupList) .build(); 
            } 
            
            private void updateUserGroupLanguage(List<UserGroup> userGroupList, List<String> userIdList) { // 생략 } }
    
    上のコードの疑問点userIdListが空の場合、emptyListとして処理されて返されますが、なぜ次は3つの演算子で空かどうかをチェックしますか?そう思ってもいいです.
    上記のコード記述の妥当性を決定するために、updateUserGroupLanguage()関数の内部にuserIdList変異体が存在するかどうかを確認する必要があるかもしれません.(最終的には、不要なコードコメント時間が発生し、生産性が低下するなどの問題が発生します.)また、実際にupdateUserGroupLanguage関数を使用してuserIdListを変更することはありません.その論理は不要な論理なので、3つの演算子部分を削除できます.
    updateUserGroupLanguageが不変性を保証する関数である場合、関数の内部を不要に表示する必要はありません.すぐにこの部分をクリアできると確信しています.
    価格は変わらないと確信しているので、他の人の方法を使うことができます.

    Javaは不変オブジェクトを作成する方法


    ex)不変クラスの例
    最終宣言
  • クラス
  • すべてのクラス変数をprivateとfinalとして宣言
  • オブジェクトを作成するジェネレータまたはスタティックファクトリメソッド
  • を追加
  • を参照して変更が可能な場合は、防御的レプリケーションを使用して
  • を転送します.
    public final class ImmutableClass{
    	
        private final int age;
        private final String name;
        private final List<String> list;
        
        private ImmutableClass(int age, String name){
        	this.age=age;
            this.name=name;
            this.list = new ArrayList<>();
        }
        
        public static ImmutableClass of(int age, String name){
        	return new ImmutableClass(age, name);
        }
        
        public int getAge(){
        	return age;
        }
        
        public String getName(){
        	return name;
        }
        
        public List<String> getList(){
        	return Collections.unmodifiableList(list);
        }
    }
    上記のコードで注意すべき点は、内部ジェネレータを作成するのではなく、オブジェクトの作成に静的ファクトリメソッドを提供し、クライアントが変更可能なlist
    Javaでは、作成者が宣言されていない場合、デフォルトの作成者が自動的に作成され、他のクラスでオブジェクトを自由に呼び出すことができます.したがって、内部ジェネレータを作成するよりも、静的ファクトリメソッドによるオブジェクトの作成を強制します.
    また、配列または他のオブジェクトまたはコレクションは、参照を渡すことによって変更できます.したがって、参照によって変更できる場合は、戻り値を防御的にコピーする必要があります.
    非常に妥当な理由がない限り、階級は融通しなければならない.クラスを一定に保つことができない場合は、可能な変更を最小限に抑えます.