220524~0526_혼.공.자 - Ch.5 참조 타입

창고·2022년 10월 14일
0

티스토리에 저장했던 글을 옮겼습니다.
https://mrcocoball.tistory.com/63
https://mrcocoball.tistory.com/64
https://mrcocoball.tistory.com/65

Chapter 5. 참조 타입

1. 참조 타입과 참조 변수

(1) 기본 타입과 참조 타입

  • Java의 데이터 타입은 크게 기본 타입 (primitive type)참조 타입 (reference type) 으로 분류됨
  • 참조 타입에는 배열 타입, 열거 타입, 클래스, 인터페이스가 있음
  • 기본 타입으로 선언된 변수와 참조 타입으로 선언된 변수의 차이점 = 저장되는 값
    • 기본 타입 변수는 실제 값을 변수 안에 저장
    • 참조 타입 변수는 메모리의 번지를 변수 안에 저장, 번지를 통해 객체를 참조
  • 참조 타입 변수의 경우 힙 영역의 객체 번지 값을 가지고 있음
    (예시 : String 클래스 변수는 힙 영역의 String 객체 번지 값을 가지고 있음)

(2) 메모리 사용 영역

  • JVM은 운영체제에서 할당 받은 메모리 영역 (Runtime Data Area) 를 다음과 같이 구분해서 사용

1) 메소드 영역 (Method Area)

  • JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역
  • 코드에서 사용되는 클래스들을 클래스 로더로 읽어 클래스별로 정적 필드(static field)와 상수(constrant),
    메소드 코드, 생성자(constructor) 코드 등을 분류해서 저장

2) 힙 영역 (Heap Area)

  • 객체와 배열이 생성되는 영역
  • 여기에 생성된 객체, 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조
  • 참조하는 변수, 필드가 없다면 의미 없는 객체가 되므로 JVM이 가비지 컬렉터(Garbage Collector)를 실행시켜 자동 제거

3) JVM 스택 영역 (JVM Stack Area)

  • 메소드를 호출할 때마다 프레임(Frame)을 추가(push) 하고 메소드 종료 시 해당 프레임을 제거(pop) 하는 동작 수행
  • 프레임 내부에는 로컬 변수 스택이 있는데 기본 타입 변수 / 참조 타입 변수가 추가되거나 제거됨
  • 스택 영역에 변수가 생성되는 시점은 초기화가 될 때, 즉 최초로 변수에 값이 저장될 때
  • 변수는 선언된 블록 안에서만 스택이 존재, 블록을 벗어나면 스택에서 제거됨
  • 예시
char v1 = 'A'; // ①

if (v1 == 'A') { // ②
  int v2 = 100;
  double v3 = 3.14;
}

boolean v4 = true; // ③

// ① 시점 : v1 생성, 존재
// ② 시점 : v2, v3 생성, v1 존재
// ③ 시점 : v3 생성, v1 존재, v2, v3 소멸
  • 참조 타입 변수는 스택 영역에 힙 영역의 객체 주소를 가짐
  • 예시
int[] scores = {10, 20, 30};

// [스택 영역]                  [힙 영역]
// scores 5번지 → 참조 →  5번지 10, 20, 30

(3) 참조 변수의 ==, != 연산

  • 기본 타입 변수의 ==, != 연산은 변수의 값이 같은지 아닌지를 조사
  • 그러나 참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지 다른 객체를 참조하는지 알아볼 때 사용
  • 동일한 번지값을 가지고 있다면 동일한 객체를 참조하는 것
  • 동일 객체 참조 시 true, 아니면 false
  • 예제
// refVar1는 객체 1을 참조, refVar2, refVar3은 객체2를 참조할 경우

refVar1 == refVar2 는 false
refVar2 == refVar3 은 true

(4) null과 NullPointerException

  • 참조 타입 변수의 경우 힙 영역의 객체를 참조하지 않는다는 의미로 null 값을 가질 수 있음
  • null 값도 초기값이기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성 됨
  • 참조 변수가 null을 가지고 있을 경우 참조 객체가 없으므로 변수를 통해 객체를 사용할 수 없음
  • NullPointerException : null 상태에서 있지도 않은 객체의 데이터(필드)나 메소드를 사용하는 코드 실행 시 발생
  • 예시
int[] intArray = null;
intArray[0] = 10; // <- NullPointerException 발생

String str = null;
System.out.println("총 문자수: " + str.length()); // <- NullPointerException 발생

