[이것이자바다] chapter 5. 참조 타입 (reference type)

chapter5

참조 타입 (reference type)

프로그램이 하는 일은 결국 데이터를 처리하는 것이다. 따라서 데이터를 얼마나 잘 다루느냐가 좋은 프로그램을 작성
할 수 있는 관건이 된다. 데이터를 잘 다루기 위해서는 자바에서 지원하는 데이터 타입에 대해서 반드시 이해하고 외워야 한다.

원시 타입(primitive type)  
정수 타입 byte
  char
  short
  int
  long
실수 타입 float
  double
논리 타입 boolean
참조 타입(reference type)
배열 타입
열거 타입
클래스
인터페이스
//[기본 타입 변수]
int age = 25;
doulbe price = 50.57;

//[참조 타입 변수]
String name = "신용권";
String hobby = "독서";
스택 영역 저장된 값
name 100
hobby 200
age 25
price 50.57
힙 영역 주소값
신용권 100
독서 200

=> primitive 타입 변수인 age,price는 직접 값을 저장하고 있지만, reference type 변수인 name, hobby는 각각 문자열 리터럴인 “신용권”과 “독서”의 주소값을 가리키고 있음을 알 수 있다.
따라서 자바는 c와 달리 reference type인 경우 포인터가 따로 필요가 없음을 알 수 있다.

JVM의 메모리 사용 영역

  1. 메소드(Method) 영역 메소드 영역에는 코드에서 사용되는 클래스(.class)들을 클래스 로더로 읽어
    클래스 별로 런타임 상수풀(runtime constant pool), 필드(field)/ 메소드(method) 데이터, 메소드(method) 코드, 생성자(constructor) 코드 등을
    분류해서 저장한다.
  1. 힙(Heap) 영역
    • 힙 영역은 객체와 배열이 생성되는 영역이다. 힙 영역에 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조한다.
      참조하는 변수나 필드가 없다면 의미 없는 객체가 되기 때문에 이것을 쓰레기로 취급하고 JVM은 Garbage Collector 를 실행시켜 쓰레기 객체를 힙 영역에서 자동으로 제거한다.
      따라서 개발자는 객체를 제거하기 위해 별도의 코드를 작성할 필요가 없다.(사실 자바는 코드로 객체를 직접 제거시키는 방법을 제공하지 않는다.)
  2. JVM 스택(Stack) 영역 JVM 스택 영역은 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 할당된다.
    (자바 프로그램에서 추가적으로 스레드를 생성하지 않았다면 main 스레드만 존재하므로 JVM 스택도 하나이다.)
    JVM 스텍은 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고, 메소드가 종료되면 프레임을 제거(pop)한다.
    예외 발생 시 printStackTrace() 메소드로 보여주는 Stack Trace의 각 라인은 하나의 프레임(하나의 메소드)을 표현한다.
    프레임 내부에는 로컬 변수 스택이 있는데, 기본 타입 변수와 참조 타입 변수가 추가(push)되거나 제거(pop)된다.
    변수가 이 영역에 생성되는 시점은 초기화 될 때, 즉 최초로 값이 저장될 때이다. 또 변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거된다.

예제

(1)char v1 = 'A';

(2)if(v1 =='A'){
  int v2 = 10;
  double v3 = 0.7;
}

(3)boolean v4 = true;

=> 이 코드의 지역 변수의 생성(push)과 제거(pop)과정.

(1)

스택 변수
v1 ‘A’

(2)

스택 변수
v3 0.7
v2 10
v1 A

(3)

스택 변수
v4 true
v1 ‘A’

printStackTrace()의 예제

public class NULLexception {
    public static void main(String[] args) {
        printStack();

    }
    public  static void printStack(){
        try {
            String data = null;
            System.out.println(data.toString());
        }
        catch (NullPointerException e ){
            e.printStackTrace();
        }
    }
}

//실행 결과 
//java.lang.NullPointerException
//	at NULLexception.printStack(NULLexception.java:9)  => 하나의 프레임(메소드)을 나타냄. printStack
//	at NULLexception.main(NULLexception.java:3)       => 하나의 프레임(메소드)을 나타냄. main

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

기본 타입 변수의 ==, != 연산은 변수의 값이 같은지, 아닌지를 조사하지만
참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는 지 알아볼 때 사용된다.
즉 참조타입 변수의 값이 힙 영역의 객체 주소이므로 결국 주소값을 비교하는 것이다.(중요!)

null 과 NullPointerException

스택 영역
String a null

String 타입

