티스토리 뷰

반응형

5.Chapter05 : 참조 타입

5.1 데이터 타입 분류

  자바의 데이터 타입에는 크게 기본타입(원시 타입 : primitive type)과 참조 타입(reference type)으로 분류된다.

기본 타입이란 정수, 실수, 문자, 논리 리터럴을 저장하는 타입을 말한다. 참조 타입이란 객체(Object)의 번지를 참조하는 타입으로 배열, 열거, 클래스, 인터페이스 타입을 말한다.

 기본 타입으로 선언된 변수와 참조 타입으로 선언된 변수의 차이점은 저장되는 값이 무엇이냐이다. 기본 타입은 실제 값을 변수안에 저장하지만, 참조 타입은 선언된 변수의 메모리의 번지를 값으로 갖는다. 번지를 통해 객체를 참조한다는 뜻에서 참조 타입이라고 부른다. 변수는 스택영역에 생성되고 객체는 힙 영역에 생성된다.

[기본 타입 변수]
int age = 25;
double price = 100.5;

[참조 타입 변수]
String name = "신용권";
String hobby = "독서";

5.2 메모리 사용 영역

  JVM은 운영체제에서 할당받은 메모리 영역(Runtime Data Area)을 다음과 같이 세부 영역으로 구분해서 사용한다.

5.2.1 메소드(Method) 영역

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

 

힙(Heap) 영역

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

JVM 스택(Stack) 영역

JVM 스택 영역은 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 할당된다. 자바 프로그램에서 추가적으로
스레드를 생성하지 않았다면 main 스레드만 존재하므로 JVM 스택도 하나이다. JVM스택은 메소드를 호출할 때
마다 프레임(Frame)을 추가(Push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행한다.
예외발생 시 printStackTrace()메소드로 보여주는 Stack Trace의 각 라인은 하나의 프레임을 표현한다.
프레임 내부에는 로컬 변수 스택이 있는데, 기본 타입 변수와 참조 타입 변수가 추가(push)되거나
제거(pop)된다. 변수가 이 영역에 생성되는 시점은 초기화가 될 때, 즉 최초로 변수에 값이 저장될 떄 이다.
변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거된다. 
ex)
char v1 = 'A';

if(v1 == 'A'){
int v2 = 100;
double v3 = 3.14;
}
boolean v4 = true;

선언된 변수는 실행 순서에 따라 스택에서 생성되고 소멸된다. v2, v3는 블록 내부가 실행되고 있을 떄만
스택 영역에 존재하고 실행 흐름이 if 블록을 빠져나가면 소멸된다.

스택의 생성 및 소멸

기본 타입 변수는 스택 영역에 직접 값을 가지고 있지만, 참조 타입 변수는 값이 아니라 힙 영역이나 메소드 영역의 객체 주소를 가진다.

int[] scores = {10, 20, 30};
배열 변수인 scores는 스택 영역에 생성되지만, 실제 10,20,30을 갖는 배열은 힙 영역에 생성된다.
배열 변수 scores에는 배열의 힙 영역의 주소가 저장된다. 자바에선 배열을 객체로 취급한다.

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

  기본 타입 변수의 ==, != 연산은 변수의 값이 같은지, 아닌지를 조사하지만 참조 타입 변수들 간의 ==,!= 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 떄 사용된다. 참조 타입의 변수의 값은 결국 주소 값을 비교하는 것이다. 객체를 비교하는 코드는 일반적으로 if문에서 많이 사용된다.

ex) if( refVar2 == refVar3) {...}

 

5.4 null과 NullPointerException

  참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null 값을 가질 수 있다. null 값도 초기 값으로 사용할 수 있기에, null로 초기화된 참조 변수는 스택 영역에 생성된다.

ex) 
refVar1 == null 결과 false
refVar1 != null 결과 true

refVar2 == null 결과 true
refVar2 != null 결과 false

  자바는 프로그램 실행 도중에 발생하는 오류를 예외(Exception)라고 부른다. 예외는 사용자의 잘못된 입력으로 발생할 수 있고, 프로그래머가 코드를 잘못 작성해서 발생할 수 있다. 참조 변수를 사용하면서 가장 많이 발생하는 예외 중 하나로 NullPointException이 있다. 이 예외는 참조 타입 변수를 잘못 사용하면 발생한다. 참조 타입 변수가 null을 가지고 있을 경우, 참조 타입 변수는 사용할 수 없다. 참조할 객체가 없으므로 사용할 수 없는 것이다.

int[] intArray = null;
intArray[0] = 10;  // NullPointerException 발생