(5) String 타입

  • 문자열은 String 객체로 생성되고 변수는 String 객체를 참조.
  • 엄밀히 말해 문자열은 String 변수에 저장한다는 말은 틀린 표현이지만 일반적으로는 저장한다고 표현함
  • 예시
String name;
name = "리코";
String hobby = "벽쿵';

// 에서 문자열 리터럴인 "리코"와 "벽쿵"은 힙 영역에 String 객체로 생성됨
// name은 String 객체의 "리코" 를 참조, hobby는 String 객체의 "벽쿵"을 참조 (= 번지 값이 저장, 참조)
  • 문자열 리터럴이 동일할 경우 String 객체를 공유하도록 되어 있음
  • 예시
String name1 = "요시코";
String name2 = "요시코";

// 일 경우 name1과 name2는 동일한 String 객체를 참조하게 되며, name1 == name2 는 true를 반환
  • new 연산자 : 직접 힙 영역에 새로운 객체를 만들며, 객체 생성 연산자라고 함.
String name1= new String("요시코");
String name2 = new STtring("요시코");

// 이 경우 name1과 name2는 서로 다른 객체를 참조하게 되며, name1 == name2는 false를 반환
  • 동일 객체 참조 여부가 아닌, 내부 문자열 비교를 하기 위해서는 equals() 메소드 사용, true or false 반환
원본 문자열.equals(비교 문자열)
  • String 변수는 참조 타입이므로 초기 값으로 null을 대입할 수 있는데
    이 때 null은 String 변수가 참조하는 String 객체가 없다는 뜻
String hobby = "벽쿵"; // hobby 변수가 String 객체를 참조하였으나
hobby = null; // null을 대입함으로써 더 이상 String 객체를 참조하지 않도록 함
// > 이 경우 가비지 컬렉터를 구동, 메모리에서 자동으로 제거됨

2. 배열

(1) 배열이란?

  • 배열 : 같은 타입의 데이터를 연속된 공간에 나열하고 각 데이터에 인덱스를 부여해놓은 자료 구조
  • 배열의 특징
    • 배열은 같은 타입의 데이터만 저장 가능 (int 배열 선언 시 int값만 저장)
    • 한 번 생성된 배열은 길이를 늘리거나 줄일 수 없음
      -> 길이를 늘여야 한다면 새로운 배열을 생성하고 기존 배열 항목을 복사해야 함
      ※ Python 에서는 .append 로 값 추가하면서 늘리기 쌉가능이었는데...

(2) 배열 선언

  • 배열 선언 형식
타입[] 변수;
int[] intArray;
double[] doubleArray;
String[] strArray;
타입 변수[];
int intArray[];
double doubleArray[];
String strArray[];
  • 배열 변수는 참조 변수이기 때문에 참조할 배열 객체가 없다면 null 값으로 초기화 가능
타입[] 변수 = null;

(3) 배열 생성

1) 값 목록으로 생성

  • 타입[] 변수 = { 값0, 값1, 값2, 값3, ... };
String[] names = { "치카", "리코", "요우" };
  • 값 읽기
names[0] > "치카"
names[1] > "리코"
  • 값 변경
names[0] = "요시코";
  • 주의사항 : 배열 변수를 이미 선언한 이후에는 다른 실행문에서 중괄호를 사용한 배열 생성이 허용되지 않음
String[] names;
names = {0,1,2... }; // << 컴파일 에러
  • 배열 변수를 미리 선언한 후에 값 목록들이 나중에 결정되는 경우라면 new 연산자를 사용해서 값 목록 지정
String[] names;
names = new String[] { "치카", "리코", "요우" };
  • 메소드의 매개값이 배열인 경우에도 마찬가지, 매개 변수로 int[] 배열이 선언된 add() 메소드가 있을 경우 값 목록으로 배열을 생성함과 동시에 add() 메소드의 매개값으로 사용하고자 할 때엔 반드시 new 연산자 사용
int add(int[] scores) {...};

int result = add(new int[] {95, 85, 90} );

2) new 연산자로 배열 생성

  • 값의 목록을 가지고 있지 않지만 향후 값들을 저장할 배열을 미리 만들고 싶을 때 사용
  • 타입[] 변수 = new 타입[길이];
String[] names = new String[5]; // 길이가 5인 String[] 배열 생성
  • new 연산자로 배열 첫 생성 시 배열이 자동적으로 기본값으로 초기화됨
    (기본 타입 = 0, 논리 타입 = false, 참조 타입 = null ...)
  • 값 저장
변수[인덱스] =;
names[0] = "하나마루";

(4) 배열 길이

  • 배열의 길이 : 배열에 저장할 수 있는 전체 항목의 개수
  • 길이 확인 : 배열 변수.length;
