[자바의 정석] 배열(array)

Jiwon An·2024년 2월 4일
0

Java

목록 보기
9/9

배열

배열은 같은 타입의 여러 변수를 하나의 묶음으로 다루는 것

많은 양의 데이터를 저장하기 위해, 그 데이터의 숫자만큼 변수를 선언해야 한다면 매우 혼란스러울 것입니다. 이런 경우에 배열을 사용하면 많은 양의 데이터를 손쉽게 다룰 수 있습니다.

중요한 것은 '같은 타입'이어야 하고, 서로 다른 타입이 변수들로 구성된 배열은 만들 수 없습니다. 또한 변수와 달리 배열은 각 저장공간이 연속적으로 배치되어 있다는 특징이 있습니다.

배열의 선언과 생성

배열의 선언

선언방법선언 예
타입[] 변수이름;int[] score;
String[] name;
타입 변수이름[];int score[];
String name[];

배열의 생성

배열을 선언한 다음에는 배열을 생성해야합니다. 배열을 선언하는 것은 단지 생성된 배열을 다루기 위한 참조변수를 위한 공간이 만들어질 뿐이고, 배열을 생성해야만 비로소 값을 저장할 수 있는 공간이 만들어집니다.

타입[] 변수이름;			// 배열을 선언 (배열을 다루기 위한 참조변수 선언)
변수이름 = new 타입[길이]; 	// 배열을 생성 (실제 저장공간을 생성) 

배열의 선언과 생성을 동시에 하면 간략히 한 줄로 할 수 있는데, 대부분의 경우 이렇게 합니다.

타입[] 변수이름 = new 타입[길이];	// 배열을 선언과 생성을 동시에
int[] score = new int[5]; 	// 길이가 5인 int배열

배열의 선언과 생성과정

  1. int[] score;
    int형 배열 참조변수 score를 선언한다. 데이터를 저장할 수 있는 공간은 아직 마련되지 않았다.

  2. score = new int[5];
    연산자 'new'에 의해서 메모리의 빈 공간에 5개의 int형 데이터를 저장할 수 있는 공간이 마련된다. 그리고 각 배열요소는 자동적으로 int의 기본값(default)인 0으로 초기화 된다.
    끝으로 대입 연산자'='에 의해 배열의 주소가 int형 배열 참조변수 score에 저장된다. 만약 배열이 주소 0x100번지에 생성되었다고 하면, 참조변수 score에 0x100에 저장된다.

이제 참조변수 score를 통해서 생성된 배열에 값을 저장하거나 읽어 올 수 있습니다. 이 배열은 '길이가 5인 int배열'이며, 참조변수의 이름을 따서 '배열 score'라고 부르면 됩니다.

배열의 길이와 인덱스

배열의 요소(element)는 생성된 배열의 각 저장공간이며, '배열이름[인덱스]'의 형식으로 배열의 요소에 접근합니다. 인덱스(index)는 배열의 요소마다 붙여진 일련번호로 각 요소를 구별하는 데 사용합니다.

인덱스의 범위는 0부터 '배열길이 -1'까지입니다. 예를 들어 길이가 5인 배열은 모두 5개의 요소(저장공간)를 가지며 인덱스의 범위는 1부터 5까지가 아닌 0부터 4까지, 즉 0, 1, 2, 3, 4가 됩니다.

배열에 값을 저장하고 읽어오는 방법은 변수와 같습니다. 단지 변수이름 대신 '배열이름[인덱스]'를 사용한다는 점만 다릅니다.

score[3] = 100; 	  // 배열 score의 4번째 요소에 100을 저장
int value = score[3]; // 배열 score의 4번째 요소에 저장된 값을 읽어서 value에 저장

for문과 배열

배열의 또 다른 장점은 index로 상수 대신 변수나 수식도 사용할 수 있다는 것입니다. 그래서 왼쪽의 코드를 오른쪽과 같이 for문을 이용해서 간단히 할 수 있습니다.

