참조 타입 (Reference Type)

JAVA의 자료형에는 흔히 기본형 또는 원시타입 이라고 부르는 Primitive type 과 참조형 또는 래퍼클래스 라고 부르는
Reference Type 두 종류가 있습니다.

라고 원시타입 (Primitive Type) 에서 이야기 했었습니다.
** 이 포스트 에서는 Reference type 에 대하서 알아보겠습니다. **

# 객체

원시타입을 제외한 다른 모든 타입은 참조타입 입니다. 그리고 그들을 우리는 객체 라는 이름으로 부르기도 합니다.

원시타입과 객체의 가장 중요한 차이점은 객체는 null을 참조할 수 있다는 점 입니다.

1
2
int primitiveInt = null; //컴파일 에러 발생
Integer referenceInt = null;

위의 첫번째 예에서와 같이 원시타입의 int로 선언된 primitiveInt를 null로 초기화 시키는 코드를 작성하면 컴파일 에러가 발생 합니다.

두번째처럼 int의 Wrapper class인 Integer타입으로 선언된 referenceInt는 null 로 초기화가 가능 합니다.

원시타입은 int a = 100; 이라고 선언되면 100이라는 값이 메모리에 할당 됩니다.

원시타입 (Primitive Type) 에서 열심히 설명 했듯이 a는 `int`이기때문에 32 bits의 크기를 메모리에 고정 크기로 갖고 그 메모리 주소에 100이라는 값이 할당 된다는 말 입니다.

그럼 이 때 int b = a; 와같이 b에 a를 할당한 이후 a의 값을 변경하게 되면 어떤일이 일어날까요?
물론 100이 b에 할당 될것인데 이쯤되면 저를 포함해서 그냥 대충 되는대로 변수 선언하고 고민없이 사용하셨던 개발자 분들은 학교에서 배웠던것 같은 Call by reference, Call by value 이런것들이 머릿속을 돌아다니면서 헷갈리기 시작합니다.

오늘 다시 개념을 잡고 가는게 좋을것 같습니다.

1
2
3
4
5
6
7
@Test
public void testEntity() {
int a = 100;
int b = a;
a++;
assertEquals(a, b);
}

위 테스트의 결과는 fail 입니다. a는 101, b는 100으로 다르기 때문 입니다. int b =a; 는 결국 학교에서 배웠던 call by value 의 개념이라고 봐도 무방하겠네요.

1
2
3
4
5
6
7
@Test
public void testEntity2() {
Integer a = new Integer(100);
Integer b = a;
a++;
assertEquals(a, b);
}

위 테스트 역시 fail 입니다. 그 이유는 Integer class는 immutable(값이 변하지 않는)로 만들어져 있습니다. Long, Double, Float 등 숫자를 wrapping 한 클래스들은 모두 immutable 입니다.

그럼 imutable 이 아닌 List 로 다시 테스트 해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testEntity2() {
List<String> list1 = new ArrayList<>(20);

list1.add("a");
assertTrue(list1.size() == 1);

List<String> list2 = list1;
list2.add("b");
assertTrue(list1.size() == 2);
assertTrue(list1.contains("b"));
}

list1 을 만들고 “a”를넣었습니다.
list2 를 만들어서 새로운 객체를 생성 하지 않고 list1을 참조하게 만들었고, list2에다가 “b”를 넣었습니다.

list2 에다가 “b”를 넣었음에도 불구하고 list1 의 길이도 2로 바뀌었고 “b”를 포함하고 있습니다.

개발 하면서 가끔 하는 실수중에 하나가
값을 복사해서 새로운 리스트를 만들고, 그 값을 변경하려는 의도로 위처럼 그냥 할당해 버린 후 변경하는 코드를 작성하는 경우가 있습니다. 이런 경우 의도치 않게 원래 객체의 값도 변경되어 버리니 주의해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testEntity2() {

List<String> list1 = new ArrayList<>();

list1.add("a");
assertTrue(list1.size() == 1);

List<String> list2 = new ArrayList<>();
list2.addAll(list1);
list2.add("b");
assertTrue(list1.size() == 2);
assertTrue(list1.contains("b"));
}

위의 예에서 처럼 새로운 객체를 만들고 모든 객체를 다 추가 시켜주는 식으로 만들어야 원하는대로 한쪽의 수정이 다른 한쪽에 영향을 미치지 않게 됩니다.

#JAVA는 항상 Call by value

마지막의 list 예를 보고 call by reference 라고 착각 할 수 있는데 java는 무조건 값을 던져줍니다.
Stack overflow 에 이 질문에 대한 훌륭한 답변들이 많이 달려있으니 한번 확인 해 보시면 많은 도움이 될 것 입니다.

Share