기본형과 참조형
변수의 데이터 타입을 크게 나눠보면 기본형과 참조형이 있다.
- 기본형(Primitive Type): `int`, `long`, `double`, `boolean`처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입을 기본형이라고 한다.
- 참조형(Reference Type): `Student student1`, `int[] students`와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입을 참조형이라고 부른다. 참조형은 객체 또는 배열에 사용된다.
정리하자면, 기본형은 숫자 10, 20과 같이 실제 사용하는 값을 변수에 담을 수 있다. 그래서 바로 해당 값을 사용이 가능하다. 반면에, 참조형에 경우 실제 사용하는 값을 변수에 담는 것이 아닌, 실제 값이 위치한 주소(참조)를 저장한다.
객체의 경우 `.(dot)`을 통해서 메모리 상에 생성된 객체를 찾아간다. 배열은 `[]`를 통해서 메모리 상에 생성된 배열을 찾아갈 수 있다.
둘의 차이점을 가장 심플하게 이해하는 방법은 기본형에 경우 `int`, `char`, `long`과 같이 모두 소문자로 시작한다. 참조형에 경우 이와 반대로 `Student`와 같이 대문자로 시작하게 된다. 기본형은 자바가 기본으로 제공하는 데이터 타입이며, 이러한 기본형은 개발자가 새롭게 정의할 수 없다. 하지만 참조형의 경우 클래스를 만들어서 직접 정의가 가능하다.
단, `Strring`경우를 봐야한다. 자바에서 `String`은 특별하다. `String`은 클래스이고 참조형이다. 그런데 기본형처럼 바로 문자값을 대입하여 사용된다. 문자의 경우 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공한다.
기본형 vs 참조형
기본형과 참조형을 사용하는 방법을 비교하여 둘의 차이점을 알아보자.
1. 계산
기본형은 들어있는 값을 그대로 계산에 사용할 수 있다. 참조형에 경우 참조값을 그대로 사용할 수 없고, 주소지에 있는 실체에 접근해야 계산을 할 수 있다.
// 기본형은 변수를 바로 계산에 이용할 수 있다.
int a = 10, b = 20;
int sum = a + b;
// 참조형에 경우 바로 이용 시 오류가 발생한다.
Student s1 = new Student();
Student s2 = new Student();
s1 + s2 //오류 발생
기본형에 경우 위 코드와 같이 바로 계산에 활용할 수 있지만, 참조형에 경우 해당 변수에 들어있는 값은 주소값(참조값)이기 때문에 오류가 발생된다.
그렇기 때문에 다음과 같이 `.`같은 키워드를 이용해 멤버 변수에 접근한 후 연산에 활용된다.
Student s1 = new Student();
s1.grade = 100;
Student s2 = new Student();
s2.grade = 90;
int sum = s1.grade + s2.grade; //연산 가능
2. 변수 대입
자바는 항상 변수의 값을 복사해서 대입한다.
위에 기본 전제를 두고 기본형과 참조형을 생각해보자. 기본형은 변수에 실제 사용하는 값이 들어가 있다고 했다. 그렇다면 변수의 값을 대입했을 경우 복수되는 값은 실제 사용하는 값이 된다. 참조형의 경우 변수에 참조값(주소)이 들어가 있다. 참조형을 대입하면 참조값이 복사되어 들어가게 된다.
기본형 대입 예시
`int a`는 `10`값을 가지고 있고 변수 `b`에 변수 `a`를 대입한 예제이다.
package ref;
public class VarChange1 {
public static void main(String[] args) {
int a = 10;
int b = a;
System.out.println("a = " + a);
System.out.println("b = " + b);
//a 변경
a = 20;
System.out.println("변경 a = 20");
System.out.println("a = " + a);
System.out.println("b = " + b);
//b 변경
b = 30;
System.out.println("변경 b = 30");
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
실행결과
a = 10
b = 10
변경 a = 20
a = 20
b = 10
변경 b = 30
a = 20
b = 30
실행 결과를 보면 처음 대입 시 `a`값이 그대로 `b`의 복사되는 것을 알 수 있다. 이후 `a`의 값을 `20`으로 변경 시 `a`값만 변경되고 `b`의 값을 변화가 없는 것을 알 수 있다. `b`의 값을 30으로 변경 시도 마찬가로 `b`의 값만 변경되는 것을 알 수 있다.
여기서 알 수 있는 것은 변수 `a`에 들어있는 값 `10`을 복사해서 변수 `b`에 대입하는 건 변수 `a` 자체를 `b`에 대입하는 것은 아니다. 즉, 변수 `a`, `b`는 각각의 독립적인 변수임을 알 수 있다.
참조형 대입 예시
참조형 예시에 사용될 `Data`클래스를 하나 선언한다. `int value`라는 멤버 변수를 하나 가진다.
package ref;
public class Data {
int value;
}
마찬가지로 `dataA`라는 참조형 변수를 선언한 후 `value=10`값을 넣은 후 변수 `dataB`에 대입한다.
package ref;
public class VarChange2 {
public static void main(String[] args) {
Data dataA = new Data();
dataA.value = 10;
Data dataB = dataA;
System.out.println("dataA 참조값=" + dataA);
System.out.println("dataB 참조값=" + dataB);
System.out.println("dataA.value = " + dataA.value);
System.out.println("dataB.value = " + dataB.value);
//dataA 변경
dataA.value = 20;
System.out.println("변경 dataA.value = 20");
System.out.println("dataA.value = " + dataA.value);
System.out.println("dataB.value = " + dataB.value);
//dataB 변경
dataB.value = 30;
System.out.println("변경 dataB.value = 30");
System.out.println("dataA.value = " + dataA.value);
System.out.println("dataB.value = " + dataB.value);
}
}
실행 결과
dataA 참조값=ref.Data@x001
dataB 참조값=ref.Data@x001
dataA.value = 10
dataB.value = 10
변경 dataA.value = 20
dataA.value = 20
dataB.value = 20
변경 dataB.value = 30
dataA.value = 30
dataB.value = 30
위에서부터 결과를 분석해보면 변수 `dataA`와 `dataB`가 참조값이 동일한 것을 알 수 있다. 그렇기 때문에 동일한 `value`값이 나오고 있다. `dataA.value`의 값을 `20`으로 변경해 보면 변수 `dataB`의 값도 `20`으로 변경되는 것을 알 수 있다.
참조형의 대입도 마찬가지로 값을 복사한다. 단, 참조형의 경우 변수에 참조값이 들어있으니 참조값이 복사가된다. 참조값을 복사한다는 것은 결국 같은 주소를 바라보는 실제 메모리에 적재된 값이 동일하다는 것(같은 객체 인스턴스를 참조)을 알 수 있다. 그렇기 때문에 `dataA` 또는 `dataB`의 값을 변경하면 둘 다 영향을 미치게 되는 것이다.
3. 메서드 호출
이번에는 기본형과 참조형이 메서드 호출에 따라 어떻게 달라지는지 알아보자.
마찬가지로, 자바의 대원칙 "자바는 항상 변수의 값을 복사해서 대입한다."를 전제를 주고 시작하자.
메서드 호출에서의 변수는 매개변수(파라미터)를 생각해보면 된다. 매개변수도 우리가 대입에서 이야기한 조건을 그대로 이어받아 값을 복사해서 전달된다.
기본형 예시
다음 예시 코드는 변수 `a`의 `10` 값으로 선언한 후 `changePrimitive()`메서드를 실행하여 값의 차이를 본다.
package ref;
public class MethodChange1 {
public static void main(String[] args) {
int a = 10;
System.out.println("메서드 호출 전: a = " + a);
changePrimitive(a);
System.out.println("메서드 호출 후: a = " + a);
}
static void changePrimitive(int x) {
x = 20;
}
}
실행 결과
메서드 호출 전: a = 10
메서드 호출 후: a = 10
결과값을 보면 메서드 실행 후에도 값이 차이가 없는 것을 알 수 있다. 메서드가 실행되는 코드를 보면 다음과 같이 진행이 된다. 매서드가 호출할 때 매개변수 `x`에 변수 `a`의 값이 전달된다.
int a = 10;
changePrimitive(a);
자바에선 변수에 값을 대입한다는 것은 항상 값을 복사한다는 의미로 아래 코드처럼 작동이 되게 된다. 그럼 값의 대입에서 언급했듯이 기본형에선 실제 들어가는 값이 복사가 이뤄지고 실제 변수 `a`가 직접 대입되는 것이 아니므로 변수 `a`와 `x`는 각각 독립적인 형태로 존재하게 된다.
int x = a;
결론적으로, 각각 독립적인 변수이므로 메서드 안에 로직인 `x = 20`이 실행돼도 변수 `a`에 영향을 미치지 않게 된다.
참조형 예시
기본형과 마찬가지로 메서드 호출을 통해 선언된 변수의 값의 변화가 있는지 확인하는 예제이다.
package ref;
public class MethodChange2 {
public static void main(String[] args) {
Data dataA = new Data();
dataA.value = 10;
System.out.println("메서드 호출 전: dataA.value = " + dataA.value);
changeReference(dataA);
System.out.println("메서드 호출 후: dataA.value = " + dataA.value);
}
static void changeReference(Data dataX) {
dataX.value = 20;
}
}
실행 결과
메서드 호출 전: dataA.value = 10
메서드 호출 후: dataA.value = 20
실행 결과를 보면 메서드 실행을 통해 참조형 변수의 값이 변경된 것을 확인할 수 있다. 위에서 설명했듯이 매개변수에 변수를 대입하면 값의 복사가 일어나는데 참조형의 경우 참조값에 복사가 이루어진다.
참조값에 복사란 결국 실제 메모리의 적재되는 값을 동일하기 때문에 메서드 안에 일어나는 로직인 `dataX.value = 20;`이 실행되면 `dataA`의 값도 변경이 된다.
정리
기본형과 참조형의 메서드 호출
자바에서 메서드의 매개변수(파라미터)는 항상 값에 의해 전달된다. 그러나 전달되는 값이 기본형 데이터인지 참조형 데이터인지에 따라 동작이 달라진다.
- 기본형:
메서드로 기본형 데이터를 전달하면, 해당 값이 복사되어 전달된다. 이 경우, 메서드 내부에서 매개변수(파라미터)의 값을 변경해도, 호출자의 변수 값에는 영향을 미치지 않는다. - 참조형:
메서드로 참조형 데이터를 전달하면, 참조값(메모리 주소)이 복사되어 전달된다. 이 경우, 메서드 내부에서 매개변수(파라미터)로 전달된 객체의 멤버 변수를 변경하면, 호출자의 객체도 동일한 변경이 반영된다.
단, 메서드 내부에서 새로운 객체를 생성해 참조를 변경하면, 호출자의 변수에는 영향을 미치지 않는다.
참조
'Language > Java' 카테고리의 다른 글
[Java] null과 NullPointerException (0) | 2025.01.07 |
---|---|
[Java] 변수의 초기화 (0) | 2025.01.07 |
[Java] 클래스와 데이터 (2) | 2024.12.31 |
[Java] 스택(Stack) (1) | 2024.11.28 |
[Java] Math 클래스 (1) | 2024.11.15 |