2025. 2. 25. 22:50ㆍLanguages/JAVA

💻 코드를 작성하면서 하기 쉬운 실수 중 하나가, 참조 복사가 되었는데... 값이 복사가 되었다고 생각할 때라고 생각합니다. 코드를 작성할 때는, 내가 원하는 것처럼 결과가 나올 것이야~~ 라고 생각할 겁니다. 하지만 막상 코드 한 줄 한 줄이 실행되고, 이후 마지막에 확인한 결과는, 놀랍게도 예측과 너무나도 다릅니다.
값 복사? 참조 복사?
아래의 코드를 봤을 때는, 어떤 생각이 드시나요? x와 y를 이용하되, 중간에 값을 변경하려고 시도해보는구나! 라고 느끼실 겁니다.
int x = 50;
int y = x;
x = 30;
//x와 y를 출력하면, x는 30 y는 50이 출력된다!
위의 코드는 어떤 복사일까요? 정답은 '값 복사'입니다. 기본형이기에 값을 복사하게 된 것이지요!
그럼 기본형은 무엇일까요? JAVA가 다루는 Data Type으로 기본(Primitive)형과 참조(Reference)형이 있습니다.
먼저, 기본형의 경우 메모리에 '값'을 직접 저장하게 됩니다. 이런 기본형의 예로는 int, long, char 등이 있습니다. 기본형의 경우 값만을 복사하기에, 복사 이후에는 서로 영향을 주지 않습니다.
반면, 참조형의 경우 값이 저장된 주소를 값으로 가지게 됩니다. 따라서 해당 주소에 저장된 값이 바뀌면, 그 값을 공유하는 참조형 변수들의 값이 전부 바뀝니다.
예를 들어 아래의 코드를 실행하는 경우, 참조형이기에 값이 바뀜을 확인할 수 있었습니다.
public static void main(String[] args) {
Apple apple = new Apple(123);
Apple appleTwo = apple;
appleTwo.number = 456;
System.out.println(apple.number); //456
System.out.println(appleTwo.number); //456
}
static class Apple {
int number;
Apple(int number) {
this.number = number;
}
}
변하지 않는 변수(불변 변수)를 활용한다면?
위에서 언급한 기본형 또는 참조형의 특징을 잘 기억하며 코드를 작성해야 합니다. 그런데 참조형의 경우, 참조하는 주소의 값을 고정할 수는 없을까요? 고정하기를 원한다면, final 즉 변하지 않을 변수(불변)로 설정해주면 됩니다.
public class Sample01 {
public static void main(String[] args) {
Apple appleA = new Apple("red");
System.out.println(appleA.getColor());
Apple appleB = appleA;
System.out.println(appleB.getColor());
}
static class Apple {
private final String color;
Apple(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
}
위의 코드는 final이 있어서 setter(this.color = color)를 이용하여 직접적인 변경은 불가능합니다. 다만 조금 더 안전하게 설계하고 싶다면, 처음에 변하지 않는 객체를 설정하고 난 뒤, 어떤 로직이 진행될 때마다 새로운 객체(인스턴스)를 return 해주면 됩니다.
public class Sample01 {
public static void main(String[] args) {
Apple appleA = new Apple("red");
System.out.println(appleA.getColor()); //red
Apple appleB = appleA.reviseColor("blue");
System.out.println(appleB.getColor()); //blue
}
static class Apple {
private final String color;
Apple(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public Apple reviseColor(String color) {
return new Apple(color);
}
}
}
다만, color를 수정할 때마다 인스턴스가 무한정으로 생길 수 있어서... 메모리에 큰 부담을 줄 수 있습니다. 따라서 이 경우에는 같은 값을 가진 경우에는 동일한 인스턴스를 return 해주게끔 코드를 작성해주면 됩니다.
public class Sample01 {
public static void main(String[] args) {
Apple appleA = new Apple("red");
Apple appleB = appleA.reviseColor("blue");
Apple appleC = appleA.reviseColor("red");
System.out.println(appleA.toString()); //Sample.Sample01$Apple@1b28cdfa
System.out.println(appleB.toString()); //Sample.Sample01$Apple@eed1f14
System.out.println(appleC.toString()); //Sample.Sample01$Apple@1b28cdfa
}
static class Apple {
private final String color;
Apple(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public Apple reviseColor(String color) {
if(this.color.equals(color)) {
return this;
}
return new Apple(color);
}
}
}변하는 변수(불변 X)를 활용하되, 같은 인스턴스를 활용한다면?
반대로 인스턴스를 계속 만드는 것이 부담이 되기 때문에, 하나의 인스턴스를 지속적으로 활용해야 한다면... 어떻게 해야 할까요?
public class Sample01 {
public static void main(String[] args) {
List<String> sList = new ArrayList<>();
Apple apple = new Apple("green");
int flagNum = 0;
boolean flag = true;
while(flag) {
if(flagNum == 0) apple.setColor("red");
if(flagNum == 1) apple.setColor("yellow");
if(flagNum == 2) apple.setColor("blue");
sList.add(apple.getColor());
System.out.println(apple); //동일한 객체가 출력됨을 확인
apple.resetColor();
//멈추기 위한 조건(예)
if(flagNum == 2) flag = false;
flagNum++;
}
System.out.println("============================");
sList.stream().forEach(System.out::println); // red yellow blue
}
static class Apple {
private String color;
private final String defaultColor;
Apple(String color) {
this.color = color;
this.defaultColor = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void resetColor() {
this.color = defaultColor;
}
}
}
변할 수 있는 변수(color)와 변하지 않는 변수(defaultColor)를 설정해주면 될 것 같습니다. 처음 생성자를 만들 때, defaultColor를 설정해두고, reset할 때 해당 변수를 이용하는 겁니다!
출력해보면 새로운 객체가 아닌, 동일한 객체를 활용함을 확인할 수 있었습니다. 또한 중간에 List<String> sList를 만들기 위해서 가져왔던 색들이 다 다름을 확인할 수도 있었습니다!!!
추가로, String?
String은 기본형이 아니라, 참조형입니다. 하지만 놀랍게도 String은 불변 객체이기도 합니다. String constant pool(문자열 풀)에 값을 저장한 뒤, 해당 주소 값을 참조하게 된다고 합니다. 따라서 동일한 문자열 리터럴인 경우, 새로운 객체를 생성하는 것이 아니라 기존 객체를 재사용할 수 있습니다.
따라서 String에 문자를 변형하게 되면, 불변 객체의 특성상 새로운 객체가 계속 생성될 수 있습니다. 새로운 객체가 너무 많이 생성되는 것이 부담된다면, 가변 객체인 StringBuilder를 사용하거나 멀티 스레드 상황에서 활용 가능한 StringBuffer가 대안이 될 수 있습니다
| 타입 | 불변 객체 | 복사 시 동작 |
| 기본형 | O | 값 복사 |
| 참조형 | X | 주소 복사 |
| 참조형(String) | O | 새로운 객체 생성(값 복사) |
공부하며 작성 중이기에 틀린 부분이 있을 수 있습니다. 댓글로 남겨주시면 수정 반영하겠습니다.
References
1. Java의 String 이야기(1) - String은 왜 불변(Immutable)일까?
'Languages > JAVA' 카테고리의 다른 글
| [JAVA] TOCTOU : Time of Check to Time of Use (0) | 2025.07.28 |
|---|---|
| [JAVA] Time (1) | 2025.02.20 |
| [JAVA] Stream++ (1) | 2024.10.05 |
| [JAVA] Optional (2) | 2024.09.08 |
| [JAVA] max() (0) | 2024.04.04 |