[Java] CH_05 참조 타입

한지혜·2025년 2월 7일

데이터 타입 분류

자바의 데이터 타입은 크게 기본 타입primitive참조 타입reference으로 분류된다.

기본 타입: 정수(byte,char,short,int,long), 실수(float,double), 논리
참조 타입: 배열, 열거, 클래스, 인터페이스

둘의 차이점은 저장되는 값이다.

  • 기본 타입: 값 자체를 저장
  • 참조 타입: 객체가 생성된 메모리 번지를 저장
    • 변수들은 모두 스택stack이라는 메모리 영역에 생성된다.
// 기본 타입 변수
int age = 25;
double price = 100.5;
// 참조 타입 변수
String name = "신용권";
String hobby = "독서";


자바가 사용하는 메모리 영역

java명령어로 JVM이 구동되면 JVM은 운영체제에서 할당받은 메모리 영역을 다음과 같이 구분해서 사용한다.

  • 메소드 영역
    • 바이트코드 파일의 내용이 저장되는 영역
    • 클래스별로 상수, 정적 필드, 메소드 코드, 생성자 코드 등이 저장됨
  • 힙 영역
    • 객체가 생성되는 영역
    • 객체의 번지는 메소드 영역과 스택 영역의 상수와 변수에서 참조 가능
  • 스택 영역
    • 메소드를 호출할 때마다 생성되는 프레임이 저장되는 영역
    • 메소드 호출이 끝나면 프레임은 자동 제거됨
    • 프레임 내부에는 로컬 변수 스택이 있음
      • 여기에서 변수가 생성되고 제거됨

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

  • ==, !=연산자는 변수의 값이 동일한지 조사한다.
  • 참조 타입 변수의 값은 객체의 번지이다.
  • 따라서 ==, !=연산자는 동일한 객체를 참조하는지 조사한다.

null과 nullPointerException

null

참조 타입 변수는 null값을 가질 수 있다.(아직 번지를 저장하고 있지 않음)

null로 초기화 된 참조 변수는 스택 영역에 생성된다.

참조 타입 변수가 null인지 검사: ==, !=연산자 사용

nullPointerException

  • 참조 변수를 사용하면서 가장 많이 발생하는 예외
  • 변수가 null인 상태에서 객체의 데이터나 메소드를 사용하려 할 때 발생

Garbage Collector

  • 자바는 코드를 이용해서 객체를 직접 제거하는 방법을 제공하지 않는다.
  • 따라서 객체를 제거하는 유일한 방법은 객체의 모든 참조를 없애는 것이다.
  • 이를 위해 일부러 null을 대입하기도 한다.

    null을 대입 -> 어떤 변수에서도 객체를 참조하지 않음 -> 해당 객체는 쓰레기가 됨 -> Garbage Collector를 실행시켜 자동으로 제거함

String hobby = "travel";
hobby = null; // "travel"객체는 쓰레기가 됨

String cat1 = "lui";
String cat2 = cat1;
cat1 = null; // "lui"객체는 cat2에서 참조하고 있으므로 쓰레기가 되지 않음

문자열

문자열 선언과 초기화

문자열은 참조 타입이다.

선언
문자열은 String 타입을 사용하므로 String 타입의 변수를 먼저 선언해야 한다.