intArray 변수가 참조하는 배열 객체가 없기 때문에 NullPointerException 발생

String str = null;
System.out.println(str.length()); //NullPointerException

5.5 String 타입

 

자바는 문자열을 String 변수에 다음과 같이 String 변수를 우선 선언해야 한다.
- String 변수;
String 변수에 문자열을 저장하려면 큰 따옴표로 감싼 문자열 리터럴을 대입하면 된다.
- 변수 = "문자열";
변수 선언과 동시에 문자열을 저장할 수도 있다.
- String 변수 = "문자열";
다음은 두 개의 String 변수를 선언하고 문자열을 저장한다.
- String name;
- name = "신용권";
- String hobby = "자바";

사실 문자열을 String 변수에 저장한다는 말은 틀린 표현이다. 문자열이 직접 변수에 저장되는 거싱 아니라,
문자열은 String 객체로 생성되고 변수는 String 객체를 참조한다.

자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 되어 있다.  다음과 같이 name1과 name2
변수가 동일한 문자열 리터럴인 "신용권"을 참조할 경우 name1과 name2는 동일한 String 객체 참조.
String name1 = "신용권";
String name2 = "신용권";

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

String name 1 = new String("신용권");
String name 2 = new STring("신용권");

이경우 name1과 name2는 서로 다른 String 객체를 참조한다.

문자열 리터럴로 생성하느냐 new 연산자로 생성하느냐에 따라 비교 연산자의 결과가 달라질 수 있따.
String name1 = "신용권";
String name2 = "신용권";
String name3 = new String("신용권");

name1 == name2 -> true
name1 == name3 -> false
name2 == name3 -> false

동일한 String이건 다른 String 객체이건 상관없이 문자열만을 비교할 때는 String 객체의 equals()메소드
를 사용해야 한다. equals() 메소드는 원본 문자열과 매개값으로 주어진 비교 문자열이 동일한지 비교한 후
true 또는 false를 리턴한다.
ex)
boolean result = name1.equalse(name3);  -> true
다음 코드처럼 hobby 변수가 String 객체를 참조하였으나, null을 대입함으로써 더 이상 String 객체를
참조하지 않도록 할 수도 있다.
String hobby = "여행";
hobby = null;

참조를 잃은 String 객체는, JVM이 참조되지 않은 객체를 쓰레기 객체로 취급하고
쓰레기 수집기(Garbage Collector)를 구동시켜 메모리에서 자동 제거한다.

5.6 배열 타입

5.6.1 배열이란?

  배열이란 index-data 기반의 자료형으로 '같은 자료형을 모아 그룹화 한것'이라고 말할 수 있다. 배열의 index는 무조건 0부터 시작한다. 배열은 선언과 동시에 저장할 수 있는 데이터 타입이 결정된다. 만약 다른 타입의 값을 저장하려면 타입 불일치 (Type mismatch) 컴파일 오류가 발생한다. 또한, 한번 생성된 배열은 길이를 늘리거나 줄일 수 없다. 즉 3개의 값을 저장하는 배열 생성 시, 나중에 5개로 수정할 수 없다. 새로운 배열을 생성하거나, 기존 배열을 새 배열로 복사 해야 한다.

 배열의 인덱스는 각 항목의 데이터를 읽거나, 저장하는데 사용되며 배열 이름 옆에 대괄호[]에 기입된다.

ex) socre[]

 

5.6.2 배열 선언 

배열 선언은 두가지 형태로 작성 가능하다.
타입[] 변수;  , 타입 변수 [];
ex)
int[] intArray;  int intArray[];

배열 변수는 참조 변수에 속한다. 배열도 객체이므로 힙 영역에 생성되고, 배열 변수는 힙 영역의 배열 객체를
참조하게 된다. 참조할 배열 객체가 없다면 배열 변수는 null 값으로 초기화될 수 있다.

5.6.3 값 목록으로 배열 생성

배열 항목에 저장될 값의 목록이 있다면, 다음과 같이 간단하게 배열 객체를 만들 수 있다.
데이터타입[] 변수 = {값0, 값1, 값2...};
ex) String[] names = {"신용권", "홍길동", "감자바"};
이렇게 생성된 배열에서 "신용권"은 names[0], "홍길동"은 names[1]로 읽을 수 있다.
names[1]의 "홍길동"을 "홍삼원"으로 바꾸고 싶다면 아래와 같은 대입 연산자를 쓰면 된다.
ex) names[1] = "홍삼원";

