배열(Array)
지금까지 변수는 한 개의 데이터만 저장 할 수 있습니다. 저장해야 할 데이터가 N개 만큼 많아지면, N개에 해당하는 변수를 선언해야 합니다. 반면 배열을 사용하면 배열 하나를 선언함으로써 N개의 데이터를 저장 할 수 있습니다. 단, 배열에 저장되는 데이터는 배열을 선언할때 지정한 데이터 타입만 가능합니다.
배열을 선언하는 방법은 두 가지입니다. 우선 '타입[] 변수명' 방식으로 선언하는 방법이 있습니다.
int[] array;
다음으로 '타입 변수명[]' 방식으로도 배열을 선언 할 수 있습니다. 배열을 의미하는 대괄호([])는 타입 또는 변수명 뒤 어디에 붙더라도 상관없습니다.
int array[];
배열은 참조(Reference) 타입에 속하는 변수 중 하나입니다. 따라서 배열은 객체(Object)에 속하며, 힙 영역(Heap Area)에 메모리를 할당받습니다. 또한 참조 타입이기 때문에 null을 저장 할 수 있습니다. 이는 배열이 '참조하는 객체의 주소가 없다'는 것을 의미합니다.
int array[] = null;
초기화
배열을 선언함과 동시에 원소(Element)를 초기화 할 수 있습니다. 여기서 원소는 배열을 구성하는 각각의 데이터입니다. 원소는 선언된 배열의 데이터 타입과 일치합니다(만약 int[] 배열을 선언한다면, 이 배열에 속하는 N개의 원소는 모두 int 타입입니다).
배열을 초기화하는 방법은 두 가지입니다.
int[] array = { 1, 2, 3 };
배열을 선언함과 동시에 중괄호({})를 사용하여 원소를 순차적으로 입력합니다. 중괄호를 사용한 원소 입력은 초기화 단계에서만 사용 할 수 있습니다. 다음과 같이 중괄호를 사용하여 원소를 배열에 대입하는 것은 허용되지 않습니다.
int[] array;
array = { 1, 2, 3 }; // compile error
이 경우 new 연산자를 사용하여 원소를 배열에 대입 할 수 있습니다. 대입 연산이 가능하기 때문에 초기화에서도 사용 할 수 있습니다.
int[] array = new int[] { 1, 2, 3 };
array = new int[] { 3, 2, 1 };
길이
앞서 배열은 참조 타입이라고 소개했습니다. 참조 타입은 힙 영역에 메모리를 할당 받습니다. 배열의 길이는 배열을 초기화하는 순간에 결정됩니다. 배열의 데이터 타입과 초기화에서 입력한 원소의 개수에 따라서 힙 영역에 고정된 메모리를 할당 받습니다.
int[] array = new int[] { 83, 41, 29, 50, 47, 68, 20, 33 };
만약 예시처럼 8개의 int 타입 원소로 구성된 int[] 배열을 선언하면, 힙 영역에는 다음과 같이 36바이트 메모리(int 4바이트 * 길이 8)를 할당하고 값을 기록됩니다. 여기서 배열의 인덱스는 첫 번째 원소부터 0으로 시작하여 마지막 원소는 (배열의 길이 - 1)로 끝납니다. 배열의 길이가 8인 경우 배열의 인덱스는 0 ~ 7까지 존재합니다.
사용 메모리 | 4바이트 | 4바이트 | 4바이트 | 4바이트 | 4바이트 | 4바이트 | 4바이트 | 4바이트 |
인덱스 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
값 | 83 | 41 | 29 | 50 | 47 | 68 | 20 | 33 |
배열의 길이는 length를 사용하여 확인 할 수 있습니다. 만약 배열이 초기화되지 않은 상태(null)인 경우 NullPointerException을 일으킵니다.
int[] array = new int[] { 83, 41, 29, 50, 47, 68, 20, 33 };
System.out.println(array.length); // 8
배열을 초기화 할 때 원소를 지정하지 않더라도 배열의 길이를 결정 할 수 있습니다. 이는 배열의 길이를 고정으로 할당하는 경우 사용합니다. 예를 들면 배열에 어떤 값이 입력될지는 나중에 결정할 것이지만, 길이는 N으로 고정해야 하는 경우입니다.
int[] array = new int[8];
배열을 원소 입력 없이 초기화 하면 배열의 각 원소에는 기본 값이 입력됩니다. 기본 값은 배열의 선언 타입에 따라서 달라집니다.
데이터 타입 | 값 |
boolean | false |
boolean 외 기본(Primitive) 타입 | 0 |
참조(Reference) 타입 | null |
배열은 한 번 초기화되면 길이를 수정 할 수 없습니다. 만약 배열의 길이를 확장하거나 축소해야 하는 경우, 새로운 배열을 초기화하여 기존 배열의 원소를 전부 복사시켜줘야합니다. 이를 위해서 for 또는 while 반복문을 사용하여 배열을 복사하는 알고리즘을 구현 할 수 있습니다.
또는 자바에서 제공하는 System.arraycopy() 함수를 사용 할 수 있습니다. arraycopy() 다음과 같이 5개의 인자를 사용합니다.
파라미터 | 파라미터 데이터 타입 | 파라미터 명 | 비고 |
0 | Object | src | 원본 배열 |
1 | int | srcPos | 원본 배열의 복사가 시작되는 인덱스 |
2 | Object | dest | 목적지 배열 |
3 | int | destPos | 목적지 배열의 복사가 시작되는 인덱스 |
4 | int | length | 복사하려는 원소의 개수 |
복사하려는 원소의 개수가 원본 배열 또는 목적지 배열의 시작 인덱스에서 배열의 길이를 초과하지 않도록 주의해야 합니다. 다음은 System.arraycopy() 함수를 사용하는 예시입니다.
int[] src_array = new int[] { 1, 2, 3 };
int[] trg_array = new int[3];
System.arraycopy(src_array, 0, trg_array, 0, 3);
System.out.println(src_array[0]); // 1
System.out.println(src_array[1]); // 2
System.out.println(src_array[2]); // 3
원소(Element)
배열을 선언하고, 원소를 초기화 하면 배열의 인덱스를 사용하여 원소에 접근 할 수 있습니다.
int[] array = new int[] { 83, 41, 29, 50 };
System.out.println(array[0]); // 83
System.out.println(array[1]); // 41
System.out.println(array[2]); // 29
System.out.println(array[3]); // 50
배열의 인덱스는 0부터 시작해서 (배열의 길이 - 1)까지 존재합니다. 만약 0보다 작은 인덱스 또는 배열의 길이보다 크거나 같은 인덱스를 사용하여 원소에 접근하는 경우 ArrayIndexOutBoundException을 일으킵니다.
int[] array = new int[] { 83, 41, 29, 50 };
System.out.println(array[-1]); // ArrayIndexOutBoundException
System.out.println(array[5]); // ArrayIndexOutBoundException
특정 원소를 수정하고자 하는 경우 인덱스를 통해 원소에 접근하고 대입 연산자를 사용하여 값을 변경 할 수 있습니다.
int[] array = new int[] { 83, 41, 29, 50 };
array[2] = 10;
System.out.println(array[2]); // 10
2차원 배열
앞서 소개한 배열은 1차원 배열에 속합니다. 차원이란 배열의 중첩을 의미합니다. 1차원 배열은 대괄호([])의 개수가 한 개입니다. 2차원 배열은 대괄호의 개수가 2개인 배열입니다.
2차원 배열은 다음과 같이 초기화 할 수 있습니다(기본적으로 1차원 배열과 사용법이 동일합니다). 초기화에 사용된 중괄호({}) 역시 이중으로 중첩되어 있습니다.
int[][] array = { {1, 2, 3 }, {1, 2, 3, 4, 5 } };
또는 다음과 같이 초기화 할 수도 있습니다.
int[][] array = new int[][] { {1, 2, 3 }, {1, 2, 3, 4, 5 } };
가장 바깥족 중괄호는 N개의 또 다른 중괄호를 포함합니다. 내부에 원소를 초기화하고 있는 중괄호는 개별 배열에 속합니다. 즉, 2차원 배열은 배열이 N개 중첩된 구조인 것을 알 수 있습니다.
int[N][M] | M = 0 | M = 1 | M = 2 | M = 3 | M = 4 |
N = 0 | [0][0] = 1 | [0][1] = 2 | [0][2] = 3 | ||
N = 1 | [1][0] = 1 | [1][1] = 2 | [1][2] = 3 | [1][3] = 4 | [1][4] = 5 |
2차원 배열에서 [0] 원소에 접근하면 첫 번째 선언된 배열 { 1, 2, 3 }에 접근 할 수 있습니다. [1] 원소에 접근하면 두 번째 선언된 배열 { 1, 2, 3, 4, 5 }에 접근 할 수 있습니다.
각 원소는 또 다른 배열이기 때문에 원소에 접근하는 방법 역시 대괄호를 중첩하여 인덱스를 입력합니다. 예를 들어 첫 번째 배열의 마지막 원소에 접근하기 위해서는 [0][2]를 사용합니다.
아래 예시에서는 2중 배열에서 첫 번째 배열의 마지막 원소의 값을 변경하는 코드입니다.
int[][] array = { {1, 2, 3}, {1, 2, 3, 4, 5} };
array[0][2] = 10;
System.out.println(array[0][2]); // 10
2차원 배열의 초기화 단계에서 배열의 길이를 결정하는 방법은 1차원 배열과 동일합니다. 각 중첩되는 배열에 대한 길이를 지정 할 수 있으며, 지정 순서는 좌측(바깥쪽 배열)부터 결정되어야 합니다. 만약 바깥쪽 배열의 길이를 결정하지 않은 상태에서 안쪽 배열의 길이를 지정하면 컴파일 에러를 발생시킵니다.
int[][] array_0 = new int[3][];
int[][] array_1 = new int[3][5];
int[][] array_2 = new int[][5]; // compile error
2차원 배열을 펼치면(위의 표 참고) 수학의 행렬의 모습과 동일합니다. 실제로 프로그램에서 행렬을 구현 할 때 2차원 배열을 사용합니다.
다차원 배열
다차원 배열은 2중 이상으로 중첩된 모든 배열을 의미합니다. 2차원 배열 역시 다차원 배열에 속합니다. 2차원 배열은 행렬 또는 xy 좌표계를 구현 할 때 사용 할 수 있습니다. 3차원 배열은 큐브 형태의 모습을 갖는데, 이런 성격 때문에 xyz 좌표계를 구현 할 때 사용 할 수 있습니다.
int[][] array_0 = new int[][];
int[][] array_1 = new int[][][];
int[][] array_2 = new int[][][][];
다만 2차원 배열과 3차원 배열은 Vector2, Vector3와 같은 대체 클래스가 존재하기 때문에 실제로는 배열을 사용하여 직접 구현할 일은 거의 없으며, 가끔 알고리즘 풀이 문제에서나 사용하는 정도입니다. 배열이 4차원 이상으로 확장되면 사람이 인지하기에 너무 복잡해지므로, 이 역시 프로그램에서는 사용되지 않습니다.
'Java > Java SE, EE' 카테고리의 다른 글
[Java/Java SE, EE] 객체 지향 프로그래밍(OOP) (0) | 2022.01.24 |
---|---|
[Java/Java SE, EE] 열거(Enumeration) 타입 (0) | 2022.01.23 |
[Java/Java SE, EE] 자바 메모리 구조, JVM 메모리 영역(Runtime Data Area) (0) | 2022.01.22 |
[Java/Java SE, EE] 문자열(String) 클래스 사용법, 상수 풀을 사용한 초기화 (0) | 2022.01.21 |
[Java/Java SE, EE] 기본 타입(Primitivie)과 참조(Reference) 타입, 값에 의한 호출(Call by value)와 참조에 의한 호출(Call by reference) (0) | 2022.01.21 |