초기화
1. 큰 따옴표(")로 감싼 문자열 리터럴을 사용해 초기화한다.
2. new 연산자로 새로운 문자열 객체를 생성해 초기화한다.

자바는 문자열 리터럴을 내부적으로 String 클래스의 객체로 처리한다.

String name1;	// 문자열 선언
name1 = "문자열";	// 문자열 초기화

String name2 = "문자열";	// 문자열 선언과 초기화

String name3 = new String("문자열"); // new 연산자를 사용해 초기화
  • name1 변수가 가리키는 "문자열" 리터럴은 내부적으로 new String("문자열")를 호출해서 생성한 객체이다. 그러나 내용이 같은 문자열 리터럴이라면 더 이상 새로운 String 객체를 생성하지 않고 기존 리터럴을 공유한다. 따라서 name1name2는 동일한 문자열 리터럴을 가리킨다.

  • name3는 new 연산자로 생성한 객체를 사용해 초기화한 변수이므로 name1, name2과 다른 객체를 가리킨다.


문자열의 비교

== 또는 != 연산자는 두 문자열의 내용이 아닌 동일한 객체인지를 검사한다.

두 문자열의 내용을 비교하기 위해서는 String 클래스에서 제공하는 문자열 비교 메서드를 사용해야 한다.

두 문자열이 동일한 객체인지 비교(==, !=)

name1 == name2 // true
name2 == name3 // false
name1 != name3 // true

두 문자열의 내용 비교(String 클래스의 메서드)

  • String 클래스에서 제공하는 문자열 비교 메서드
String s1 = "Hi, Java";
String s2 = "hi, java";

// 문자열을 사전 순으로 비교 -> 정숫값(int) 반환
s1.compareTo(s2); // -32

// 대소문자 무시하고 문자열을 사전 순으로 비교 -> 정숫값(int) 반환
s1.compareToIgnoreCase(s2); // 0

// 문자열s2와 현재 문자열(s1)을 비교 -> true/false(boolean) 반환
s1.equals(s2); // false

// 대소문자 무시하고 문자열s2와 현재 문자열(s1)을 비교 -> true/false(boolean) 반환
s1.equalsIgnoreCase(s2); // true

문자열의 다양한 연산

▼ String 클래스에서 제공하는 유용한 메서드

String s1 = "Apple ";
String s2 = "Banana";

s1.charAt(1); 	// index 1 문자를 반환 -> p
s1.concat(s2); 	// s1 뒤에 s2 연결 -> Apple Banana
s1.contains("a"); 	// 문자열 "a"를 포함하는지 조사 -> false
s1.endsWith("e"); 	// 끝나는 문자열이 "e"인지 조사 -> false
s1.indexOf("b"); 	// 문자열 "b"가 나타난 위치를 반환 -> 없으면 -1
s1.repeat(2); 	// 2번 반복한 문자열을 반환 -> Apple Apple 
s1.replace("Apple", "JAVA"); // 특정 문자열을 다른 문자열로 대체
s1.startsWith("a"); 	// 시작하는 문자열이 "a"인지 조사(대소문자구분) -> false
s1.substring(3); 	// index 3 부터 시작하는 문자열의 일부를 반환 -> le
s1.substring(2, 5); 	// index 2 부터 (5-1)까지 문자열을 반환 -> ple
s1.toLowerCase(); 	// 모두 소문자로 변환 -> apple 
s1.toUpperCase(); 	// 모두 대문자로 변환 -> APPLE 
s1.trim(); 	// 앞뒤에 있는 공백 모두 제거한 후 반환 -> Apple
s1.isBlank(); 	// 길이가 0 혹은 공백 있으면 true 반환 -> false
s1.isEmpty(); 	// 길이가 0이면 true 반환 -> false
s1.length; 	// s1의 길이를 반환 -> 6

▼ 문자열 분리

  • 문자열이 구분자를 사용하여 여러 개의 문자열로 구성되어 있을 경우, 이를 따로 분리해서 얻고 싶다면 split()메소드를 사용한다.
  • split()메소드는 배열(array)을 반환한다.
String board = "번호, 제목, 내용, 글쓴이"; // 구분자 == ","
String[] arr = board.split(",");
			// {"번호", "제목", "내용", "글쓴이"}

▼ String 클래스에서 제공하는 유용한 "정적" 메서드

String term = String.format("%d - %d %s", 24, 1, "학기"); // 24 - 1 학기
String animals = String.join(", ", "cats", "dogs", "sudal", "panda"); 
				// cats, dogs, sudal, panda
String pi = String.valueOf(3.14); // (문자열)3.14

배열

  • 동일한 데이터 타입의 집합이다.
  • 연속된 공간에 값을 나열시키고, 각 값에 인덱스를 부여해 놓은 자료구조이다.

    배열 변수는 참조 변수이다.
    배열도 객체이므로 힙 영역에 생성되고 배열 변수는 힙 영역의 배열 주소를 저장한다.
    참조할 배열이 없다면 배열 변수도 null로 초기화할 수 있다.

배열의 특징

  • 배열은 같은 타입의 값만 관리한다.
  • 배열의 길이는 늘리거나 줄일 수 없다.

배열의 선언과 생성

  • 배열 변수를 미리 선언한 후에는 {}로 감싼 값의 목록을 변수에 대입할 수 없다.
// 선언
int[] intArray;

// 선언 후 값 목록을 대입할 때 -> 'new 타입[]'을 앞에 붙임
String[] names = null;
names = new String[] {"kim", "Ryu", "Han"};

// 값 목록으로 생성
String[] season = {"Spring", "Summer", "Fall", "Winter"};

// new 연산자로 배열 생성
//   값의 목록은 없지만 향후 값들을 저장할 목적으로 배열을 미리 생성할 수 있다.
int[] scores = new int[10];
//   new 연산자로 생성한 배열은 배열 변수가 선언된 후에도 대입 가능하다.
int[] scores = null;
scores = new int[10];

메소드의 매개변수가 배열 타입일 경우도 마찬가지

void printItem(int[] scores) { ... } // 메소드 선언
printItem( {95, 82, 67} ); // 컴파일 에러
printItem( new int[] {95, 82, 67} ); // 올바른 메소드 호출

new 연산자로 배열을 처음 생성하면, 배열 항목은 기본값으로 초기화된다.
정수 배열은 0, 실수 배열은0.0, 논리 배열은 false, 참조 배열은 null

  • 배열의 크기는 배열이 생성될 때 정해지며, length 필드에 저장한다.
    • length 필드는 읽기만 가능하다.
    intArray.length = 10; // 컴파일 에러 발생 !
    • for문과 함께 많이 사용. 배열의 인덱스를 초과하여 사용하면 ArrayIndexOutOfBoundsException 발생

다차원 배열

1차원 배열을 서로 연결한 것

위 그림에서 값1, 값3, 값6을 읽는 법

// 항목에 접근하는 방법: 변수[1차원인덱스][2차원인덱스] ... [N차원인덱스]
값1: 변수[0][0][0] 
값3: 변수[0][1][0]
값6: 변수[1][0][1]

다차원 배열 생성

1. 값 목록으로 다차원 배열 생성

  • 배열 변수 선언 시 타입 뒤에 대괄호 []를 차원의 수만큼 붙이고, 값 목록도 차원의 수만큼 중괄호{}를 중첩시킨다.

예제: 1반과 2반의 점수 저장

int[][] scores = {
	{ 90, 80, 89 },
    { 76, 88 }
}
  • 각 반은 1차원 배열, 해당 반 학생 점수는 2차원 배열
    • 반의 개수 == 1차원 배열의 길이 scores.length
    • 학생 수 == 2차원 배열의 길이 scores[0].length, scores[1].length

2. new 연산자로 다차원 배열 생성

  • int[][] scores = new int[2][3];
    2차원 배열의 길이는 모두 3이고, 항목들은 0으로 초기화된다.

  • String[][] names = new String[2][3];
    2차원 배열의 길이는 모두 3이고, 항목들은 null로 초기화된다.

  • 2차원 배열의 길이를 다르게 주려면?

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

객체를 참조하는 배열

기본 타입 배열은 각 항목에 값을 직접 저장한다.
참조 타입(클래스, 인터페이스) 배열은 각 항목에 객체의 번지를 저장한다.

String[] languages = new String[3];
languages[0] = "Java";
languages[1] = "Java";
languages[2] = new String("Java");

System.out.println(languages[0] == languages[1]); // true
System.out.println(languages[0] == languages[2]); // false
System.out.println(languages[0].equals(languages[2])); // true

배열 복사

배열은 한 번 생성하면 길이를 변경할 수 없다.
더 많은 저장 공간이 필요하다면 더 큰 길이의 배열을 새로 만들고 이전 배열로부터 항목들을 복사해야 한다.

방법 1. for문 사용

int[] oldArray = { 1, 2, 3 };
int[] newArray = new int[5];

// 배열 항목 복사
for (i=0; i<oldArray.length; i++) {
	newArray[i] = oldArray[i];
}

// oldArray { 1, 2, 3, 0, 0 }

방법2. System의 arraycopy() 메소드 이용

System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
// (원본배열, 원본배열복사시작인덱스, 새배열, 새배열붙여넣기시작인덱스,복사항목수)

향상된 for문

int[] scores = { 95, 71, 84, 93, 87 };
int sum = 0;
// enhanced for
for (int score : scores) {
	sum += score;
}
System.out.println("점수 총합: " + sum);

열거(Enum) 타입

한정된 값을 갖는 타입

  • 열거 타입을 사용하기 위해서는 먼저 열거 타입 이름으로 소스 파일(.java)을 생성하고 한정된 값을 코드로 정의해야 한다.
  • 열거 타입 이름은 첫 문자는 대문자, 캐멀 스타일
  • 열거 상수 목록은 관례적으로 모두 대문자로 작성, 여러 단어일 경우 언더바_로 연결
public enum week {
	// 열거 상수 목록
	MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}
Week today = Week.SUNDAY;
Week birthday = null;

today == Week.SUNDAY; // true

profile
공부

0개의 댓글