Notice
Recent Posts
Recent Comments
05-18 01:37
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

Byeol Lo

참조타입 본문

Programming Language/Java

참조타입

알 수 없는 사용자 2022. 9. 24. 15:52

 데이터 타입에는 크게 기본 타입, 참조 타입이 있는데, 서로간의 선언된 변수들은 차이가 있다. primitive 타입으로 선언된 변수는 실제값을 변수 안에 저장하지만, 참조 타입배열, 열거, 클래스, 인터페이스를 이용해서 선언된 변수는 메모리의 번지를 값으로 갖는다. 번지를 통해 객체를 참조한다는 뜻에서 참조 타입이라고 부른다.

 변수들은 스택영역에 생성되고 객체는 힙 영역에 생성되는데, 참조 타입 변수가 100번지 주소를 가지고 있는것 자체는 스택 영역에 있는 것이고 스택 영역의 값을 읽어서 힙영역의 객체에 접근하게 된다.

 참조타입을 알아보기 전에 우선 JVM이 사용하는 메모리 영역을 알아보자. java.exe로 JVM이 시작되면 JVM은 운영체제에서 할당받은 메모리 영역(Runtime Data Area)을 다음 세부 영역으로 구분하여 사용한다.

메소드(Method) 영역

 메소드 영역에는 코드에서 사용되는 클래스(.class)들을 읽어 클래스별로 런타임 상수풀(runtime constant pool), 필드(field) 데이터, 메소드(method) 데이터, 메소드 코드, 생성자(constructor) 코드 등을 분류해서 저장, 메소드 영역은 JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역이다.

힙(Heap) 영역

 객체와 배열이 생성되는 영역이며, 참조하는 변수나 필드가 없으면 의미없는 객체가 되어 Garbage Collector(GC)를 실행시켜 쓰레기 객체를 힙 영역에서 자동으로 제거한다. 따라서 개발자는 객체를 제거하기 위해 별도의 코드를 작성할 필요가 없다

JVM 스택 영역

 JVM은 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당된다. 자바 프로그램에서 추가적으로 스레드를 생성하지 않았다면 main 스레드만 존재하므로 JVM 스택도 하나이다. JVM 스택은 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 방식이고, 예외 발생 시 printStackTrace() 메소드로 보여주는 Stack Trace의 각 라인은 하나의 프레임을 표현한다.

 

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

객체는 같은 번지를 저장할때 equal이 True로 반환된다. 따라서 다음은 각각 true, false를 리턴하는 메소드이다.

public class Main {
	public static void main(String[] args) {
    	String name1 = "hi";
        String name2 = "hi";
        String name3 = new String("hi");
        
        System.out.println(name1==name2);
        System.out.println(name2==name3);
        // System.out.println(name3==name1); false
        
    }
}

 

null과 NullPointerException

 참조타입변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null의 값을 지니게 될 수 있다. 따라서 null 값도 초기값으로 사용할 수 있기 때문에, null 로 초기화된 참조변수는 stack에 저장된다. 따라서 참조 변수가 null의 값을 가진다면, == 또는 !=을 통해 null을 가지는지 판별 가능하다.

public class Main {
	public static void main(String[] args) {
    	String name1 = null;
        
        System.out.println(name1 == null);	//true
    }
}

 참조타입변수로 포인터 접근을 했을때, 해당 객체가 heap 영역에 없다면 NullPointerException이 발생한다. 다음은 그 예제이다.

public class ExceptionExample {
	public static void main(String[] args) {
    	Stirng str = null;
        
        System.out.println(str.length());
    }
}
public class ExceptionExample2 {
	public static void main(String[] args) {
    	int[] intArray = null;
        
        intArray[0] = 10;
    }
}

 

메모리 영역에서의 String

 자바는 문자열 리터럴이 동일하다면 String객체를 공유하도록 되어 있고, 그 증거는 맨 첫 코드를 참조하면 되겠다. 이때 stack에 있는 번지를 담은 변수들은 각각 서로 같은 heap의 class를 가르키고 있다는 소리인데, 그렇게 하지 않기 위해서 new 예약어를 쓰면 되겠다.

 또한 만약 번지에 대한 동등성을 무시하고 싶다면 String.equals(String);을 쓰면 되겠다.

public class Main {
	public static void main(String[] args) {
    	String str1 = "hi";
        String str2 = new String("hi");
        
        System.out.println(str1.equals(str2));
        
    }
}

 

배열타입

 배열 또한 String과 마찬가지로 참조타입 변수이다. 배열을 사용하기 위해서 우선 배열 변수를 선언해야 하는데, 이미 main을 주구장창 쓰면서 많이 써왔을 것이다. 타입[] 변수명; 으로 선언하면 된다. 이때 배열 변수를 선언만 해놓고 값들은 나중에 저장하고 싶을때가 있을 수 있다. 그때는 다음과 같이 쓴다.

String[] names = null;
names = new String[] {"홍길동", "아이유", "이순신"};

 또한 배열의 크기도 지정 해줄 수 있다. 배열의 크기가 정해질 때는 각각 초기값들이 있는데 해당 초기값들로 맨 처음 초가화가 된다. numeric variable은 전부 0으로 초기화가 된다. (String은 null)

public class MainArray {
	public static void main(String[] args) {
    	int[] arr = new int[10];
        
        for(int i:arr) {
        	System.out.println(i);
        }
    }
}

 초기값들은 다음과 같다.