score[0] = 0; 				for(int i=0;i<5;i++) {
score[1] = 10;					score[i] = i * 10;
score[2] = 20;				}
score[3] = 30;
score[4] = 40;

for문의 제어변수 i는 배열의 index로 사용하기에 딱 알맞아서, 배열을 다룰 때 for문은 거의 필수적입니다. 만일 괄호[]안에 수식이 포함된 경우, 이 수식이 먼저 계산됩니다. 그래야만 배열의 몇 번째 요소인지 알 수 있기 때문입니다.

주의할 점

배열을 다룰 때는 index의 범위를 벗어난 값을 index로 사용하지 않아야 합니다.

int[] score = new int[5]; // 길이가 5인 int배열. index의 범위는 0~4
...
score[5] = 100; // index 범위를 벗어난 값을 index로 사용

컴파일러는 이러한 실수를 걸러주지 못합니다. 왜냐하면 배열의 index로 변수를 많이 사용하는데, 변수의 값은 실행 시에 대입되므로 컴파일러는 이 값의 범위를 확인할 수 없습니다.
그래서 유효한 범위의 값을 index로 사용하는 것은 전적으로 프로그래머의 책임이며, 유효하지 않은 값을 index로 사용하면, 무사히 컴파일을 마쳤더라도 실행 시에 에러(ArrayIndexOutOfBoundsException)가 발생합니다.

배열의 길이

배열의 길이는 배열의 요소의 개수, 즉 값을 저장할 수 있는 공간의 개수입니다. 당연하게도 배열의 길이는 양의 정수이어야 하며 최대값은 int타입의 최대값, 약 20억입니다. 실제로 이렇게 큰 배열을 생성하는 경우는 꽤 드무니까 배열의 길이는 거의 제약이 없다고 할 수 있습니다.

길이가 0인 배열도 생성이 가능합니다. 길이가 0이라는 얘기는 값을 저장할 수 있는 공간이 하나도 없다는 뜻이지만, 프로그래밍을 하다보면 길이가 0인 배열이 필요한 상황이 있게 됩니다.

int[] score = new int[0]; // 길이가 0인 배열도 생성 가능

배열의 길이는 int범위의 양의 정수(0도 포함)이어야 한다.

배열이름.length

자바에서는 JVM이 모든 배열의 길이를 별도로 관리하며, '배열이름.length'를 통해서 배열의 길이에 대한 정보를 얻을 수 있습니다.

int[] score = new int[5]; 	// 길이가 5인 int배열
int tmp = arr.length; 		// arr.length의 값은 5이고 tmp에 5가 저장됨

배열은 한번 생성하면 길이를 변경할 수 없기 때문에, 이미 생성된 배열의 길이는 변하지 않습니다. 따라서 '배열이름.length'는 상수입니다. 즉, 값을 읽을 수만 있을 뿐 변경할 수 없습니다.

모든 경우에 배열의 길이를 직접 적어주는 것보다 '배열이름.length'를 사용하는 것이 코드의 관리가 쉽고 에러가 발생할 확률이 적어집니다. 배열의 길이를 직접 적어주게 되면 배열의 길이가 변경될 때 일일히 변경해주지 않으면 에러가 발생할 수 있기 때문입니다.

for (int i=0; i<6; i++)  ->  for (int i=0; i<score.length; i++)

배열의 초기화

배열의 생성과 동시에 자동적으로 자신의 타입에 해당하는 기본값으로 초기화되므로 배열을 사용하기 전에 따로 초기화를 해주지 않아도 됩니다.

하지만 원하는 값을 저장하려면 3가지 방식이 있습니다.

  1. 각 요소마다 값을 지정
  2. for문으로 초기화
  3. 생성시 괄호{}안에서 지정

각 요소마다 값을 지정

배열의 길이가 큰 경우에는 이렇게 요소 하나하나에 값을 지정하기 어렵습니다.

