Java Category/Java

[JAVA] 참조 타입과 참조 변수

ReBugs 2022. 12. 26.

혼자 공부하는 자바 (저자 : 신용권)의 내용을 개인적으로 정리하는 글임을 알립니다.


데이터 타입은 기본 타입과 참조 타입으로 나눠지며, 기본 타입은 이전에 정리한 바가 있다.
2022.12.22 - [Language/JAVA] - [JAVA] 기본 타입

 

[JAVA] 기본 타입

변수를 선언할 때 주어지는 타입에 따라 변수에 저장할 수 있는 값의 종류와 허용 범위가 달라진다 자바에서 타입에는 기본 타입과 참조 타입 두 개로 나눠지며, 오늘 다룰 내용은 기본 타입이

rebugs.tistory.com


참조 타입

참조 타입이란 객체의 주소를 참조하는 타입으로 배열, 열거, 클래스, 인터페이스를 뜻함
기본 타입은 실제 값을 변수 안에 저장하지만, 참조 타입은 메모리의 주소를 변수 안에 저장한다. 주소를 통해 객체를 참조한다는 뜻에서 참조 타입이라고 부른다.
 

기본 타입 변수 참조 타입 변수
int age = 25;
double price = 100.5;
String name = "ReBugs";
String hobby = "프로그래밍";

String은 클래스의 일종이다

위의 그림에서 보는 것과 같이 기본타입 변수는 직접 값을 저장하고 있지만, 참조 타입 변수는 힙 영역의 String 객체 주소값을 가지고 있다.
이처럼 주소를 통해 객체를 참조하기 때문에 String 클래스 변수를 참조 타입 변수라고 한다.
 

 메모리 사용 영역

JVM은 운영체제에서 할당받은 메모리 영역을 다음과 같이 세부 영역으로 구분해서 사용함

메소드 영역

  • JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역
  • 바이트코드 파일의 내용이 저장되는 영역
코드에서 사용되는 클래스들을 클래스 로더로 읽어 클래스별로 정적 필드와 상수, 메소드 코드, 생성자 코드 등을 분류해서 저장함.

그림상에는 메소드 영역 내부 클래스에 정적 필드와 정적 메소드 밖에 없지만, 인스턴스 메소드, 생성자 코드도 있다.

 

힙 영역

  • 힙 영역은 객체와 배열이 생성되는 영역.
  • 여기에 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조함.
  • 만일 참조하는 변수나 필드가 없다면 의미 없는 객체가 되기 때문에 JVM이 이것을 쓰레기로 취급하고 Garbage Collector를 실행시켜 자동으로 제거함. 따라서 개발자는 객체를 제거하기 위해 별도로 코드를 작성할 필요가 없음.
  • 자바는 이 때문에 객체를 직접 제거하는 방법을 제공하지 않음
     

JVM 스택 영역

  • 여기에서 기본 타입 변수와 참조 타입 변수가 생성되고 제거된다.
  • JVM 스택은 메소드를 호출할 때마다  프레임을 추가(push)하고, 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행
  • 프레임 내부에는 로컬 변수 스택이 있는데, 기본 타입 변수와 참조 타입 변수가 추가되거나 제거된다.
  • 스택 영역에 변수가 생성되는 시점은 초기화가 될 때, 즉, 최초로 변수에 값이 저장될 때임
  • 변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거됨
JVM 스택 영역
아래는 main함수 내에서 함수의 호출이 이뤄졌으므로 메인 스레드에서 프레임이 생성된다.
스레드 n에서 함수의 호출이 이뤄지면 스레드-n에서 프레임이 생성된다.


 
다음과 같이 배열 변수인 scores는 스택 영역에 생성되지만 실제 10, 20, 30을 갖는 배열은 힙 영역에 생성됨. 배열 변수인 scores에는 배열의 힙영역의 주소가 저장됨. 참고로 자바에서는 배열을 객체로 취급함

int[] scores = {10, 20, 30};