String[] names = {"하나마루", "요시코", "루비"}'
int num = names.length;
  • length 필드는 읽기 전용 필드로 값을 바꿀 수가 없음
  • length 필드는 for문 등에서 배열 전체를 루핑할 때 자주 사용됨
    (Python 리스트 반복문에서 len() 사용 과 동일)

(5) 명령 라인 입력

  • main() 메소드와 매개값 String[] args
public static void main(String[] args) { ... }

(6) 다차원 배열

  • 지금까지 살펴본 배열은 값 목록으로 구성된 1차원 배열
  • 2차원 배열은 행과 열로 구성되어 있으며, 행렬 같은 모양이며 가로 인덱스와 세로 인덱스 사용
  • JAVA는 중첩 배열 방식으로 2차원 배열을 구현
  • 2(행) x 3(열) 행렬을 만들기 위해 다음과 같이 코드 사용
int[][] scores = new int[2][3];
→행 / ↓열012
0(0,0)(0,1)(0,2)
1(1,0)(1,1)(1,2)
scores.length // > 2 (배열 A, 배열 B와 C가 있음)
scores[0].length // > 3 (배열 B, 내부에 항목 3개가 있음)
scores[1].length // > 3 (배열 C, 내부에 항목 3개가 있음)
  • 계단식 구조
int[][] scores = new int[2][]; // > 2 (배열 A, 배열 B와 C가 있으나 아직 항목 미정)
scores[0] = new int[2]; // (배열 내부에 길이 2짜리 배열 B 생성)
scores[1] = new int[3]; // (배열 내부에 길이 3짜리 배열 C 생성)

scores.length // > 2
scores[0].length // > 2
scores[1].length // > 3
  • 정리하자면 타입[][] 변수 = { {값1, 값2, ...} , {값1, 값,2 ...}, ...};
    즉 배열 안에 배열을 둘 수 있다고 생각하면 됨
    (Python 리스트 요소로 리스트를 둘 수 있는 것과 동일)
  • 예시
int[][] scores = { {95, 90, 95}, {98, 87, 92} }
int score = scores[0][1]; // > 90
int score = scores[1][2]; // > 92

String[][] idols = { {"리코", "요시코", "마리"}, {"치카", "요우", "루비"}, {"하나마루", "카난", "다이아"} };
String idol = idols[0][0]; // > "리코"
String idol = idols[2][1]; // > "카난"
// ※ Python 리스트 내부 리스트의 요소 접근과 동일

(7) 객체를 참조하는 배열

  • 기본 타입은 배열의 각 항목에 직접 값을 갖고 있으나 참조 타입 (클래스, 인터페이스) 배열은
    각 항목에 객체의 번지를 가지고 있음

  • String[] 배열의 경우 각 항목에 문자열이 아닌, String 객체의 번지를 가지고 있으며 String 객체를 참조하게 됨

  • 따라서 String[] 배열의 항목도 결국 String 변수와 동일하게 취급되어야 함

    • 문자열 비교를 위해선 == 연산자가 아닌 equals() 메소드를 사용해야 함
    • ==는 객체의 번지를 비교하기 때문에 문자열 비교에 사용할 수 없음
    • 기본적으로 객체 생성 시 문자열이 같다면 동일한 String 객체로 인식
    • new 연산자로 String 객체 생성 시 새로운 객체가 생성되므로 문자열이 같더라도 다른 개체로 처리
  • 예시

String[] idols = new String[3];
idols[0] = "요시코";
idols[1] = "요시코";
idols[2] = new String("요시코");

System.out.println(idols[0] == idols[1]); // true (같은 객체 참조)
System.out.println(idols[0] == idols[2]); // false (다른 객체 참조)
System.out.println(idols[0].equals(idols[2])); // true (문자열 동일)

(8) 배열 복사

  • 위에서 언급하였듯 배열은 한 번 생성하면 크기를 변경할 수 없으므로 더 많은 저장 공간이 필요하다면
    더 큰 배열을 만들고 이전 배열로부터 항목 값들을 복사해야 함
  • 항목 값을 복사하는데에는 for문을 사용하거나 System.arraycopy() 메소드를 사용

1) for문을 통한 배열 복사

int[] oldIntArray = {1,2,3};
int[] newIntArray = new int[5];

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