분류 데이터 타입 초기값
기본 타입 (정수) byte[]
char[]
short[]
int[]
long[]
0
\u0000
0
0
0L
기본 타입 (실수) float[]
double[]
0.0F
0.0
기본 타입 (논리) boolean[] false
참조타입 클래스명[]
인터페이스명[]
null
null

 배열에 있어서 Stack에는 배열의 생성번지(맨 첫번째)가 들어간다. 즉 0 index의 번지가 들어가게 된다.

 

커맨드 라인 입력에 들어가는 String[] 배열

 java 클래스로 프로그램을 실행하면 JVM은 길이가 0인 String 배열을 먼저 생성하고 main() 메소드를 호출할 때 매개값으로 전달한다. 즉 어떤 java파일을 실행할때, 우리는 커맨드에 java 파일명.java 를 통해 실행을 하는데, 이 라인 뒤에 공백으로 구분된 문자열 목록을 주고 실행하면, 문자열 목록으로 구성된 String[] 배열이 생성되고 main() 메소드를 호출할 때 매개값으로 전달한다.

//java Main.java hi im byeol lo


public class Main {
	public static void main(String args[]) {
    	if(args.length >0) {
        	for(String s : args) {
            	System.out.println(s);
            }
        }
    }
}

위의 코드는 hi, im, byeol, lo의 4개의 원소를 받고 줄 단위로 출력시키는 코드이다.

 

다차원 배열

 또한 배열 안의 배열도 가능한데, 다음과 같이 다차원 배열을 선언할 수 있다. 다차원 배열은 stack-heap-heap으로 참조가 된다.

public class MultiDimension {
	public static void main(String[] args) {
    	int[][] arr = new int[2][]; // 첫번째는 무조건 상수 크기만큼 넣어줘야 함
    }
}

 

 

 

배열의 복사

 배열을 복사할 때는 다음과 같은 방법들을 이용한다.

public class Main {
    public static void main(String[] args) {
        int[] oldIntArray = { 1, 2, 3 };
        int[] newIntArray = new int[5];

        for(int i=0; i<oldIntArray.length; i++) {
            newIntArray[i] = oldIntArray[i];
        }

        for(int i=0; i<newIntArray.length; i++) {
            System.out.print(newIntArray[i] + ", ");
        }
    }
}

해당 for문을 간단하게 하기 위해 System에선 다음 메소드를 지원한다.

public class Main {
    public static void main(String[] args) {
        String[] oldStrArray = {"홍길동", "heap", "stack"};
        String[] newStrArray = new String[5];

        System.arraycopy(oldStrArray, 0, newStrArray, 0, oldStrArray.length);

        for(int i=0; i<newStrArray.length; i++) {
            System.out.print(newStrArray[i] + ", ");
        }
    }
}

 참조 타입 배열일 경우, 배열 복사가 되면 복사되는 값이 객체의 번지이기 때문에, 새 배열의 항목은 이전 배열의 항목을 그냥 참조하는 것 뿐이다. 따라서 이는 이전 배열의 항목의 값을 변경하면 그대로 새 배열의 항목에도 그대로 적용이 되므로 서로 종속적으로 진행된다. 이를 얕은 복사(Shallow copy)라고 한다. 반대로 깊은 복사(deep copy)는 참조하는 객체도 별도로 생성하는 것을 말한다.

 

향상된 for문

배열을 통한 반복문을 말한다.

public class Main {
	public static void main(String[] args) {
    	int[] arr = {10,20,30,40,50};
        
        for(int i:arr) {
        	System.out.println(i);
        }
    }
}


열거 타입

 데이터 중에는 몇 가지로 한정된 값만을 갖는 경우가 흔히 있는데, 요일이나 계절, 월, 범주형데이터(categorical data)가 그 예이다. 이런 한정된 값만을 갖는 데이터 타입을 열거 타입의 특성으로 맞게 저장할 수 있다.

 열거타입의 이름은 관례(약속)적으로 첫 문자를 대문자로 하고 나머지는 소문자로 한다. 또한 내부의 값 목록들은 전부 상수(Constant) 취급이기에 대문자로 전부 선언한다.

enum Week {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

public class Main {
    public static void main(String[] args) {
        
    }
}

이렇게 선언된 열거타입의 변수 Week는 선언을 했으니 사용을 할 수 있다.

enum Week {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

public class Main {
    public static void main(String[] args) {
        Week sun = Week.SUNDAY;

        System.out.println(sun);
    }
}

 Week라는 열거형 데이터 타입의 값들을 저장하기 위해서는 Week 타입의 변수를 선언해주어야 한다. 이는 Long이라는 데이터 타입의 값들을 저장하기 위해 Long 타입의 변수를 선언해주어야 하는 것과 비슷하게 보면 되겠다.

 해당 코드를 자바 메모리 사용 영역에 대해 이해를 하려면 sun이라는 변수는 stack 영역에 생성되고, sun에 저장되는 값은 Week.SUNDAY 열거 상수가 참조하는 객체번지이다. 따라서 열거 상수 Week.SUNDAY와 today 변수는 서로 같은 객체를 참조하게 된다. 이를 통해 Week.SUNDAY에서 SUNDAY는 메소드 영역에서 생성됨을 알 수 있다.

'Programming Language > Java' 카테고리의 다른 글

Java - Inheritance 상속  (2) 2022.10.13
객체 지향 프로그래밍 Objective Oriented Programming (OOP)  (0) 2022.10.06
조건문과 반복문  (0) 2022.09.14
자바 연산자  (2) 2022.09.08
Variable 변수  (0) 2022.09.05
Comments