정리하자면 메소드가 호출되면 JVM스택에는 프레임이 들어가고 프레임 안에는 여러 변수가 있다.
기본 타입의 경우는 변수 자체에 저장되지만, 참조 타입은 힙 영역에 객체로 생성되며 변수는 힙 영역에 생성된 객체의 주소값을 참조하고 있다. 메소드가 종료되면 JVM 스택에서 프레임이 제거된다.
힙 영역에서 객체를 참조하는 변수가 하나도 없다면 Garbage Collector가 객체를 수거한다.

참조 변수의 ==, != 연산

참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 때 사용됨
참조 타입 변수의 값은 힙 영역의 객체 주소이므로 ==, != 연산은 결국 주소 값을 비교하는 것이 됨
동일한 주소값을 갖고 있다는 것은 동일한 객체를 참조한다는 의미
반대로 다른 주소값을 갖고 있다는 것은 다른 객체를 참조한다는 의미
 

null과 NullPointerException

참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null값을 가질 수 있음. null값도 초기값으로 사용할 수 있기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성됨
참조 변수가 만약 null을 가지고 있을 경우, 참조 객체가 없으므로 변수를 통해 객체를 사용할 수 없음. 따라서 만약 null 상태에서 있지도 않은 객체의 데이터나 메소드를 사용하는 코드를 실행하면 NullPointerException이 발생함

int[] arr = null;
arr[0] = 10; //NullPointerException

 

String 타입

문자열을 String 변수에 저장한다는 말은 엄밀히 말해 틀린 표현이다.
문자열이 직접 변수에 저장되는 것이 아니라, 문자열은 String 객체로 생성되고 변수는 String 객체를 참조하기 때문
자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 되어 있음
다음과 같은 경우는 동일한 String 객체를 참조하게 됨

String A = "ReBugs";
String B = "ReBugs";

일반적으로 변수에 문자열을 저장할 경우에는 문자열 리터럴을 사용하지만, new 연산자를 사용해서 직접 String 객체를 생성시킬 수도 있음
new 연산자는 힙 영역에 새로운 객체를 만들 때 사용하는 연산자로 객체 생성 연산자라고 함

String A = new String("ReBugs");
String B = new String("ReBugs");

이 경우는 A와 B는 서로 다른 객체를 참조하고 있다.
 
따라서 동일한 String 객체이건 다른 String 객체이건 상관없이 내부 문자열을 비교하고 싶을 때는 String 객체의 equals()메소드를 사용해야 함
equals() 메소드는 원본 문자열과 매개값으로 주어진 비교 문자열이 동일한지 비교한 후 true 또는 false를 리턴함

package TestPakage;
public class Test {
	public static void main(String[] args) {
		String strVar1 = "ReBugs";
		String strVar2 = "ReBugs";
		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("ReBugs");
		String strVar4 = new String("ReBugs");
		
		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는 문자열이 같음
*/

 
또한 자바 13부터는 아래와 같은 텍스트 블록 문법을 제공한다.

String str = """
//...
""";

 
큰따옴표 3개로 감싸면 이스케이프하거나 라인피드를 할 필요 없이 작성된 그대로 문자열로 저장된다.
 

public class Main{
    public static void main(String[] args) {
        String str = """
                "id" : "winter",
                "name" : "눈송이"
                """;
        String str1 = """
                나는 자바를 \
                학습합니다.
                나는 자바 고수가 될 겁니다.
                """;
        System.out.print(str);
        System.out.println("----------------------");
        System.out.print(str1);
    }
}
/*
"id" : "winter",
"name" : "눈송이"
----------------------
나는 자바를 학습합니다.
나는 자바 고수가 될 겁니다.
*/
str1에서의 '\'의 의미
만약 줄바꿈을 하지 않고 다음 줄에 이어서 작성하고 싶다면 맨 끝에 \를 붙여주면 된다.
이 기능은 Java 14부터 제공된다.

 

'Java Category > Java' 카테고리의 다른 글

[JAVA] 열거 타입(enum)  (0) 2022.12.28
[JAVA] 배열(Array)  (0) 2022.12.27
[JAVA] 연산자  (2) 2022.12.25
[JAVA] 시스템 입출력  (0) 2022.12.24
[JAVA] 타입 변환(형 변환)  (0) 2022.12.23

댓글