java&spring

[Java] 불변 객체(Immutable Object)

sungjine 2017. 7. 14. 20:11
반응형

불변 객체란 한번 생성된 후 변경할 수 없는 객체를 뜻하고 변경할 수 없기 때문에 멀티스레드 환경에서 마음 놓고 사용할 수 있다는 장점을 가졌다.

 

불변 객체를 만드는 방법은 아래와 같다.

 

1. 모든 인스턴스 변수는 private final키워드를 사용한다.

2. 인스턴스 변수를 수정하는 메서드를 제공하지 않는다.

3. 만약 객체를 return 해야 하는 경우가 생긴다면 새로운 객체를 생성하여 return 해준다.

4. 상속할 수 없게 한다. 만약 상속이 가능하다면 sub class를 통해 불변 상태가 깨질 수 있다.

( ex > public final class className {} )

5. 인스턴스 변수로 불변 객체 또는 기본형 타입을 변수로 사용해야 한다. 가변 객체를 사용한다면 방어적 복사본을 만들어야 한다.

 * 자주 생성되는 인스턴스는 재사용하기 위해서 캐시를 해두면 좋다.

 

대표적인 불변 객체는 String의 객체이다.

 

String str = "a";
str = "b";
str += "c";
str.toUpperCase();
System.out.println(str);  // "bc"를 출력한다.
 
위 코드에서 처음 str 변수를 초기화할 때 String 객체가 생성되고 "b"를 다시 담을 때 "b"를 담은 String 객체가 다시 생성되어 str 변수에 담긴다. 그리고 "b"에 "c"를 더할 때 또한 "bc"라는 String 객체가 다시 생성되어 str 변수에 담긴다. str 변수의 toUpperCase 메소드를 호출해도 str변수가 수정되지 않는 것을 볼 수 있다.

 

이제 여러 불변 객체를 만들어보자.

 

public final class User {
    private final String name;
    private final String password;
    public User(String name, String password){
        this.name = name;
        this.password = password;
    }
    // 수정을 해야 한다면 새로운 객체를 생성하여 리턴
    public User update(String name, String password){
        return new User(name, password);
    }
}

 

위 코드는 수정이 될 때 새로운 객체를 반환하여 불변을 유지하는 코드이다.

 

import java.util.ArrayList;
import java.util.List;
 
public final class User {
    private final List<Integer> list;
     
    public User(){
        list = new ArrayList<>();
    }
    public List<Integer> getList(){
        return Collections.unmodifiableList(list); // 방어적 복사본을 리턴한다.
//      return this.list;    이렇게 리턴하면 문제가 발생한다.
    }
 
    public static void main(String[] args) {
        User user = new User();
        List<Integer> list = user.getList();
        list.add(1); // 원하는 데로 에러가 발생하여 불변이 깨지지 않았다. 만약 getList()에서 this.list를 리턴한다면 에러없이 실행되 불변을 깨진다.
        list = user.getList();
         
        for(int i : list){
            System.out.println(i);
        }
    }
}

 

위 코드는 가변 객체를 담았을 때 방어적 복사본을 만드는 코드이다.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class User {
    protected final List<Integer> list;
     
    public User(){
        list = new ArrayList<>();
    }
    public List<Integer> getList(){
        return Collections.unmodifiableList(list);
    }
 
    public static void main(String[] args) {
        User user = new Member();
        List<Integer> list = user.getList();
        list.add(1);   // 에러가 없이 실행되기에 불변이 깨진다.
        list = user.getList();
         
        for(int i : list){
            System.out.println(i);
        }
    }
}
 
class Member extends User {
    @Override
    public List<Integer> getList(){
        return super.list;
    }
}
 
위 코드는 상속이 가능할 때 발생할 수 있는 문제점을 보여주는 코드이다.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public final class User {
    private final String name;
    private final List<Integer> list;
     
    public User(String name){
        this.name = name;
        list = new ArrayList<>();
    }
     
    public User update(String name){
            return new User(name);
    }
 
    public String getName(){
        return name;
    }
    public List<Integer> getList(){
        return Collections.unmodifiableList(list);
    }
}
 
위 코드는 최종적인 불변객체이다.
반응형