본 글에 사용된 코드와 이미지의 일부는
김영한 님의 『김영한의 실전 자바 - 기본편』 강의를 참조하여 발췌·활용하였습니다.
[본 게시물은 파트너스 활동의 일환으로 소정의 수수료를 받을 수 있습니다.]
자바에서 참조형을 제대로 이해하는 것을 정말 중요하다.
기본형 vs 참조형 - 특징
변수의 데이터 타입을 크게 보면 기본형과 참조형으로 분류할 수 있다.
각 특징을 알아보자.
기본형
- int, long, double, boolean 처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입이다.
- 해당 값을 바로 사용할 수 있다.
- 들어있는 값을 그대로 계산에 사용할 수 있다.
참조형
- Student student, int[] students 와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입, 객체 또는 배열에 사용한다.
- 객체는 .(dot) 을 통해서 메모리 상에 생성된 객체를 찾아가야 사용할 수 있다.
- 배열을 [] 를 통해서 메모리 상에 생성된 배열을 찾아가야 사용할 수 있다.
- 참조값만 가지고는 계산 할 수 없다.
- 클래스는 대문자로 시작하고, 모두 참조형이다.
- String 도 클래스이므로 참조형이다.
기본형 vs 참조형 - 변수 대입
기본형, 참조형 모두 항상 변수에 있는 값을 복사해서 대입한다.
기본형은 변수에 값을 대입하더라도 실제 사용하는 값이 변수에 바로 들어있기 때문에 해당 값만 복사해서 대입한다.
그런데 참조형의 경우 실제 사용하는 객체가 아니라 객체의 위치를 가리키는 참조값만 복사된다.
다음 코드들을 보고 어떤 값들이 나올지 예측해보자.
기본형 - 변수 대입
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
참조형 - 변수 대입
public class Data {
int value;
}
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 에는 참조값 x001 이 들어있다. 여기서는 변수 dataA 에 들어있는 참조값을 복사해서 변수 dataB 에 대입한다.
인스턴스를 복사하는 것이 아니라 참조값만 복사해서 전달하는 것이다.
dataA , dataB 은 같은 참조값을 가지게 되고, 두 변수는 같은 객체 인스턴스를 참조하게 된다.
기본형 vs 참조형 - 메서드 호출
메서드를 호출할 때 사용하는 매개변수(파라미터)도 결국 변수일 뿐이다.
따라서 메서드를 호출할 대 매개변수에 값을 전달하는 것도 앞서 설명한 내용과 같이 값을 복사해서 전달한다.
기본형 - 메서드 호출
다음 메서드 호출 코드를 보고 어떤 결과가 나올지 생각해보자.
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의 값을 전달(복사)한다.
- 메서드 안에서 값을 변경 : 메서드 안에서 x=20 으로 새로운 값을 대입
- 메서드 종료 : a=10 출력 , 매개변수 x 제거
참조형 - 메서드 호출
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 에 dataA 의 값을 전달(복사) 한다. 참조값을 복사해서 전달한다.
- 메서드 안에서 값을 변경 : 참조값을 통해 인스턴스에 접근하고 그 안에 있는 value 값을 변경한다.
- 메서드 종료 : dataA.value 의 값을 확인하면 20으로 변경된것을 확인할 수 있다.
즉, 자바에서 매개변수(파라미터)는 항상 값에 의해 전달된다. 그러나 이 값이 실제 값이냐, 참조(메모리 주소)값 이냐에 따라 달라진다.
변수와 초기화
변수 종류
변수의 종류에는 멤버 변수, 지역 변수가 있다.
- 멤버 변수(필드) : 클래스에 선언
- 지역 변수 : 메서드에 선언, 매개변수도 지역 변수의 한 종류
변수의 값 초기화
멤버 변수는 인스턴스를 생성할 때 자동으로 초기화 되고, 개발자가 초기값을 직접 지정할 수 있다.
지역 변수는 항상 직접 초기화 해야 한다.
null
참조형 변수에 가리키는 대상이 없거나, 대상을 나중에 입력하고 싶다면 어떻게 해야 할까?
그럴때는 null 을 사용하면 된다.
null 이란 값이 존재하지 않는, 없다는 뜻으로, 참조형 변수에서 아직 가리키는 대상이 없다면 null 이라는 특별한 값을 넣어둘 수 있다.
null 값 할당
public class Data{
int value;
}
public class NullMain1 {
public static void main(String[] args) { Data data = null;
System.out.println("1. data = " + data);
data = new Data();
System.out.println("2. data = " + data);
data = null;
System.out.println("3. data = " + data);
}
}
---실행 결과---
1. data = null
2. data = ref.Data@x001
3. data = null
- Data data = null
- Data 타입을 받을 수 있는 참조형 변수 data 에 null 을 할당했다.
- data 변수에는 아직 가리키는 객체가 없다는 뜻이다.
- data = new Data()
- 새로운 Data 객체를 생성해서 참조값을 data 변수에 할당했다.
- data = null
- data 에 null 값을 할당했다.
- 앞서 만든 Data 인스턴스를 더는 참조하지 않는다.
NullPointerException
만약 참조값 없이 객체를 찾아가면 어떤 문제가 발생할까?
이 경우 NullPointerException 예외가 발생한다.
NullPointerException 은 이름 그대로 null 을 가리키다 인데, 이 때 발생하는 예외이다.
결국, 주소가 없는 곳을 찾아갈 때 발생하는 예외이다.
멤버 변수와 null
public class Data {
int value;
}
public class BigData {
Data data;
int count;
}
public class NullMain {
public static void main(String[] args) {
BigData bigData = new BigData();
System.out.println("bigData.count=" + bigData.count);
System.out.println("bigData.data=" + bigData.data);
//NullPointerException
System.out.println("bigData.data.value=" + bigData.data.value);
}
}
위 코드를 실행시키면 NullPointerException 예외가 발생한다.
BigData 를 생성하면 BigData 인스턴스가 생성된다. 이때 인스턴스의 멤버 변수에 초기화가 일어나는데, data 멤버 변수는 참조형이므로 null 로 초기화 되고, count 는 0으로 초기화 된다.
bigData.data.value 를 출력하면 data 의 값이 null 이고, .(dot) 을 찍게 되고, 참조할 곳이 없으므로 예외가 발생한다.
'인프런 > 김영한 자바' 카테고리의 다른 글
| [김영한의 실전 자바 기본편] - 4. 생성자 (0) | 2025.09.09 |
|---|---|
| [김영한의 실전 자바 기본편] - 3. 객체 지향 프로그래밍 (0) | 2025.09.09 |
| [김영한의 실전 자바 기본편] - 1. 클래스와 데이터 (0) | 2025.09.02 |
| [김영한의 자바 입문] - 8. 메서드 (1) | 2025.09.01 |
| [김영한의 자바 입문] - 7. 배열 (1) | 2025.09.01 |