[CS] 방어적 복사

말하는 감자·2025년 1월 16일

CS

목록 보기
13/33
post-thumbnail

참조 타입

정의

: 변수의 값으로 실제 값이 아닌 값이 저장된 메모리의 주소를 가지는 타입

종류

클래스
인터페이스
배열
열거형(Enum)

주의점

참조 타입을 클래스의 인스턴스 변수로 사용 시, 사이드 이펙트 발생 우려

사이드 이펙트

  1. 공유 상태: 참조 타입의 인스턴스 변수를 여러 객체가 공유할 경우, 한 객체에서 해당 변수를 수정하면 다른 객체에도 그 변경이 반영
  2. 불변성 유지 어려움: 어떤 객체가 다른 객체의 상태를 변경할 수 있게 되는 순간 불변성 유지가 어려워짐

방어적 복사

원본과의 참조를 끊은 복사본을 만들어 사용하는 방식으로, 원본의 변경에 의한 복사본의 예상치 못한 변경을 방지하여 안전한 코드를 만들 수 있도로 도움

예시

class Garage {
    private List<Car> cars;

    public Garage(List<Car> cars) {
        // 방어적 복사: 전달받은 리스트의 복사본을 생성하여 저장
        this.cars = new ArrayList<>(cars);
    }

    public List<Car> getCars() {
        // 방어적 복사: 내부 리스트의 복사본을 반환
        return new ArrayList<>(cars);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Car> initialCars = new ArrayList<>();
        initialCars.add(new Car("Toyota"));
        initialCars.add(new Car("Honda"));

        Garage garage = new Garage(initialCars);

        // garage에서 반환된 리스트에 Car를 추가
        List<Car> carsFromGarage = garage.getCars();
        carsFromGarage.add(new Car("Ford")); // 원래 Garage의 cars에 영향을 미치지 않음
        System.out.println("Original garage cars: " + garage.getCars().size()); // 2
        System.out.println("Cars from garage: " + carsFromGarage.size()); // 3
    }
}

  • 전달받은 리스트를 이용하여 새로운 리스트(복사본)을 생성
  • 반환할 때도 복사본을 반환
  • carsFromGarage에 저장되는 리스트는 원본이 아닌 복사본.
  • 즉, 새로운 Car(Ford)를 추가해도 원본에는 영향을 전혀 미치지 않는다.
System.out.println("Original garage cars: " + garage.getCars().size()); 
// 2
  • garage.getCars()로 가져오는 리스트는 원본 즉, size는 2
System.out.println("Cars from garage: " + carsFromGarage.size()); // 3
  • carsFromGarage는 복사본(Ford가 추가된)이므로 size는 3

why?

복사본(carsFromGarage)과 원본(initialCars)가 서로 다른 메모리 주소를 참조

캡슐화

하지만, 아직 외부에서 initialCars에 대한 수정 가능성이 열려있는 상태
-> 외부에서 새로운 Car객체를 추가하면 initialCars의 크기는 변경됨
-> 방지: 캡슐화

class Garage {
    private List<Car> cars;

    public Garage(List<Car> cars) {
        // 방어적 복사 적용
        this.cars = new ArrayList<>(cars);
    }

    public List<Car> getCars() {
    	//this.cars = new ArrayList<>(cars);
        // 불변 리스트 반환으로 캡슐화 강화
        return Collections.unmodifiableList(new ArrayList<>(cars));
    }

    // 리스트를 조작할 수 있는 메서드를 통해 변경을 관리
    public void addCar(Car car) {
        cars.add(car);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Car> initialCars = new ArrayList<>();
        initialCars.add(new Car("Toyota"));
        initialCars.add(new Car("Honda"));

        Garage garage = new Garage(initialCars);

        // 제공된 메서드를 통해서만 수정 가능
        garage.addCar(new Car("Ford"));
        System.out.println("Garage cars: " + garage.getCars().size()); // 3
        // getCars()가 반환하는 리스트는 복사본이므로 외부 수정이 내부에 영향을 주지 않음
        List<Car> carsCopy = garage.getCars();
        carsCopy.add(new Car("BMW")); // 이 변경은 Garage의 cars에 영향 없음
		  System.out.println("Garage cars after external modification: " + garage.getCars().size()); // 3
        System.out.println("CarsCopy size after adding BMW: " + carsCopy.size()); // 4
    }   
}

  • initialCars: Toyota, Honda
  • garage를 호출하여 지정된 메서드(addCar)를 통해 Ford를 추가 -> 복사본임
  • 크기는 3
  • carsCopy는 Toyota,Honda에 Ford가 추가된 복사본
  • 리스트 carsCopy에 BMW를 추가하지만, 이 변경은 Garage의 cars에는 아무런 영향을 주지 않음

    carsCopy는 4를 반환하지만

    garage의 cars는 변경되지 않은 3을 반환
profile
주니어개발자(?)

0개의 댓글