int[] score = new int[3];  // 길이가 3인 int형 배열을 생성
score[0] = 10; 			   // 각 요소에 직접 값을 저장
score[1] = 20;
score[2] = 30;

for문으로 초기화

배열의 크기가 큰 경우에는 for문을 사용하는 것이 좋습니다. 하지만 for문으로 배열을 초기화하려면, 저장하려는 값에 일정한 규칙이 있어야만 가능합니다.

int[] score = new int[3]; // 길이가 3인 int형 배열을 생성

for(int i=0; i<score.length; i++)
	score[i] = i * 10;

생성시 괄호{}안에서 지정

저장할 값들을 괄호{} 안에 쉼표로 구분해서 나열하면 되며, 괄호{} 안의 값의 개수에 의해 배열의 길이가 자동으로 결정되기 때문에 괄호[] 안에 배열의 길이는 안적어도 됩니다.

int[] score = new int[]{10, 20, 30};
int[] score = {10, 20, 30}; // new int[]를 생략가능

심지어 'new 타입[]'을 생략하여 코드를 더 간단히 할 수도 있습니다. 아무래도 생략된 형태의 코드가 더 간단하므로 자주 사용됩니다. 다만 다음과 같이 배열의 선언과 생성을 따로 하는 경우에는 생략할 수 없다는 점만 주의하면 됩니다.

int[] score;
score = new int[]{10, 20, 30}; // OK
score = {10, 20, 30}; 		   // 에러. new int[]를 생략할 수 없음

또 다른 예로, 메서드의 매개변수로 배열이 있는 경우 메서드를 호출할 때 역시 'new 타입[]'을 생략할 수 없으며, 이유는 같습니다.

int add(int[] arr) { /* 내용 생략 */} 		// add메서드

int result = add(new int[]{10, 20, 30});    // OK
int result = add({10, 20, 30}); 			// 에러. new int[]를 생략할 수 없음

그리고 괄호{} 안에 아무것도 넣지 않으면, 길이가 0인 배열이 생성됩니다. 참조변수의 기본 값은 null이지만, 배열을 가리키는 참조변수는 null대신 길이가 0인 배열로 초기화하기도 합니다.

int[] score = new int[0]; 	// 길이가 0인 배열
int[] score = new int[]{};  // 길이가 0인 배열
int[] score = {}; 			// 길이가 0인 배열, new int[]가 생략됨

배열의 출력

배열을 초기화할 때 for문을 사용하듯이, 배열에 저장된 값을 확인할 때도 for문을 사용하면 됩니다.

int[] iArr = {10, 20, 30};

// 배열의 요소를 순서대로 하나씩 출력
for(int i=0; i<iArr.length; i++) {
	System.out.println(iArr[i]);
}

더 간단한 방법은 'Arrays.toString(배열이름)'메서드를 사용하는 것입니다. 이 메서드는 배열의 모든 요소를 '[첫번째 요소, 두번째 요소, ...]'와 같은 형식의 문자열로 만들어서 반환합니다.

int[] iArr = {10, 20, 30};

// 배열 iArr의 모든 요소를 출력. [10, 20, 30]이 출력됨
Sytem.out.println(Arrays.toString(iArr));

iArr의 값을 바로 출력하게 되면 뭐가 나올까요? iArr은 참조변수이기 때문에 변수에 저장된 값, 즉 '배열의 주소'가 출력된다고 생각이 들겠지만, '타입@주소'의 형식으로 출력됩니다. '[I'는 1차원 int배열이라는 의미이고, '@'뒤에 나오는 16진수는 배열의 주소인데 실제 주소가 아닌 내부 주소입니다.

배열의 복사

배열의 길이 변경

배열은 한번 선언되고 나면 길이를 변경할 수 없습니다. 그래서 배열에 저장할 공간이 부족한 경우에는 더 큰 길이의 새로운 배열을 생성한 다음, 기존의 배열에 저장된 값들을 새로운 배열에 복사하면 됩니다.

  1. 더 큰 배열을 새로 생성한다.
  2. 기존 배열의 내용을 새로운 배열에 복사한다.