2) System.arraycopy() 메소드를 통한 배열 복사

  • 기본 형태
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
// src 매개값은 원본 배열
// srcPos는 원본 배열에서 복사할 항목의 시작 인덱스
// dest 매개값은 새 배열
// destPos는 새 배열에서 붙여넣을 시작 인덱스
// length 는 복사할 개수
  • 예시 : old_array 에서 new_array로 모든 항목을 복사하기 위해서는?
System.arraycopy(old_array, 0, new_array, 0, old_array.length);
String[] oldStrArray = {"치카", "리코", "요우"};
String[] newStrArray = new String[5];

System.arraycopay(oldStrArray, 0, newStrArray, 0, oldStrArray.length);
  • 참조 타입 배열이 복사될 경우, 복사되는 값이 객체의 번지이므로
    새 배열의 항목은 이전 배열의 항목이 참조하는 객체와 동일

(9) 향상된 for문

  • 배열이나 컬렉션을 좀 더 쉽게 처리하기 위해 향상된 for문을 제공
  • 향상된 for문은 반복 실행을 하기 위한 루프 카운터 변수와 증감식을 사용하지 않음
  • 배열 및 컬렉션 항목의 개수만큼 반복하고 자동적으로 for문을 빠져나감
  • 형태
for (타입 변수 : 배열) {

     실행문;
}

//  배열 : for문이 처음 실행될 때 배열에서 가져올 첫 번째 값이 존재하는지 평가, 값이 없을 경우 for문 종료
//  가져올 항목이 있을 경우 해당 값을 타입 변수에 저장
//  실행문 : 타입 변수에 저장한 후 실행문을 실행, 실행문 실행 후 다시 루프를 돌아 배열 1로 돌아감
//  배열 -> 타입 변수 -> 실행문 ... 반복
//  for문의 반복 횟수 = 배열의 항목 수

3. 열거 타입

(1) 열거 타입이란?

  • 열거 타입 : 한정된 값인 열거 상수 (enumeration constant) 중에서 하나의 상수를 저장하는 타입
  • 예시
    Week.java
public enum Week {
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY,
  SUNDAY
}

// Week : 열거 타입 이름
// MONDAY, TUESDAY, ... : 열거 상수
  • Week today; 로 변수를 선언할 수 있음
  • today 변수에 저장할 수 있는 것은 Week에 선언된 7개의 열거 상수 중 하나
    today = Week.FRIDAY;

(2) 열거 타입 선언

  • 열거 타입 선언 전에 열거 타입의 이름을 정하고 해당 이름으로 소스 파일(.java) 생성해야 함
  • 열거 타입 이름 관례 : 첫 글자 대문자, 나머지 소문자, 여러 단어로 구성될 경우 각 단어의 첫글자 대문자 - Week.java / SchoolIdol.java / LoskArk.java
  • 소스 파일의 내용은 다음과 같이 열거 타입 및 열거 상수 선언
    public enum 열거 타입 이름 { 열거 상수(대문자) }
public enum Week { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
  • 열거 상수 이름 관례 : 모두 대문자, 여러 단어 구성 시 단어 사이를 _로 연결
LOGIN_SUCCESS, LOGIN_FAILED

(3) 열거 타입 변수

  • 열거 타입 선언 이후 열거 타입을 사용할 수 있으며, 하나의 타입이므로 변수 선언 후 사용해야 함
  • 선언 방법
열거타입 변수;
Week today;
Week happyBirthday;
  • 열거 타입 변수 선언 시 열거 상수를 저장할 수 있으며 단독으로 사용 불가, 반드시 열거 타입.열거 상수 형태로 진행
열거 타입 변수 = 열거 타입.열거 상수;
Week today = Week.SUNDAY;
Week happyBirthday = Week.SATURDAY;
  • 열거 타입 변수 역시 참조 타입이므로 null 값 저장 가능
  • 열거 상수 역시 열거 객체로 생성되며 열거 타입 변수 내의 열거 상수는 그 갯수만큼 열거 타입 객체로 생성됨
    (Week 내부의 열거 상수 7개는 총 7개의 Week 객체로 생성됨)
  • 메소드 영역에 생성된 열거 상수가 해당 Week 객체를 참조하게 됨
Week today = Week.SUNDAY;

// 열거 타입 변수 today는 스택 영역에 생성, today에 저장된 값은 Week.SUNDAY 열거 상수가 참조하는 객체의 번지
// 따라서 열거 상수 Week.SUNDAY와 today 변수는 서로 같은 Week 객체를 참조
// today == Week.SUNDAY; // ture가 됨
// 이를 응용하게 되면

Week week1 = Week.SATURDAY;
Week week2 = Week.SATURDAY;
System.out.println(week1 == week2); // true
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글