배열 변수를 이미 선언한 후에 다른 실행문에서 중골호를 사용한 배열 생성은 허용되지 않는다.
타입[] 변수;
변수 = {값0, 값1, 값2}; //컴파일 에러

배열변수를 미리 선언한 후, 값 목록들이 나중에 결정되는 상황이라면 new 연산자를 사용하면 된다.
변수 = new 타입[] {값0, 값1};
ex) 
String[] names = null;
names = new String[]{"신용권,"홍길동"};

메소드의 매가값이 배열일 경우도 동일
ex)
int result = add({95,85}); //컴파일 에러
int result = add(new int[]{95,85}); //정상 동작

5.6.4 new 연산자로 배열 생성

값의 목록을 가지고 있지 않지만, 향후 값들을 저장할 배열을 미리 만들고 싶다면 new 연산자를 사용한다.
ex)
타입 []변수 = new 타입[길이];
또는
타입[]변수 = null;
변수 = new 타입[길이];

new 연산자로 배열 처음 생성시, 배열은 자동적으로 기본값으로 초기화 한다.
ex) int 타입일시 0으로 초기화, String 타입일시 null로 초기화


5.6.5 배열 길이

배열의 길이란 배열에 저장할 수 있는 전체 항목 수를 말한다.
배열변수.length;
ex) 
int[] intArray = {10,20,30};
int num = intARray.length; //3
length 필드는 읽기 전용 필드이기 때문에 값을 바꿀 수 없다.

for문을 사용하여 배열 전체를 루핑할 때 유용하게 사용할 수 있다.
ex)
for(int i=0; i<intArray.length; i++){
	sum += intArray[i];
}

조건식에서 < 연산자를 사용하는 이유는 배열의 마지막 인덱스는 배열 길이보다 1이 적기 때문이다.
배열의 인덱스 범위는 0 부터 시작이다. 그러나 length 길이는 3개의 값이 있기 때문에 3이 저장된다.
고로 인덱스 범위는 legnth-1

5.6.6 커맨드 라인 입력

   프로그램 실행시 main()메소드가 필요하다. 하지만 main()메소드의 매가값인 String[]args가 왜 필요한지는 알지 못하였다. "java 클래스"로 프로그램을 실행하면 JVM은 길이가 0인 String 배열을 먼저 생성하고 main()메소드를 호출할 떄 매가값으로 전달한다.

"java 클래스"뒤에 공백으로 구분된 문자열 목록을 주고 실행하면, 문자열 목록으로 구성된 String[]배열이 생성되고 main() 메소드를 호출할 때 매개값으로 전달된다.

main()메소드는 String[] args 매개 변수를 통해서 커맨드 라인에서 입력된 데이터수(배열의 길이)와 입력된 데이터(배열의 항목 값)를 알 수 있게 된다.

 

5.6.7 다차원 배열

  지금까지 알아본 배열은 1차원 배열이다. 이와 달리 값들이 행과 열로서 구성된 배열을 2차원 배열이라 한다. 자바는 2차원 배열을 중첩 배열 방식으로 구현한다. 2행 x 3열 행렬을 만들기 위한 코드

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

 

배열 변수인 scroes는 길이 2인 배열 A를 참조한다. 배열 A의 scores[0]은 다시 길이 3인 배열 B를 참조한다.
그리고 scores[1]역시 길이 3인 배열 C를 참조한다. scores[0]과 scores[1]은 모두 배열을 참조하는
변수 역할을 한다. 
scores.length //2 배열 A의 길이
scores.[0].length //3 배열 B의 길이
scores.[1].length //3 배열 C의 길이

ex)
int [][] scores = { {95,80},{92,96} };

int score = scores[0][0]; //95
int score = scores[1][1]; //96

5.6.8 객체를 참조하는 배열

기본타입(byte, char, short, int, long, float, double, boolean)배열은 각 항목에 직접 값을 갖고
있지만, 참조타입(클래스, 인터페이스) 배열은 각 항목에 객체의 번지를 가지고 있다.
그렇기에 만약 String[] 배열 항목간에 문자열을 비교하기 위해서는 ==연산자 대신 equals()메소드를 
사용해야 한다. ==는 객체의 번지 비교이기 때문에 문자열 비교에 사용할 수 없다.

5.6.9 배열 복사

배열은 한번 생성하면 크기를 변경할 수 없기 때문에 더 많은 저장 공간이 필요하다면, 보다 큰 배열을 만들고
이전 배열로부터 항목 값들을 복사해야 한다.
복사하려면 for문을 사용하여 옮겨주거나, System.arraycopy() 메소드를 사용하면 된다.

