스프링부트에서 로그인기능을 만드는 과정에 유저 객체를 생성하고 아무생각 없이 해당 객체를 복사하여 정보를 수정 하였더니 원래의 정보까지 변경되어 버리는 참사가 일어났다..
User user = new User();
User persistenceUser = user;
위의 코드에서 user
와 persistenceUser
인스턴스는 같은 주소를 가리킨다. 때문에 둘 중 하나의 참조 변수를 수정하더라도 두 변수 모두 변경이 일어나게 된다.
해당 문제가 발생하는 이유는 user
변수가 참조 타입 변수이기 때문이다.
User
의 인스턴스를 생성하고 인스턴스의 주소를 user
변수가 참조하고 있는 것이다. 즉, 값!이 아닌 주소 값!을 가지고 있다는 것. 그렇다면 user
의 값을 복사한 persistenceUser
변수는 user
가 가리키는 주소값을 가리키게 된다는 것이다. 결국 두 변수는 이름만 다를 뿐 동일한 녀석이라는 뜻,,
얕은 복사와 깊은 복사
자바에서는 객체의 복사를 얕은 복사와 깊은 복사로 구분한다.
얕은 복사는 객체의 메모리 주소를 복사하는 것이고, 깊은 복사는 객체 자체를 복사하는 것이다.
얕은 복사 (Shallow Copy)
얕은 복사는 객체를 복사할 때, 객체 내부에 있는 참조 변수들의 메모리 주소만 복사하는 방식 이다. 복사된 객체와 원본 객체는 같은 객체를 참조하게 된다. 따라서, 복사된 객체를 수정하면 원본 객체도 함께 수정된다.
public class Person {
private String name;
private Car car;
public Person(String name, Car car) {
this.name = name;
this.car = car;
}
public void setCar(Car car) {
this.car = car;
}
public Car getCar() {
return car;
}
}
public class Car {
private String brand;
public Car(String brand) {
this.brand = brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getBrand() {
return brand;
}
}
Car car = new Car("Benz");
Person person1 = new Person("John", car);
Person person2 = person1; // 얕은 복사
person2.setCar(new Car("BMW")); // person2의 차량 변경
System.out.println(person1.getCar().getBrand()); // BMW 출력
위 예제에서 person2
는 person1
을 얕은 복사한 객체 이다. person2
의 차량을 변경하면 person1
의 차량도 함께 변경되는 것을 확인할 수 있다.
그렇다면 같은 정보를 가지고 있지만 서로 다른 메모리를 참조하는 객체를 만드려면 어떨게 해야 할까? 깊은 복사를 사용하면 된다.
깊은 복사 (Deep Copy)
깊은 복사는 객체를 복사할 때, 객체 내부에 있는 참조 변수들이 참조하는 객체까지 복사하는 방식 이다. 복사 대상의 참조 변수를 사용하여 새로운 인스턴스를 생성하는 것이라고 생각하면 편하다.
복사된 객체와 원본 객체는 서로 다른 객체를 참조하게 된다. 따라서, 복사된 객체를 수정해도 원본 객체는 영향을 받지 않는다.
// 깊은 복사 예제
Car car = new Car("Benz");
Person person1 = new Person("John", car);
// 깊은 복사
Person person2 = new Person(person1.getName(), new Car(person1.getCar().getBrand()));
person2.getCar().setBrand("BMW"); // person2의 차량 변경
System.out.println(person1.getCar().getBrand()); // Benz 출력
위 예제에서 person2
는 person1
을 깊은 복사한 객체이다. new 키워드를 사용하여 새로운 인스턴스를 생성한다고 선언한 후 person1
의 참조변수의 값을 생성될 인스턴스의 값으로 전달한다.
person2
의 차량을 변경해도 person1
의 차량은 변경되지 않는 것을 확인할 수 있다.
문제는 객체의 멤버변수가 여러개 존재할 경우 생성자를 통해 모두 입력해주어야 하니,, 코드가 길어 질 수도 있다는 단점이 존재한다.
Class 클래스의 newInstance() 메서드를 통한 객체 생성을 통해 더 간결한 깊은 복사가 가능하다
나는 웹 서버를 개발하면서 카카오 로그인 기능 구현 시 깊은 복사를 사용한 적이 있다. (깊은 복사 방식은 불필요한 코드가 많아져 리팩터링 되었다..)
카카로 로그인 시 회원정보가 DB에 존재하지 않는다면 자동으로 회원가입 후 로그인이 되는 기능이었다. 카카오 로그인 API에 요청을 보내 사용자 정보를 받아와 일반 회원가입에 사용되는 User 객체의 인스턴스를 만들었고 이를 회원가입 로직으로 보내는 간단한 과정이었으나.
문제는 회원가입 로직에서 전달받은 User 객체를 복사하고 비밀번호를 해시암호화하여 setter로 변경 후 DB에 저장한다는 것이었다.
그렇다면 무슨일이 일어날까 자동 회원가입 이후 로그인 시 해시암호화된 비밀번호를 로그인 로직에 넘겨주게 되어서 오류가 발생했다. 이 때 깊은 복사를 사용하여 문제를 해결할 수 있었다.
'JAVA' 카테고리의 다른 글
[JAVA] Runtime Constant Pool? 자바는 상수와 문자열을 어떻게 변수에 저장할까 (0) | 2023.03.24 |
---|---|
[JAVA] 상속 (Inheritance) - Is A (0) | 2023.03.23 |
[JAVA] 싱글톤 패턴 (Singleton pattern) (0) | 2023.03.22 |
[JAVA] Static (1) | 2023.03.22 |
[JAVA] 객체 간의 협력(Collaboration)과 인터페이스(Interface) (0) | 2023.03.22 |