자바의 타입은 크게 기본 타입(primitive type)과 참조 타입(reference type)으로 분류되는데, 기본 타입은 정수, 실수, 문자, 논리 리터럴을 저장하는 타입을 말하고 참조 타입이란 객체(object)의 번지를 참조하는 타입으로 배열, 열거, 클래스, 인터페이스를 말한다.
기본 타입과 참조 타입
기본 타입으로 선언된 변수와 참조 타입으로 선언된 변수의 차이점은 저장되는 값이다.
기본 타입인 byte, char, short, int, long, float, double, boolean 변수는 실제 값을 변수 안에 저장하지만,
참조 타입인 배열, 열거, 클래스, 인터페이스 변수는 메모리의 번지를 변수 안에 저장하며, 번지를 통해 객체를 참조한다는 뜻.
ex)
| 기본 타입 변수 | 참조 타입 변수 |
| int age = 25; double price = 100.5; |
String name = "홍길동"; String hobby = "독서"; |
int 타입 변수인 age와 double 타입 변수인 price는 직접 값을 저장하고 있지만, String 클래스 변수인 name과 hobby는 힙 영역의 String 객체 번지 값을 가지고 있으며, 이처럼 번지를 통해 객체를 참조하기 때문에 String 클래스 변수를 참조 타입 변수라고 함.
| 기본 타입 변수 변수: age, price 타입: int, double 메모리 저장 방식: 스택 영역에 실제 값을 직접 저장 |
참조 타입 변수 변수: name, hobby 타입: String(클래스) 메모리 저장 방식: 스택 영역에 힙 영역의 객체를 가리키는 주소(번지)를 저장함. |
| 스택 (Stack) 영역 메서드 실행 시 생성되는 영역 (변수 및 로컬 데이터 저장) -- 변수 age: 25(기본 값) price: 100.5(기본 값) name: 0x100(힙 번지 참조) hobby: 0x200(힙 번지 참조) |
힙 (Heap) 영역 객체(인스턴스)가 저장되는 영역 (동적 할당된 데이터 저장) -- 데이터 String 객체 (번지 0x100) - 내부 값: "홍길동" String 객체 (번지 0x200) - 내부 값: "독서" |
메모리 사용 영역(Runtime Data Area)
JVM은 운영체계에서 할당받은 메모리 영역을 세부 영역으로 구분하면 메소드 영역, 힙 영역, JVM 스택 영역이 있다.
메소드 영역(Method Area)은 JVM이 시작할 때 생성되고, 모든 스레드가 공유하는 영역이며, 메소드 영역에는 코드에서 사용되는 클래스(~.class)들을 클래스 로더로 읽어 클래스별로 정적 필드(static field)와 상수(constant), 메소드 코드, 생성자(constructor)코드 등을 분류해서 저장.
힙 영역(Heap Area)은 객체와 배열이 생성되는 영역이며, 여기서 생성된 객체와 배열은 JVM스택 영역의 변수나 다른 객체의 필드에서 참조하고 만일 참조하는 변수나 필드가 없다면 의미 없는 객체가 되기 때문에 JVM은 이것을 쓰레기로 취급하고 쓰레기 수집기(Garbage Collector)를 실행시켜 자동으로 제거함.
JVM 스택 영역
JVM 스택은 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행.
프레임 내부에는 로컬 변수 스택이 있는데, 기본 타입 변수와 참조 타입 변수가 추가(push)되거나 제거(pop)됨.
스택 영역에 변수가 생성되는 시점은 초기화가 될 때, 즉 최초로 변수에 값이 저장될 때이며, 변수가 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거됨.
| 메모리 영역 | 저장 내용 | 주요 특징 | 공유 여부 |
| 메소드 영역 (Method Area) |
클래스 정보(정적 필드, 상수, 메소드 코드, 생성자 코드) | JVM이 시작될 때 로드되며, 프로그램의 구조적인 정보를 담음. | 모든 스레드 공유 |
| 힙 영역 (Heap Area) |
프로그램 실행 중 생성되는 객체(Object)와 배열(Array) | Garbage Collection(GC)의 대상이 되는 영역 | 모든 스레드 공유 |
| JVM 스택 ( JVM Stack) |
프레임(Frame)(메소드 호출 정보, 지역 변수, 매개 변수) | 메소드가 호출될 때 푸시(push)되고 종료되면 팝(pop)되는 LIFO 구조 | 스레드별 독립적 |
참조 변수의 ==, != 연산
기본 타입 변수의 ==, != 연산은 변수의 값이 같은지, 아닌지를 조사하지만 참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 때 사용되며, 참조 타입 변수의 값은 힙 영역의 객체 주소이므로 ==, != 연산은 결국 번지 값을 비교하는 것이 됨.
동일한 번지 값을 갖고 있다는 것은 동일한 객체를 참조한다는 의미이며, 동일한 객체를 참조하고 있을 경우 == 연산의 결과는 true이고
!= 연산의 결과는 false.
null과 NullPointerException
참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null(널) 값을 가질 수 있는데 null값도 초기값으로 사용할 수 있기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성됨.
자바는 프로그램 실행 도중에 발생하는 오류를 예외(Exception)라고 부르며, 참조 변수를 사용하면서 가장 많이 발생하는 예외 중 하나로 NullPointerException이 있는데 이 예외는 참조 타입 변수를 잘못 사용하면 발생함.
참조 변수가 null을 가지고 있을 경우에는, 참조 객체가 없으므로 변수를 통해 객체를 사용할 수 없으며, 만약 null 상태에서 있지도 않은 객체의 데이터(필드)나 메소드를 사용하는 코드를 실행하면 NullPointerException이 발생.
String 타입
자바는 문자열을 String 변수에 저장하기 때문에 변수를 우선 선언해야 하는데, String 변수에 문자열을 저장하려면 큰따옴표("")로 감싼 문자열 리터럴을 대입함.
| 변수 = "문자열"; |
변수 선언과 동시에 문자열을 저장할 수도있고 여러개의 String 변수를 선언하고 문자열을 저장 할 수 있음.
String 변수 = "문자열";
String name;
name = "홍길동";
String hobby = "독서";
** 일반적으로 String 변수에 저장한다고 표현하는데 문자열은 String 객체로 생성되고 변수는 String 객체를 참조하기 때문에 문자열이 직접 변수에 저장되는 것은 아님.
자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 되어 있는데
| String name1 = "홍길동"; String name2 = "홍길동"; |
name1, name2 변수가 동일한 문자열 리터럴인 "홍길동"을 참조할 경우 name1,name2는 동일한 String 객체를 참조하게 됨.
일반적으로 변수에 문자열을 저장할 경우에는 문자열 리터럴을 사용하지만, new 연산자를 사용해서 직접 String객체를 생성시킬 수도 있으며, new 연산자는 힙 영역에 새로운 객체를 만들 때 사용하는 연산자로 객체 생성 연산자라고 함.
| String name1 = new String("홍길동"); String name2 = new String("홍길동"); |
이 경우 name1과 name2는 서로 다른 String 객체를 참조하고 있음.
문자열 리터럴로 생성하느냐 new 연산자로 생성하느냐에 따라 비교 연산자의 결과는 달라질 수 있으며, 동일한 문자열 리터럴로 String 객체를 생성했을 경우 == 연산의 결과는 true가 나오지만 new 연산자로 String 객체를 생성했을 경우 == 연산의 결과는 false가 나오는데, 그 이유는 == 연산자는 변수에 저장된 객체의 번지가 동일한지를 검사하기 때문.
동일한 String 객체이건 다른 String 객체이건 상관없이 내부 문자열을 비교하고 싶을 때에는 String 객체의 equals( ) 메소드를 사용.
equals( ) 메소드는 원본 문자열과 매개값으로 주어진 비교 문자열이 동일한지 비교한 후 true 또는 false를 리턴.
// 원본 문자열 비교 문자열
boolean result = str1.equals(str2);
// 문자열 비교
package sec01.exam01;
public class StringEqualsExample {
public static void main(String[]args) {
String strVar1 = "홍길동";
String strVar2 = "홍길동";
if(strVar1 == strVar2) {
System.out.println("strVar1과 strVar2는 참조가 같음");
} else {
System.out.println("strVar1과 strVar2는 참조가 다름");
}
if(strVar1.equals(strVar2)) {
System.out.println("strVar1과 strVar2는 문자열이 같음");
}
String strVar3 = new String("홍길동");
String strVar4 = new String("홍길동");
if(strVar3 == strVar4) {
System.out.println("strVar3과 strVar4는 참조가 같음");
} else {
System.out.println("strVar3과 strVar4는 참조가 다름");
}
if(strVar3.equals(strVar4)) {
System.out.println("strVar3과 strVar4는 문자열이 같음");
}
}
}
// 실행 결과
// strVar1과 strVar2는 참조가 같음
// strVar1과 strVar2는 문자열이 같음
// strVar3과 strVar4는 참조가 다름
// strVar3과 strVar4는 문자열이 같음
String 변수는 참조 타입이므로 초기앖으로 null을 대입할 수 있는데, 이때 null은 String 변수가 참조하는 String 객체가 없다는 뜻
String hobby = null;
hobby 변수가 String 객체를 참조하였으나, null을 대입함으로써 더 이상 String 객체를 참조하지 않도록 할 수도 있음.
String hobby = "여행";
hobby = null;
참조를 잃은 String 객체는 JVM이 참조되지 않은 객체를 쓰레기 객체로 취급하고 쓰레기 수집기(Garbage Collector)를 구동시켜 메모리에서 자동 제거함.
🔘 기본 타입: byte, short, char, int, long, float, double, boolean 타입을 말하며 이들 타입의 변수에는 값 자체가 저장됨.
🔘 참조 타입: 기본 타입을 제외한 배열, 열거, 클래스, 인터페이스 등을 말합니다. 참조 타입의 변수에는 객체의 번지가 저장됨.
번지로 객체를 참조한다는 의미에서 참조 타입이라고 함.
🔘 메모리 사용 영역: JVM은 운영체제에서 할당받은 메모리 영역을 메소드 영역, 힙 영역, 스택 영역으로 구분해서 사용함.
메소드 영역에는 정적 필드, 상수, 메소드 코드, 생성자 코드가 위치하며, 힙 영역에는 객체가 생성되며 스택 영역에는 변수가 생성됨.
🔘 번지 비교: 비교 연산자(==, !=)가 기본 타입에서 사용되면 값을 비교하지만, 참조 타입에서 사용되면 번지를 비교함.
==이 true가 나오면 객체를 참조한다는 뜻이고 false가 나오면 다른 객체를 참조한다는 뜻.
🔘 null: 참조 타입 변수는 객체를 참조하지 않는다는 뜻으로 null 값을 가질 수 있으며, null값도 초기값으로 사용할 수 있기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성됨.
🔘 NullPointerException: 참조 변수가 null을 가지고 있을 경우에는, 참조 객체가 없으므로 참조 변수를 통해 객체를 사용할 수 없다. 만약 null인 참조 변수를 통해 있지도 않은 객체의 필드나 메소드를 사용하면 NullPointerException이 발생함.
'Java' 카테고리의 다른 글
| [Java] 열거 타입 (0) | 2025.11.05 |
|---|---|
| [Java] 배열 (0) | 2025.11.04 |
| [Java] 반복문: for문, while문, do-while문 (0) | 2025.11.03 |
| [Java] 조건문: if문, switch문 (0) | 2025.11.02 |
| [Java] 연산자의 종류 - 이항 연산자 & 삼항 연산자 (0) | 2025.11.02 |