티스토리 뷰
프로그램에서 복사는 데이터의 복제본을 만드는 것을 의미하는데 많이 실수하는 것이 Shallow Copy와 Deep Copy이다, 특히 객체를 가지고 작업을 하는 경우 특히 주의해야 한다. 다음 코드를 보자 ( 코드는 setter, getter, equals를 작성해야 하지만 lombok 라이브러리를 사용하였다. )
@Getter
@Setter
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
class 서비스요소VO implements Cloneable {
private String 이벤트코드;
private String 요소코드;
private String 파라메터;
public 서비스요소VO(서비스요소VO p서비스요소VO) {
this.이벤트코드 = p서비스요소VO.get이벤트코드();
this.요소코드 = p서비스요소VO.get요소코드();
this.파라메터 = p서비스요소VO.get파라메터();
}
public static 서비스요소VO 서비스요소VOFactory(서비스요소VO p서비스요소VO) {
return new 서비스요소VO(p서비스요소VO);
}
public 서비스요소VO 서비스요소복제() {
try {
return (서비스요소VO) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
@SneakyThrows(JsonProcessingException.class)
public void ObjectCopy() {
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001002");
서비스요소VO 서비스요소VO_shallo = 서비스요소VO_01;
서비스요소VO_shallow.set파라메터("F00=001003");
boolean is서비스요소VO_shallow = 서비스요소VO_01.equals(서비스요소VO_shallow);
System.out.println("is서비스요소VO_shallow : " + is서비스요소VO_shallow);
서비스요소VO 서비스요소VO_Cloneable = 서비스요소VO_01.서비스요소복제();
서비스요소VO_Cloneable.set파라메터("F00=001004");
boolean is서비스요소VO_Cloneable = 서비스요소VO_01.equals(서비스요소VO_Cloneable);
System.out.println("is서비스요소VO_Cloneable : " + is서비스요소VO_Cloneable);
서비스요소VO 서비스요소VO_Constructor= new 서비스요소VO(서비스요소VO_01);
서비스요소VO_Constructor.set파라메터("F00=001004");
boolean is서비스요소VO_Constructor = 서비스요소VO_01.equals(서비스요소VO_Constructor);
System.out.println("is서비스요소VO_Constructor : " + is서비스요소VO_Constructor);
서비스요소VO 서비스요소VO_Factory = 서비스요소VO.서비스요소VOFactory(서비스요소VO_01);
서비스요소VO_Factory.set파라메터("F00=001004");
boolean is서비스요소VO_Factory = 서비스요소VO_01.equals(서비스요소VO_Factory);
System.out.println("is서비스요소VO_Factory : " + is서비스요소VO_Factory);
서비스요소VO 서비스요소VO_Lombok = 서비스요소VO_01.toBuilder().build();
서비스요소VO_Lombok.set파라메터("F00=001004");
boolean is서비스요소VO_Lombok= 서비스요소VO_01.equals(서비스요소VO_Lombok);
System.out.println("is서비스요소VO_Lombok : " + is서비스요소VO_Lombok);
ObjectMapper objectMapper = new ObjectMapper();
서비스요소VO 서비스요소VO_ObjectMapper = objectMapper
.readValue(objectMapper
.writeValueAsString(서비스요소VO_01), 서비스요소VO.class);
서비스요소VO_ObjectMapper.set파라메터("F00=001004");
boolean is서비스요소VO_ObjectMapper = 서비스요소VO_01.equals(서비스요소VO_ObjectMapper);
System.out.println("is서비스요소VO_ObjectMapper : " + is서비스요소VO_ObjectMapper);
}
@SneakyThrows : lombok 에서 제공하는 것으로 throws 나 try-catch 구문을 통해서 Exception에 대해 번거롭게 명시적으로 예외 처리를 해줘야 하는 경우에 @SneakyThrows 어노테이션을 사용하여 명시적인 예외 처리를 생략할 수 있다. 그러나 사용 시 주의를 해야 한다.(룸북의 공식 홈페이지에서는 이 어노테이션은 논쟁의 여지가 있어 사용 시 신중하게 사용해야 한다). 해당 어노테이션을 없애면 다음과 같이 작성해야 한다.
ObjectMapper objectMapper = new ObjectMapper();
서비스요소VO 서비스요소VO_ObjectMapper = null;
try {
서비스요소VO_ObjectMapper = objectMapper.readValue(
objectMapper.writeValueAsString(서비스요소VO_01), 서비스요소VO.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
1. Shallow Copy
서비스요소VO 서비스요소 VO_shallow = 서비스요소VO_01; 구문은 서비스요소VO_shallow에 서비스요소_01 값은 복사하여 틀린 값이라고 생각하지만 실제는 주소를 복사하는 것으로 같은 값이 되는 것으로 주소만 복사하는 것을 복사(Shallow Copy)이다
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001002");
서비스요소VO 서비스요소VO_shallow = 서비스요소VO_01;
서비스요소VO_shallow.set파라메터("F00=001003");
boolean is서비스요소VO_shallow = 서비스요소VO_01.equals(서비스요소VO_shallow);
System.out.println("is서비스요소VO_shallow : " + is서비스요소VO_shallow);
수행 결과를 보면 서비스요소VO_01과 서비스요소VO_shallow는 주소 10450으로 같은 주소를 가지고 있어서 파라미터 값을 "F00=001003"으로 변경 시 서비스요소VO_01과 서서비스요소 VO_shallow이 모두 변경이 된다.
2. Deep Copy
Shallow Copy와 틀리게 일반적으로 복사를 생각하는 것처럼 변경한 객체의 데이터를 변경해도 원본의 데이터 변경이 없는 것을 Deep Copy라 한다. 즉 새로운 주소에 값을 변경하는 것으로 자바에서는 직접 구현하는 방법과 라이브러리를 이용하는 방법이 있다.
직접 구현하는 방법
- Cloneable을 상속받아 clone() 메서드를 재정의 : Cloneable Interface를 사용하는 방법으로 Cloneable을 상속받아 clone() 메서드를 재정의하는 방법으로 하위 객체는 지원하지 않는 방법으로 추천하지 않는 방법이다.
- 생성자에 자기 자신의 객체를 파라미터로 받아 필드를 재정의 : Copy Constructor과 Copy Factory를 이용하는 방법으로 구현 방식에 차이가 있다.
2-1. Cloneable Interface
@Getter
@Setter
class 서비스요소VO implements Cloneable {
public 서비스요소VO 서비스요소복제() {
private String 이벤트코드;
private String 요소코드;
private String 파라메터;
public 서비스요소VO 서비스요소복제() {
try {
return (서비스요소VO) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
public void ObjectCopy() {
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001003");
서비스요소VO 서비스요소VO_Cloneable = 서비스요소VO_01.서비스요소복제();
서비스요소VO_Cloneable.set파라메터("F00=001004");
boolean is서비스요소VO_Cloneable = 서비스요소VO_01.equals(서비스요소VO_Cloneable);
System.out.println("is서비스요소VO_Cloneable : " + is서비스요소VO_Cloneable);
}
클래스 작성 시 Cloneable를 상속받고 ( implements Cloneable ) 서비스요소VO 서비스요소복제()와 같은 메서드를 만들어야 한다. 그러고 나서 만든 서비스요소복제() 메서드를 사용하여 다른 변수에 할당한다. 수행하면 틀린 주소 서비스요소VO_01는 10450, 서비스요소 VO_Cloneables는 10458에 할당되어 서비스요소 VO_Cloneables의 값을 변경해도 값이 다르게 된다.
2-2. Copy Constructor
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode
class 서비스요소VO {
private String 이벤트코드;
private String 요소코드;
private String 파라메터;
public 서비스요소VO(서비스요소VO p서비스요소VO) {
this.이벤트코드 = p서비스요소VO.get이벤트코드();
this.요소코드 = p서비스요소VO.get요소코드();
this.파라메터 = p서비스요소VO.get파라메터();
}
}
public void ObjectCopy() {
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001003");
서비스요소VO 서비스요소VO_Constructor= new 서비스요소VO(서비스요소VO_01);
서비스요소VO_Constructor.set파라메터("F00=001004");
boolean is서비스요소VO_Constructor = 서비스요소VO_01.equals(서비스요소VO_Constructor);
System.out.println("is서비스요소VO_Constructor : " + is서비스요소VO_Constructor);
}
서비스요소 VO을 받는 생성자를 만들어서 사용하는 것으로 옆 이미지를 보면 서비수요소 VO_01, 서비스요소 VO_Constructor의 주소가 각각 10450, 10463으로 값울 변경해도 영향을 받지 않는다,.
서비수요소 VO_01.get파라미터() : F00=001003
서비스요소 VO_Constructor.get파라미터() : F00=001004
2-3. Copy Factory
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode
class 서비스요소VO {
private String 이벤트코드;
private String 요소코드;
private String 파라메터;
public 서비스요소VO(서비스요소VO p서비스요소VO) {
this.이벤트코드 = p서비스요소VO.get이벤트코드();
this.요소코드 = p서비스요소VO.get요소코드();
this.파라메터 = p서비스요소VO.get파라메터();
}
public static 서비스요소VO 서비스요소VOFactory(서비스요소VO p서비스요소VO) {
return new 서비스요소VO(p서비스요소VO);
}
}
public void ObjectCopy() {
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001003");
서비스요소VO 서비스요소VO_Factory = 서비스요소VO.서비스요소VOFactory(서비스요소VO_01);
서비스요소VO_Factory.set파라메터("F00=001004");
boolean is서비스요소VO_Factory = 서비스요소VO_01.equals(서비스요소VO_Factory);
System.out.println("is서비스요소VO_Factory : " + is서비스요소VO_Factory);
}
기본적으로 자신의 객체를 받는 기본 생성자를 생성 후 static 메서드를 생성하여 객체를 생성하는 방법으로 Copy Constructor와 동일한 한다, static으로 메서드를 생성하는 것은 프로그램 어느 곳에서도 접근이 가능하게 하기 때문이다,
서비수요소VO_01.get파라메터() : F00=001003
서비스요소VO_Factory.get파라메터() : F00=001004
라이브러리를 이용하는 방법
- lombok의 toBuilder 사용
- ObjectMapper
- MapStruct
2-4. lombok의 toBuilder 사용
lombok을 사용하기 위해서는 Maven Repository에서 의존성 주입을 해아 한다.
<dependency> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
@Getter
@Setter
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
class 서비스요소VO {
private String 이벤트코드;
private String 요소코드;
private String 파라메터;
}
public void ObjectCopy() {
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001002");
서비스요소VO 서비스요소VO_Lombok = 서비스요소VO_01.toBuilder().build();
서비스요소VO_Lombok.set파라메터("F00=001004");
boolean is서비스요소VO_Lombok= 서비스요소VO_01.equals(서비스요소VO_Lombok);
System.out.println("is서비스요소VO_Lombok : " + is서비스요소VO_Lombok);
}
lombok toBuilder를 사용하기 위해서는 모든 멤버 변수를 받는 생성자라 필요해서 @AllArgsConstructor를 사용했으며, toBuilder를 사용하기 위해서 @Builder(toBuilder = true)를 사용한다,
서비스요소VO_01.toBuilder().build()로 변수에 할당하면 된다,.
서비수요소VO_01.get파라메터() : F00=001003
서비스요소VO_Lombook.get파라메터() : F00=001004
2-5. ObjectMapper
ObjectMapper을 사용하기 위해서는 Maven Repository에서 의존성 주입을 해아 한다.
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
@Getter
@Setter
@EqualsAndHashCode
class 서비스요소VO implements Cloneable {
private String 이벤트코드;
private String 요소코드;
private String 파라메터;
}
@SneakyThrows(JsonProcessingException.class)
public void ObjectCopy() {
서비스요소VO 서비스요소VO_01 = new 서비스요소VO();
서비스요소VO_01.set이벤트코드("ACT");
서비스요소VO_01.set요소코드("FTR000001");
서비스요소VO_01.set파라메터("F00=001002");
ObjectMapper objectMapper = new ObjectMapper();
서비스요소VO 서비스요소VO_ObjectMapper = objectMapper
.readValue(objectMapper
.writeValueAsString(서비스요소VO_01), 서비스요소VO.class);
서비스요소VO_ObjectMapper.set파라메터("F00=001004");
boolean is서비스요소VO_ObjectMapper = 서비스요소VO_01.equals(서비스요소VO_ObjectMapper);
System.out.println("is서비스요소VO_ObjectMapper : " + is서비스요소VO_ObjectMapper);
}
ObjectMapper 객체를 생성하고 사용한다.
서비수요소VO_01.get파라메터() : F00=001003
서비스요소VO_ObjectMapper.get파라메터() : F00=001004
2-6. MapStruct
MapStruct을 사용하기 위해서는 Maven Repository에서 의존성 주입을 해아 한다.
<dependency>
<groupId>org.mapstruct</groupId>
<version>1.5.3.Final</version>
</dependency>
lombok과 같이 사용하는 경우 다음과 같이 pom.xml을 수정 해야 한다.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.3.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
mapper interface를 만든다.
@Mapper
public interface CopyMapper {
CopyMapper copyMapper = Mappers.getMapper(CopyMapper.class);
SvcFtrVO toSvcFtrVO(SvcFtrVO fromSvcFtrVO);
}
Mapstruct 인터페이스는 compile과정에서 구현체가 자동으로 생성된다,
public void MapStructCopy() {
SvcFtrVO svcFtrVO = SvcFtrVO.builder()
.이벤트코드("ACT")
.요소코드("FTR000001")
.파라메터("F00=001003")
.build();
SvcFtrVO svcFtrVO_01 = CopyMapper.copyMapper.toSvcFtrVO(svcFtrVO);
svcFtrVO_01.set파라메터("F00=001004");
boolean isSvcFtrVO_01 = svcFtrVO.equals(svcFtrVO_01);
System.out.println("isSvcFtrVO_01 : " + issvcFtrVO_01);
}
Mapstruct 인터페이스를 생성한 후 생성된 인터페이스(컴파일 시 구현채 생성됨)를 사용해서 변수애 할당 한다.,
svcDtrVO.get파라메터() : F00=001003
svcDtrVO_01r.get파라메터() : F00=001004
'프로그램이야기' 카테고리의 다른 글
메시지 브로커 - RabbitMQ (0) | 2024.06.05 |
---|---|
메세지 서비스 (0) | 2024.05.29 |
프로그램이야기 일곱째 - 캡슐화 (0) | 2023.02.18 |
프로그램이야기 여섯번째 - 함수 (0) | 2023.01.22 |
프로그램이야기 다섯번째 - 반복문 (0) | 2023.01.19 |