자바의 타입은 크게 기본타입과 참조타입으로 분류된다. 기본타입에는 정수, 실수, 논리가 있고 참조 타입에는 배열, 열거(enum),클래스, 인터페이스가 있다.
기본 타입은 변수에 값을 직접 저장하지만 참조 타입은 메모리의 주소를 변수에 저장한다. 주소를 참조한다고 하여 참조 타입이다.
그렇다면 String은 어떤 타입일까? String은 클래스이므로 참조 타입이다.
JVM이 운영체제에서 할당받아 사용하는 메모리 영역은 다음과 같이 나누어 진다.
메소드 영역은 JVM이 시작될 때 생성되고 모든 스레드가 공유한다. 여기서 사용되는 클래스들은 (.class) 클래스 로더로 읽고 클래스별로 정적 필드, 상수, 메소드 코드, 생성자 코드 등을 분류해서 저장한다.
힙 영역은 객체를 저장한다. 자바에서는 배열도 객체로 취급하기 때문에 배열도 힙 영역에 저장된다. 여기에 저장된 객체와 배열들은 JVM Stack 의 변수나 다른 객체의 필드에서 참조한다. 만약에 참조되는 변수나 필드가 없다면 JVM이 Garbage Collector 를 실행시켜 자동으로 제거한다.
메소드를 호출할 때마다 프레임을 추가(push)하고 메소드가 종료되면 제거(pop)한다. 그리고 하나의 프레임 안에는 로컬 변수 스택이 있다. 로컬변수는 블록 안에서 초기화 될 때 즉, 최초로 변수에 값이 저장될 때 생성되고 블록을 벗어나면 스택에서 제거된다.
기본 타입 변수의 ==, != 연산은 변수의 값이 같은지 아닌지를 연산하지만 참조 타입 변수들 같의 해당 연산은 동일한 객체를 바라보는지 즉, 동일한 객체를 참조 하는지 확인한다.
참조 타입 변수는 null 값을 가질 수 있는데 이는 Heap 영역의 객체를 참조하지 않는다는 뜻이다. null 값도 초기값으로 사용될 수 있기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성된다. 참조 변수를 사용하면서 가장 많이 발생하는 예외가 NullPointerException이다. 참조 변수가 null 값을 가지고 있는데 이를 사용하게 되면 NullPointerException이 발생한다. 참조하는 객체가 없는데 사용하려고 하니까 발생하는 것이다. 예를 들어 아래와 같은 경우이다.
int[] intArray = null;
intArray[0] = 10;
java는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 되어있다. 따라서 다음과 같이 name1과 name2 변수가 동일한 문자열 리터럴인 "커피챗"을 가질 경우 name1과 name2는 동일한 String 객체를 참조하게 된다.
String name1 = "커피챗";
String name2 = "커피챗";
일반적으로 변수에 문자열을 저장할 때 문자열 리터럴을 사용하지만, new 연산자를 사용해서 직접 String 객체를 생성시킬 수도 있다. new 연산자는 Heap 영역에 직접 새로운 객체를 만들 때 사용하는 객체 생성 연산자이다.
String name1 = new String("커피챗");
String name2 = new String("커피챗");
따라서 위의 경우에는 서로 다른 객체를 바라보고 있다. 따라서 비교연산자를 사용해보면 같지 않다고 나올 것이다.
if(name1 == name2){
System.out.println("they are the same");
} else {
System.out.println("they are not the same");
}
//they are not the same이 출력 된다.
동일한 객체이건 말건 내부 문자열을 비교하고 싶다면 String 객체의 equals()를 사용하면 된다.
boolean result = name1.equals(name2);
System.out.println(result);
//true 가 출력된다.
다음 코드처럼 변수가 String 객체를 참조하였지만 null을 대입함으로써 더 이상 String 객체를 참조하지 않도록 할 수도 있다. 이 때 JVM은 참조되지 않게된 객체를 쓰레기로 취급하여 Garbage Collector를 가동시켜 메모리에서 제거한다.
String hobby = "soccer";
hobby = null;