String name1 = "신용권";
String name2 = "신용권";
// name1 과 name2는 지역변수로서 스택에 할당되고, 문자열 리터럴인 "신용권"은 객체로서 힙에 할당된다. 
// 또 자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 되어 있으므로 모든 리터럴 문자열 "신용권"은 같은 주소값이다. 
// 따라서 변수 name1과 name2는 "신용권"을 참조하고, 같은 주소값을 갖고 있다. ("신용권"이 리터럴 문자열이기에)

배열 타입

int[] intArray = null; // 배열의 초기값 null로 할당 가능.
intArray[0] = 10; //NullPointerException. => null 인 상태에서 element의 값을 읽거나 할당 불가능. (애초에 힙의 객체가 없으니깐!)
// 결론: 배열 변수는 배열을 생성하고 참조하는 상태에서 값을 저장하거나 읽어야 한다.

값 목록으로 배열 생성

int[] a ={1,2,3};
// 지역 변수 a는 스택에 생성됨. a[0], a[1], a[2]는 객체로써 힙 영역에 생성되고 값이 각각 1,2,3 으로 할당됨.
// 배열 a는 첫번째 요소 a[0]의 시작 주소를 가리킴(참조) 

주의할점:
배열 변수를 이미 선언한 후에 다른 실행문에서 중괄호를 사용한 배열 생성은 허용치 않는다. (컴파일 에러) 이런 경우는 new 연산자를 사용해 값 목록을 지정해주면 된다.

int[] a;
a = {1,2,3,}; // 컴파일에러
a = new int[]{1,2,3}; // new 연산자를 사용하여 초기화 가능.

메소드의 매개값이 배열일 경우에 마찬가지이다.

int add(int[] scores){ ...}  //함수 add 의 매개변수인 배열 scores
//====================================
int result = add({95,85,90}); // 컴파일 에러
int result = add(new int[] {95,85,90}) // 정상작동

new 연산자로 배열 생성

 타입[변수] = new 타입[길이];
 int[] intArray = new int[5]; // intArray[0], intArray[1], intArray[2], intArray[3], intArray[4] 생성.

배열 길이

배열변수.length로 길이측정가능하다.

int[] arr = {1,2,3};
int num = arr.length; // num은 3이다. 

커맨드 라인 입력

public static void main(String[] args)

=> “java 클래스”로 프로그램을 실행하면 JVM은 길이가 0인 String배열을 먼저 생성하고 main() 메소드를 호출할 때 매개값으로 전달한다.

public class Args {
    public static void main(String[] args) {
        int num1 = Integer.parseInt(args[0]);
        int num2 = Integer.parseInt(args[1]);

        int result = num1+num2;
        System.out.println("result: " + result);
    }
}

실행결과
=> javac Args.java
=> java Args 10 20
=> result: 30

다차원 배열

int[][] scores = new int[2][3];

=> java도 배열이 decay가 되는지 물어보기 (될것같다.)

객체를 참조하는 배열

배열 복사

for문 이용

public class Copy {
    public static void main(String[] args) {
        int[] oldArray = {1,2,3,4};
        int[] newArray = new int[8];

        for(int i=0; i < newArray.length; i++){
            if(i<4)
              newArray[i] = oldArray[i];
            else
                newArray[i] = i+1;
            System.out.print(newArray[i] + " ");
        }

    }
}

System.arraycopy() 이용

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
// src는 원본 배열, srcPos는 복사할 항목의 시작 인덱스, dest는 새 배열, destPos는 복사될 시작 인덱스 , length는 복사할 원소 개수.


public class Copy {
    public static void main(String[] args) {
        int[] oldArray = {1,2,3,4};
        int[] newArray = new int[8];

        System.arraycopy(oldArray,0,newArray,0,oldArray.length);
        for(int i=0; i<newArray.length; i++)
            System.out.print(newArray[i] +" ");  // 1 2 3 4 0 0 0 0 
        System.out.println();
        }

} 

향상된 for문

public class Arrays {
    public static void main(String[] args) {
        int sum = 0;
        int[] array = {1,2,3};
        for (int i : array){
            sum +=i;
        }
        System.out.println(sum);
    }
}

열거 타입

열거 타입 선언

열거 타입 변수

public class Enumtest {
    public static void main(String[] args) {
        Week today = Week.FRIDAY; // 메소드로 불러들인 FRIDAY는 메소드 영역에 있는 힙 영역의 FRIDAY 객체를 참조한다.
        // 따라서  변수 today 와 메소드 영역의 열거 상수 Week.FRIDAY 는 FRIDAY 객체를 같이 참조 한다.
        System.out.println(today);
        Week week2 = null; // 열거변수도 reference type 이므로 null 값 지정가능하다.

    }
}

열거 타입의 name() 메소드