System.arraycopy()를 호출하는 방법

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
src : 원본 배열
srcPos : 원본 배열에서 복사할 항목의 시작 인덱스
dest : 새 배열
destPos : 새 배열에서 붙여넣을 시작 인덱스
length : 복사할 개수

복사되지 않은 항목은 배열의 기본 값으로 유지된다.

5.6.10 향상된 for문

향상된 for문은 foreach문이라고 한다. 반복실행을 하기 위해 카운터 변수와 증감식을 사용하지 않는다.
배열 및 컬렉션 항목의 개수만큼 반복하고, 자동적으로 for문을 빠져 나간다.

5.7 열거 타입

데이터 중에는 몇 가지로 한정된 값만을 갖는 경우가 흔히 있다.
ex) 요일 (월,화,수,목,금,토,일)
이와 같이 한정된 값만을 갖는 데이터 타입이 열거 타입(enumeration type)이다.

5.7.1 열거 타입 선언

열거 타입 이름은 관례적으로 첫문자를 대문자로 하고 나머지는 소문자로 구성한다.
ex) Week.java
public enum Week{
	MONDAY,
    TUESDAY,
    ......
    SUNDAY
 };

열거상수인 MONDAY, TUESDAY 등은 열거 타입의 값으로 사용되는데, 관례적으로 모두 대문자로 작성한다.
만약 열거 상수가 여러 단어로 구성될 경우네느 단어 사이를 밑줄(_)로 연결하는 것이 관례이다.
ex) LOGIN_SUCCESS

    

5.7.2 열거 타입 변수

열거 타입도 하나의 데이터 타입이므로 변수를 선언하고 사용해야 한다.
ex)
Week today;

열거 타입 변수를 선언했다면 열거 상수를 저장할 수 있다. 열거 상수는 단독으로 사용할 수 없다 반드시
열거 타입, 열거상수로 사용된다.
ex)
Week today = Week.MONDAY;

열거 타입 변수도 참조타입이기 때문에 null 값을 저장할 수 있다.
Week birthday = null;

열거타입 Week의 경우 MONDAY부터 SUNDAY까지의 열거 상수는 다음과 같이 총 7개의 Week객체로 생성된다.
그리고 메소드 영역에 생성된 열거 상수가 해당 Week 객체를 각각 참조하게 된다.

ex) 
Week today = Week.SUNDAY;
today == Week.SUNDAY // true
열거 타입 변수 today는 스택 영역에 생성된다. today에 저장되는 값은 Week.SUNDAY 열거 상수가 참조하는
객체의 번지이다. 따라서 열거 상수 Week.SUNDAY와 today 변수는 서로 같은 Week 객체를 참조하게 된다.
그러므로 연산결과는 true이다.

5.7.3 열거 객체의 메소드

리턴타입 메소드(매개 변수) 설명
String name() 열거 객체의 문자열을 리턴
int ordinal() 열거 객체의 순번(0부터 시작)을 리턴
int compareTo() 열거 객체를 비교해서 순번차이를 리턴
열거 타입 valueOf(String name) 주어진 문자열의 열거 객체를 리턴
열거 배열 values() 모든 열거 객체들을 배열로 리턴
- name() 메소드
열거 객체가 가지고 있는 문자열을 리턴한다. 
Week today = Week.SUNDAY;
String name = today.name(); //SUNDAY를 name 변수에 저장한다.

- ordinal() 메소드
전체 열거 객체 중 몇 번째 열거 객체인지 알려준다.
0부터 시작한다.
Week today = Week.SUNDAY;
int ordinal = today.ordinal(); // 0부터 시작이기에 6을 ordinal에 저장한다.

- compareTo() 메소드
 매개값으로 주어진 열거 객체를 기준으로 전후로 몇 번째 위치하는 지를 비교한다. 만약 열거 객체가
 매개값의 열거 객체보다 순번이 빠르다면 음수가, 순번이 늦다면 양수가 리턴된다.
 
 Week day1 = Week.MONDAY;
 Week day2 = Week.WEDNESDAY;
 
 int result1 = day1.compareTo(day2); //-2 monday(0) wednesday(2)의 순번차이가 2이므로 
 int result2 = day2.compareTo(day1); //2
 
 -valueOf() 메소드
 매개값으로 주어진 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다. 외부로부터 문자열을 입력받아
 열거 객체로 변환할 때 유용하게 사용할 수 있다.
 Week weekDay = Week.valueOf("SUNDAY");
 
 -values() 메소드
 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다. 
 Week[] days = Week.values();
 
반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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
글 보관함