하지만 이런 작업들은 꽤나 비용이 많이 들기 때문에, 처음부터 배열의 길이를 넉넉하게 잡아줘서 새로 배열을 생성해야하는 상황이 가능한 적게 발생하도록 해야 합니다. 그렇다고 배열의 길이를 너무 크게 잡으면 메모리를 낭비하게 되므로, 기존의 2배정도의 길이로 생성하는 것이 좋습니다.

배열 복사

배열을 복사하는 방법
1. for문 이용
2. System.arraycopy()를 이용한 배열의 복사

for문 이용

int[] arr = new int[5];
...
int[] tmp = new int[arr.length*2]; // 기존 배열보다 길이가 2배인 배열 생성

for(int i=0; i<arr.length; i++)
	tmp[i] = arr[i]; 			  // arr[i]의 값을 tmp[i]에 저장
    
arr = tmp;						  // 참조변수 arr이 새로운 배열을 가리키게 함

이러한 작업들은 꽤나 비용이 많이 들기 때문에 처음부터 배열의 길이를 넉넉하게 잡아줘서 새로 배열을 생성해야하는 상황이 가능한 적게 발생하도록 해야 합니다.

작동 원리

  1. 배열 arr의 길이인 arr.length의 값이 5이므로 길이가 10인 int배열 tmp가 생성되고, 배열 tmp의 각 요소는 int의 기본값인 0으로 초기화됩니다. (arr의 값은 0x100, tmp의 값은 0x200 가정)

    int[] tmp = new int[arr.length*2];
    -> int[] tmp = new int[5*2];
    -> int[] tmp = new int[10];
  2. for문을 이용해서 배열 arr의 모든 요소에 저장된 값을 하나씩 배열 tmp에 복사합니다.

    for(int i=0; i<arr.length; i++)
     tmp[i] = arr[i]; 
  3. 참조변수 arr에 참조변수 tmp의 값을 저장합니다. arr의 값은 0x100에서 0x200으로 바뀌고, arr은 배열 tmp를 가리키게 됩니다.

    arr = tmp;

결국 참조변수 arr과 tmp는 같은 배열을 가리키게 됩니다. 즉, 배열 arr과 배열 tmp는 이름만 다를 뿐 동일한 배열입니다. 그리고 전에 arr이 가리키던 배열은 더 이상 사용할 수 없게 됩니다.

배열은 참조변수를 통해서만 접근할 수 있기 때문에, 자신을 가리키는 참조변수가 없는 배열은 사용할 수 없습니다. 이렇게 쓸모없게 된 배열을 JVM의 가비지 컬렉터에 의해서 자동적으로 메모리에서 제거됩니다.

System.arraycopy()를 이용한 배열의 복사

for문은 배열의 요소 하나하나에 접근해서 복사하지만, arraycopy()는 지정된 범위의 값들을 한 번에 통째로 복사합니다. 각 요소들이 연속적으로 저장되어 있다는 배열의 특성때문에 이렇게 처리하는 것이 가능합니다.

배열의 복사는 for문보다 System.arraycopy()를 사용하는 것이 효율적

이전 for문을 arraycopy()로 바꾸면 이렇습니다.

for(int i=0; i<arr.length; i++)	tmp[i] = arr[i]; 

System.arraycopy(arr, 0, tmp, 0, arr.length);
// arr[0]에서 tmp[0]으로 arr.length개의 데이터를 복사

이 때 복사하려는 배열의 위치가 적절하지 못하여 복사하려는 내용보다 여유 공간이 적으면 에러(ArrayIndexOutOfBoundsException)가 발생합니다.

profile
🚀 백엔드 2년차 개발자입니다 🚀 성장의 즐거움으로 아자자자!🤣

0개의 댓글