본문 바로가기

Java/Java SE, EE

[Java/Java SE, EE] 기본 타입(Primitivie)과 참조(Reference) 타입, 값에 의한 호출(Call by value)와 참조에 의한 호출(Call by reference)

기본(Primitivie) 타입

Java에서 사용하는 데이터 타입 byte, char, short, int, long, flot, double, boolean을 기본(Primitive) 타입이라고 부릅니다.

  • 기본 타입
    • 정수형
      • byte
      • char
      • short
      • int
      • long
    • 실수형
      • float
      • double
    • 논리형
      • booleaen

기본 타입으로 선언 된 변수는 동일한 타입의 리터럴 상수를 값으로 갖게 됩니다. 변수를 선언한다는 것은 데이터 타입에 맞는 메모리를 할당하고, 해당 메모리에 값을 기록하는 행위입니다. 아래 예시에서 b = a에 대한 변수 선언은 b = 100의 값을 할당하는 것으로 해석 할 수 있습니다. 혹여나 기본 타입의 변수가 서로 같은 리터럴을 저장하더라도, 기본 타입 변수는 고유의 메모리를 할당하고 값을 기록하므로 상호간에 어떠한 영향도 미치지 않습니다.

int a = 100;
int b = a;

System.out.println("a : " + a + ", b : " + b);

a = 200;

System.out.println("a : " + a + ", b : " + b);

챰조(Reference) 타입

Java에서 기본 타입을 제외한 배열(Array), 열거(Enum), 클래스(Class), 인터페이스(Interface) 등을 참조 타입이라고 부릅니다. 

  • 참조 타입
    • 배열
    • 열거
    • 클래스
    • 인터페이스
    • ...

기본 타입의 변수는 메모리에 값을 저장하지만, 참조 타입의 경우 메모리에 객체의 주소를 저장합니다. 객체(Object)란 참조 타입으로 생성한 각각의 데이터를 의미하며, 인스턴스(Instance)라고도 부릅니다. 이러한 차이점은 아래 코드에서 극명하게 나타납니다. b = a에 대한 변수 선언은 이전 예시와 동일한 코드이지만, b = a가 참조하고 있던 객체의 메모리 주소를 할당하게 됩니다. 따라서 변수 a가 참조하고 있는 객체의 속성(value = 200)이 변하게 되면, 이를 참조하고 있는 변수 b역시 영향을 받게 됩니다. 정확히는 변수 a의 변화에 대해서 b가 반응하고 있는 것은 아니며, 객체의 속성이 변했기 때문에 이를 참조하고 있는 각각의 변수 a와 b가 모두 변화된 객체의 속성을 동일하게 읽어오는 것입니다.

SomeClass a = new SomeClass();
a.value = 100;
SomeClass b = a;

System.out.println("a : " + a.value + ", b : " + b.value);

a.value = 200;

System.out.println("a : " + a.value + ", b : " + b.value);

값에 의한 호출(Call by value)

어떤 함수를 구성하는 인자(Parameters) 역시 데이터 타입을 지정해야 합니다. 함수의 인자가 기본(Primitive) 타입인 경우, 이를 값에 의한 호출(Call by value)라고 부릅니다. 값에 의한 호출은 함수 호출 시 전달된 변수와 함수 내부에서 전달받은 변수가 서로 완전히 다른 데이터입니다. 함수 인자에 해당하는 변수는 함수가 호출되면 새로운 메모리를 할당하고, 인자로 받은 값(리터럴)을 복사하여 해당 메모리에 저장합니다. 즉, 저장 값만 동일한 완전히 다른 변수입니다.

아래 예시는 함수 내부에서 인자의 값을 제어하더라도, 함수를 호출했던 외부에는 영향을 주지 않고 있습니다.

public static void main(String[] _args) {
   int v = 100;
   
   System.out.println("함수 호출 전 : " + v);
   
   CallByValue(v);
   
   System.out.println("함수 호출 후 : " + v);
}

public static void CallByValue(int _v) {
   _v = _v * 2;
}

코드 실행 결과는 다음과 같습니다.

함수 호출 전 : 100
함수 호출 후 : 100

참조에 의한 호출(Call by reference)

함수를 구성하는 인자가 참조(Reference) 타입인 경우 함수 외부에서 전달한 변수와 함수 내부에서 전달받은 변수가 같은 변수입니다. 정확히는 인자로 전달되는 값은 단순히 객체를 가리키는 주소이기 때문에, 이 값이 복사되었든 아니든 상관이 없습니다. 이 값(주소)이 가리키는 객체는 함수 내외를 걸쳐 일관성있기 때문입니다.

아래 예시에서는 함수 내부에서 인자의 값을 제어하면, 함수를 호출했던 외부에도 영향을 주고 있습니다.

public static void main(String[] _args) {
   SomeClass v = new SomeClass();
   v.value = 100;
   
   System.out.println("함수 호출 전 : " + v.value);
   
   CallByReference(v);
   
   System.out.println("함수 호출 후 : " + v.value);
}

public static void CallByReference(SomeClass _v) {
   _v.value = _v.value * 2;
}

결국 인자로 전달된 변수가 복사되었다는 사실 관계는 중요하지 않습니다. 새로운 메모리를 할당하고 값(주소)을 복사하였다 하더라도, 주소는 똑같은 객체를 가리키고 있기 때문입니다. 

함수 호출 전 : 100
함수 호출 후 : 200

한 가지 사실을 더 확인하기 위해서, 추가 예시를 작성해 봤습니다. 추가 예시에서는 참조 타입의 변수에 다른 참조 타입 변수가 저장하고 있는 주소를 덮어 쓰고 있습니다. 이렇게 하면 원래 참조하고 있던 주소가 바뀌므로, 바뀐 주소가 가리키는 객체를 읽어오게 됩니다.

public static void main(String[] _args) {
   SomeClass a = new SomeClass();
   SomeClass b = new SomeClass();
   a.value = 100;
   b.value = 200;
   
   System.out.println("함수 호출 전 : " + a.value);
   
   CallByReference(a, b);
   
   System.out.println("함수 호출 후 : " + a.value);
   
   a = b;
   
   System.out.println("주소 변경 후 : " + a.value);
}

public static void CallByReference(SomeClass _a, SomeClass _b) {
   _a = _b;
}

함수 외부에서는 변경된 주소(a = b)가 가리키는 객체를 제대로 읽어오고 있습니다. 하지만 함수 내부에서 동일한 작업(_a = _b)을 수행하더라도 함수 외부의 변수 a는 원래 가리키고 있는 객체를 그대로 읽어오고 있습니다. 결국 참조 타입 역시 인자로 전달되는 경우 기본 타입처럼 새로운 메모리를 할당하고 값을 복사한다는 사실을 알 수 있습니다.

함수 호출 전 : 100
함수 호출 후 : 100
주소 변경 후 : 200