일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 개념
- leetcode
- error
- Kakao
- siver3
- 9252
- leetcode 69
- gold5
- java
- LEVEL2
- LCS
- 구현
- 오류
- Gold4
- CSS
- AWS
- jpa
- 백준
- 배포
- Thymeleaf
- mysql
- LEVEL1
- glod4
- PYTHON
- 프로그래머스
- glod5
- gold2
- spring
- 백엔드
- HTML
- Today
- Total
이 험난한 세상에서어어~
[java] 당신의 객체에 값이 없는 이유 (저장되는 것은 값이 아니라 객체의 주소다.) 본문
개인 프로젝트를 진행하던 어느날...
나는 리스트에 있는 값들을 특정한 범위 만큼 잘라서 map에 넣고 싶어졌다. 그래서 나는 아무렇지 않게 아래처럼 코드를 작성했다.
List<Pair> list = new ArrayList<>();
list.add(new Pair(1, 2));
list.add(new Pair(2, 3));
list.add(new Pair(3, 4));
list.add(new Pair(4, 5));
int count = 0;
Map<Integer, List<Pair>> map = new HashMap<>();
List<Pair> tmp = new ArrayList<>();
for (int i=0; i<list.size(); i++) {
tmp.add(list.get(i));
if (tmp.size() == 2) {
map.put(count, tmp);
tmp.clear();
count++;
}
}
나는 생각했다. 음, 나름대로 돌아가긴 하겠군. 그리고 실행을 해봤는데..! 해봤는데...!
띠용... 값이 하나도 안 들어가 있는 거 아닌가?
이게 뭐지... 싶어서 아래와 같이 위의 코드를 변경해줬다.
for (int i=0; i<list.size(); i++) {
tmp.add(list.get(i));
if (tmp.size() == 2) {
map.put(count, tmp);
tmp = new ArrayList<>();
//tmp.clear();
count++;
}
}
그랬더니 값이 멀쩡하게 나왔다.
이유가 뭐야?
clear()를 해주면 값이 다 날아가고 new 로 새로운 객체를 생성하면 값이 남아 있는 이유가 뭘까?
tmp에 있는 값들을 map에 저장해 주고 tmp는 비워줬으니까. 순서대로 가면 map에는 당연히 내가 아까 넣어줬던 값이 남아 있어야 하는 거 아닌가?
그런데 왜 clear를 쓰면 값이 다 날라가지?
Java의 변수들은 새로운 객체에 들어가는 걸 좋아하는 걸까? 객체지향이 세상을 본떴다고 했던데, 새집 선호 경향까지 적용할 줄은...
그건 객체의 대입 방식을 제대로 파악하지 못했기에 일어나는 문제였다.
새집 어쩌고의 문제가 아니였다. 이러한 문제는 바로 객체가 대입되는 방식에서 비롯된 문제였다.
우리는 int와 같은 변수를 선언하고자 할 때 'int i = 5'라는 방식을 이용한다. 이렇게 되면 메모리에 4바이트 정도 되는 공간이 변수 i에게 할당이 되고 5라는 값이 들어가가 된다. 그렇기 때문에 'int i = 5', 'int j = i', 'int i = 4' 을 차례대로 실행해도 j에는 값이 5로 남아있는 것이다.
하지만, 객체를 '=' 하게 되면 값이 아닌 주소가 들어가게 된다.
아래와 같은 코드가 있다고 하자. list1에는 i를 0부터 4까지 해서 new Pair(i, i)로 넣어줬다. 그리고 'list2 = list1'로 하여 list1의 값을 list2로 넣어준 뒤, list1의 첫 번째 값을 new Pair(10, 10)으로 바꿔줬다고 해보자. 그렇다면 list2의 첫 번째 값은 뭐가 나올까? (0, 0)? 아니면 (10, 10)?
List<Pair> list1 = new ArrayList<>();
List<Pair> list2 = new ArrayList<>();
for (int i=0; i<5; i++) {
list1.add(new Pair(i, i));
}
list2 = list1;
list1.set(0, new Pair(10, 10));
System.out.println(list2.get(0).a + " " + list2.get(0).b);
답은 10, 10이다.
여기서 우리가 조심해야 할 부분은 바로 'list2 = list1'이다. 언뜻 보기에는 list1의 값을 list2에 넣어주는 것처럼 보일 수 있다. 그러나 아니다! list2에는 list1의 주소가 들어가 있다.
'deep copy'와 'shallow copy'란 말을 들어본 적이 있을 것이다. 'deep copy'의 경우에는 그 객체에 있는 값을 모조리 가지고 오는 것이지만, 'shallpw copy'는 객체의 주소를 가지고 오는 것이다.
그러므로 'list2' = 'list1'를 해주면 'list2'에는 'list1'의 주소가 복사되는 것이다! 그러므로 list1의 값을 바꿔주면 해당 주소와 연결되어 있는 list2의 값도 바뀌어서 나오는 것이다...
아래 출력된 list1의 주소와 list2의 값을 비교해보면 더 쉽게 이해할 수 있다.
그렇기에 겉보기에는 list1과 list2가 서로 독립되어 존재하는 것 같지만, 실은 서로 연결되어 있다. 참고로 저렇게 주소 값이 들어간 이유는 내가 list1에 객체 Pair를 넣어줬기 때문이다.
Map에 put한 tmp도 이러한 원리이다.
그렇기에 이제 내가 맞닥뜨렸던 첫 번째 예시로 올라가보자.
리스트에 있는 값을 tmp라는 리스트에 넣다가 특정한 조건이 나오면 해당 tmp를 map에 넣어주고 tmp를 전부 지운 다음(clear()) 다시 리스트의 값을 tmp에 넣는 방식이다.
이제 생각해 보자. 우리는 map에 tmp를 넣어줬다. 그렇다면 이때 정말로 map의 value에는 tmp의 값이 들어가는 것일까. 주소가 들어가는 것일까?
정답은 주소가 들어간다!!!!!!!!!!!!!!!!!!
주소가 들어가서 tmp를 clear 해버리면 자동으로 map에 있는 value를 꺼내려고 해도 tmp가 clear 된 값밖에 나오지 않는다. 아니면 마지막으로 들어간 값이 나오거나.
그러므로 new로 조건에 맞을 때마다 새로운 객체를 생성해서 map에 넣어줘야 실질적인 답을 얻을 수 있다.