์์ ๋ณต์ฌ(Shallow Copy): ๊ฐ์ฒด์ 1์ฐจ ๋ ๋ฒจ ํ๋ ๊ฐ๋ง ์๋ก ๋ด๋๋ค. ์ฐธ์กฐ ํ์
ํ๋๋ ์ฃผ์(์ฐธ์กฐ)๋ง ๋ณต์ฌํ๋ค.
๊น์ ๋ณต์ฌ(Deep Copy): ์ค์ฒฉ ๊ฐ์ฒด๊น์ง ์ ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด ์ ์ฒด ๊ทธ๋ํ๋ฅผ ๋ณต์ ํ๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ์์ ๋ณต์ฌ๋ ๋ด๋ถ ์ฐธ์กฐ๊ฐ ๊ณต์ ๋๊ณ ๊น์ ๋ณต์ฌ๋ ๊ณต์ ๋์ง ์๋๋ค. ์๋ฅผ ๋ค์ด ์๋์ ๊ฐ์ ๋๋ฉ์ธ์ด ์์ ๋
class Address {
private String city;
public Address(String city) { this.city = city; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
}
class Person {
private String name; // ๋ถ๋ณ(๋ฌธ์์ด ์์ฒด๋ ๋ถ๋ณ)
private Address address; // ๊ฐ๋ณ ์ฐธ์กฐ ํ๋
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() { return name; }
public Address getAddress() { return address; }
}
์์ ๋ณต์ฌ๋ ์์ฑ์๋ ํฉํ ๋ฆฌ์์ ๋ด๋ถ ์ฐธ์กฐ๋ฅผ ๊ทธ๋๋ก ๋๊ธฐ๋ ๊ฒฝ์ฐ, clone()์ ๊ธฐ๋ณธ ๋์ ๋ฑ์ด ์๋ค.
Person p1 = new Person("Min", new Address("Seoul"));
// ์ ๊ฐ์ฒด๋ฅผ ์์ฑํ์์ง๋ง ๋ด๋ถ ์ฐธ์กฐ ๊ณต์
Person p2 = new Person(p1.getName(), p1.getAddress());
p1.getAddress().setCity("Busan");
System.out.println(p2.getAddress().getCity()); // Busan (๊ฐ์ด ๋ฐ๋)
๋ฐ๋ฉด ๊น์ ๋ณต์ฌ๋ ์ค์ฒฉ ๊ฐ์ฒด๊น์ง ์๋ก ๋ง๋๋ ๊ฒฝ์ฐ์ด๋ค.
Person p1 = new Person("Min", new Address("Seoul"));
// ๊น์ ๋ณต์ฌ
Person p4 = new Person(
p1.getName(),
new Address(p1.getAddress().getCity())
);
p1.getAddress().setCity("Busan");
System.out.println(p4.getAddress().getCity()); // Seoul (์ํฅ ์์)
์๋ฐ์ Cloneable / Object#clone()์ ์ญ์ฌ์ ์ผ๋ก ์ค๊ณ๊ฐ ๋ํดํ๋ค.
1. ์์ ๋ณต์ฌ๊ฐ ๊ธฐ๋ณธ: super.clone()์ ํ๋ ๋จ์ ๊ฐ ๋ณต์ฌ๋ฅผ ์ํํ๋ค. ์์ ํ์
์ ๊ฐ์ด ๋ณต์ฌ๋๊ณ ์ฐธ์กฐ ํ์
์ ์ฐธ์กฐ๋ง ๋ณต์ฌ๋๋ฏ๋ก ์์ ๋ณต์ฌ๊ฐ ๋๋ค. ๊ฒ๋ค๊ฐ ์์ฑ์๋ฅผ ๊ฑฐ์น์ง ์์ ๋ถ๋ณ์์ด ๊นจ์ง ์ํ์ด ์๋ค.
2. ์์ธ/๊ฐ์์ฑ ์ด์: ๋จ์ ๋ง์ปค ์ธํฐํ์ด์ค์ธ Cloneable์ ๊ตฌํํ์ง ์์ผ๋ฉด CloneNotSupportedException์ ๋์ง๋ฉฐ ๊ธฐ๋ณธ์ ์ผ๋ก protected๋ผ์ public์ผ๋ก ์ค๋ฒ๋ผ์ด๋ ํ์ง ์์ผ๋ฉด ์ธ๋ถ ์ฝ๋์์ ํธ์ถํ ์ ์๋ค. ๋ํ ํธ์ถํ๋ ์ธก์์๋ ๋ถํ์ํ๊ฒ ์ฒดํฌ ์์ธ๋ฅผ ์ฒ๋ฆฌํด์ผ ํ๋ค.
3. ์์ ํธ๋ฆฌ ๋ชจํธ์ฑ: ์ด๋ ๋ ๋ฒจ์์ ๊น๊ฒ/์๊ฒ ๋ณต์ฌํ ์ง๋ฅผ ๊ฐ์ ํ์ง ์์ ํ์
/ํ์ฅ ๊ณผ์ ์์ ์ผ๊ด์ฑ์ด ์ฝ๊ฒ ๊นจ์ง๋ค.
๋ฐ๋ผ์ clone()์ ์ฌ์ฉํ ๋๋ ๋ชจ๋ ๊ฐ๋ณ ์ฐธ์กฐ ํ๋๋ฅผ ์ง์ ๋ณต์ ํด์ผ ๊น์ ๋ณต์ฌ๊ฐ ๋๋ค.
// Cloneable ์์
class Address implements Cloneable {
private String city;
public Address(String city) { this.city = city; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
// public์ผ๋ก ์ค๋ฒ๋ผ์ด๋
@Override
public Address clone() {
// ์์ธ ์ฒ๋ฆฌ
try {
return (Address) super.clone(); // ๋ถ๋ณ/๋ฌธ์์ด ํ๋๋ง ์์ผ๋ฉด ์์
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
class Person implements Cloneable {
private String name;
private Address address; // ๊ฐ๋ณ ์ฐธ์กฐ
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
public Person clone() {
try {
Person copy = (Person) super.clone(); // ํ๋ ๋จ์ ๋ณต์ฌ
copy.address = address == null ? null : address.clone(); // ๊น์ ๋ณต์ฌ
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
์์ ๊ฐ์ ์ด์ ๋ก ์๋ฐ์์๋ ๊ฐ์ฒด ๋ณต์ฌ ์ ๋ ๊ถ์ฅ๋๋ ๋ฐฉ๋ฒ๋ค์ด ์กด์ฌํ๋ค. ์ฒซ ๋ฒ์งธ๋ก๋ ๋ณต์ฌ ์์ฑ์/์ ์ ํฉํ ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ด๋ค.
class Person {
private final String name;
private final Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address; // ์ฃผ์: ๊ทธ๋๋ก ๋์
ํ๋ฉด ์์ ๋ณต์ฌ
}
// ๋ณต์ฌ ์์ฑ์ (๊น์ ๋ณต์ฌ)
public Person(Person other) {
this.name = other.name; // String์ ๋ถ๋ณ
this.address = other.address == null ? null : new Address(other.address.getCity());
}
public static Person copyOf(Person other) {
return new Person(other);
}
}
*๊ฐ์ฅ ๋ช
์์ ์ด๊ณ ์์ ํ ๋ฐฉ์์ด๋ค.
๋ ๋ฒ์งธ๋ ์ง๋ ฌํ ๊ธฐ๋ฐ์ ๊น์ ๋ณต์ฌ์ด๋ค. Jackson, Gson, Java ์ง๋ ฌํ ๋ฑ์ผ๋ก ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํธ/ํธ๋ฆฌ๋ก ๋ฐ๊ฟจ๋ค๊ฐ ๋ค์ ์ฝ์ผ๋ฉด ๊น์ ๋ณต์ฌ๊ฐ ๋๋ค.
ObjectMapper om = new ObjectMapper();
// original ๊ฐ์ฒด๋ฅผ JSON ๋ฐ์ดํธ๋ก ์ง๋ ฌํ
byte[] bytes = om.writeValueAsBytes(original);
// JSON ๋ฐ์ดํธ๋ฅผ ๋ค์ Person.class๋ก ์ญ์ง๋ ฌํ -> ์๋ก์ด ๊ฐ์ฒด ์์ฑ
Person copy = om.readValue(bytes, Person.class);
๋น ๋ฅด๊ฒ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค๋ ์ ์ ์ฅ์ ์ด์ง๋ง ์ฑ๋ฅ ๋น์ฉ์ด ํฌ๋ค๋ ๋จ์ ๊ณผ ์ํ ์ฐธ์กฐ ๊ตฌ์กฐ์ ๊ฒฝ์ฐ JSON ์ง๋ ฌํ ์ StackOverflow๋ ๋ฌดํ ๋ฃจํ๊ฐ ๋ฐ์ํ ์ ์๋ค.
๋ํ ํ๋๊ฐ transient, @JsonIgnore ๋ฑ์ผ๋ก ์ง๋ ฌํ ๋์์์ ์ ์ธ๋๋ ๊ฒฝ์ฐ ๋ณต์ฌ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง ์ ์์ด ์ฃผ์ํด์ ์ฌ์ฉํด์ผ ํ๋ค.
์ธ ๋ฒ์งธ๋ก๋ ๋งคํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. MapStruct, ModelMapper ๋ฑ์ผ๋ก DTO์ ๋๋ฉ์ธ ๊ฐ์ ๋ณต์ฌ ๊ท์น์ ์ปดํ์ผ/๋ฐํ์์ ์์ฑํ๋ค.
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
PersonDto toDto(Person entity);
Person toEntity(PersonDto dto);
}
// ์ฌ์ฉ ์
Person p1 = new Person("Min", new Address("Seoul"));
PersonDto dto = PersonMapper.INSTANCE.toDto(p1);
// dto ์์ Address๋ ์ ์ธ์คํด์ค๋ก ๋งคํ๋์ด ๊น์ ๋ณต์ฌ
๋ฐ๋ณต๋๋ ๋งคํ ์ฝ๋๋ฅผ ์ค์ฌ ์์ฐ์ฑ๊ณผ ์ ์ง ๋ณด์์ฑ์ ๋์ผ ์ ์๊ณ ๊น์ ๋ณต์ฌ ๊ท์น์ ์ค์์์ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค. ๊ทธ๋ฌ๋ ๋จ์ ๋ณต์ฌ๋ง ํ์ํ ๊ฒฝ์ฐ์๋ ์ค๋ฒ ์์ง๋์ด๋ง์ด ๋ ์ ์๋ค.
new ArrayList<>(original)๋ ์์ ๋ณต์ฌ(์์ ์ฐธ์กฐ๋ง ๋ณต์ฌ)์ด๋ค. ๊น์ ๋ณต์ฌ๋ฅผ ์ํด์๋ ์์ ์์ฒด๋ฅผ ๋ณต์ฌํด์ผ ํ๋ค.
List<Person> src = ...; // ์๋ณธ ๋ฆฌ์คํธ
List<Person> deep = src.stream()
.map(Person::new) // Person ๋ณต์ฌ ์์ฑ์๋ฅผ ์ด์ฉํด ์ ๊ฐ์ฒด ์์ฑ
.toList(); // ์ ๋ฆฌ์คํธ๋ก ์์ง
List.copyOf(โฆ)/Set.copyOf(โฆ)๋ ๋ถ๋ณ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ค. ๋ฆฌ์คํธ ์์ฒด๋ ์๋ก ๋ง๋ค์ด์ง์ง๋ง ๋ด๋ถ ์์๋ ๊ทธ๋๋ก ์ฐธ์กฐ๋ง ๋ณต์ฌ๋๋ค. (์์ ๋ณต์ฌ)
List<String> list = new ArrayList<>();
list.add("Blue");
list.add("Cool");
List<String> copy = List.copyOf(list); // ๋ถ๋ณ ๋ฆฌ์คํธ + ์์ ๋ณต์ฌ
copy.add("c"); // UnsupportedOperationException ๋ฐ์
๋ฐฐ์ด์ arr.clone()์ ์ ๋ฐฐ์ด์ ๋ง๋ค์ง๋ง ์์๊ฐ ์ฐธ์กฐ ํ์
์ด๋ผ๋ฉด ์์ ๋ณต์ฌ์ด๋ค. ํ์ง๋ง ์์(primitive) ํ์
๋ฐฐ์ด์ ๊ฐ ๋ณต์ฌ์ด๋ฏ๋ก ๊ฒฐ๊ณผ์ ์ผ๋ก ๊น์ ๋ณต์ฌ์ฒ๋ผ ๋์ํ๋ค.
Address[] a1 = { new Address("Seoul") };
Address[] a2 = a1.clone(); // ๋ฐฐ์ด ๊ฐ์ฒด๋ ์๋ก ์์ฑ, ์์๋ ๊ฐ์ ์ฐธ์กฐ
์ธ๋ถ์ ๊ฐ๋ณ ๊ฐ์ฒด๋ฅผ ๋
ธ์ถํ ๋๋ ๋ฐ๋์ ๋ฐฉ์ด์ ๋ณต์ฌ๋ฅผ ๊ณ ๋ คํด์ผ ํ๋ค.
class Order {
private final Date createdAt; // Date๋ ๊ฐ๋ณ ํด๋์ค
public Order(Date createdAt) {
this.createdAt = new Date(createdAt.getTime()); // ์
๋ ฅ ๋ฐฉ์ด ๋ณต์ฌ
}
public Date getCreatedAt() {
return new Date(createdAt.getTime()); // ๋ฐํ ๋ฐฉ์ด ๋ณต์ฌ
}
}
*Instant, LocalDateTime ๊ฐ์ java.time ๊ณ์ด์ ๋ถ๋ณ์ด๊ธฐ ๋๋ฌธ์ ๋ฐฉ์ด ๋ณต์ฌ๊ฐ ๋ถํ์ํ๋ค.
๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ์ต๋ํ ๋ถ๋ณ์ผ๋ก ๋ง๋ค๋ฉด ์์/๊น์ ๋ณต์ฌ์ ๋ํ ๊ณ ๋ฏผ ์์ฒด๊ฐ ์ค์ด๋ ๋ค. ๋ด๋ถ ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์์ด ์ฐธ์กฐ๋ฅผ ๊ณต์ ํด๋ ์์ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์๋ฐ์ record๋ JDK 16๋ถํฐ ์ ์ ๋์
๋ ๋ฐ์ดํฐ ์ ์ฉ ํด๋์ค์ด๋ค. ๋ชจ๋ ํ๋๊ฐ ์๋์ผ๋ก final์ด ๋๋ฉฐ ์์ฑ์๋ก๋ง ๊ฐ์ด ์ค์ ๋๋ค. ๋ฐ๋ผ์ record ์์ฒด๊ฐ ๋ถ๋ณ์ ์ด๋ค.
public record PersonR(String name, AddressR address) {}
public record AddressR(String city) {}
// ์ฌ์ฉ ์
PersonR p1 = new PersonR("Min", new AddressR("Seoul"));
PersonR p2 = p1; // ๊ฐ์ ์ฐธ์กฐ๋ฅผ ์จ๋ ์์ ํ๋ค. (๋ถ๋ณ)
์ฃผ์ํ ์ ์ record์ ํ๋ ์์ฒด๋ final์ด์ง๋ง ์ฐธ์กฐ ํ์
ํ๋๊ฐ ๊ฐ๋ณ ๊ฐ์ฒด๋ผ๋ฉด ๋ด๋ถ์ ์ผ๋ก ์ฌ์ ํ ๋ฐ๋ ์ ์๋ค. ๋ฐ๋ผ์ record๋ฅผ ๋ถ๋ณ์ ์ผ๋ก ์ฐ๋ ค๋ฉด ํ๋ ํ์
๋ ๋ถ๋ณ ๊ฐ์ฒด๊ฐ ๋์ด์ผ ํ๋ค.
๋ ๊ฐ์ฒด๊ฐ ๊ฐ์ ์ค์ฒฉ ์ฐธ์กฐ๋ฅผ ๊ณต์ ํ๋ฉด ๊ทธ ๋ด๋ถ ์ํ๊ฐ ๋ณํ ๋ equals()๋ hashCode()์ ๊ฒฐ๊ณผ๊ฐ ๋ฐ๋ ์ ์๋ค.
class Address {
String city;
Address(String city) { this.city = city; }
// equals/hashCode city ๊ธฐ์ค์ผ๋ก ๊ตฌํํ๋ค๊ณ ๊ฐ์
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
class Person {
String name;
Address address;
// equals/hashCode: name + address ๊ธฐ์ค์ผ๋ก ๊ตฌํํ๋ค๊ณ ๊ฐ์
...
}
Person p1 = new Person("Min", new Address("Seoul"));
Person p2 = new Person(p1.name, p1.address); // ์์ ๋ณต์ฌ
Set<Person> set = new HashSet<>();
set.add(p1);
// ์ค์ฒฉ ๊ฐ์ฒด(Address) ๋ณ๊ฒฝ
p1.address.city = "Busan";
// p2.equals(p1)๋ ์ฌ์ ํ true (๊ฐ์ ์ฐธ์กฐ ๊ณต์ )
// set.contains(p1) -> false (hashCode๊ฐ ๋ฌ๋ผ์ง)
ํนํ HashSet, HashMap ๊ฐ์ ํด์ ๊ธฐ๋ฐ ์ปฌ๋ ์
์์ ์น๋ช
์ ์ธ ๋ฒ๊ทธ๋ก ์ด์ด์ง ์ ์๊ธฐ ๋๋ฌธ์ ๊น์ ๋ณต์ฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
