Skip to content

5. 배열(Array)


1. 키워드

  • 메서드(Method) 영역, 클래스(Class), 클래스 변수(Static Variable)
  • 힙(Heap) 영역, 객체(Object), 인스턴스(Instance)
  • 스택(Stack) 영역, 메서드(Method), 지역 변수(Local Variable), 매개변수(Parameter)
  • 배열(Array)과 다차원 배열(Multi-Dimensional Array)
  • 가변 배열(Dynamic Array)
  • Enhanced for


2. 메모리 구조

  • 모든 자바 프로그램은 JVM을 통해서 실행된다.
  • 자바 프로그램이 실행되면, JVM은 OS로부터 해당 프로그램을 수행할 수 있도록 필요한 메모리를 할당받는다.


  • 이렇게 할당받은 메모리를 JVM은 용도에 따라 다음과 같이 구분하여 관리한다.


001


1) 메서드(Method) 영역

  • 메서드 영역은 자바 프로그램에서 사용되는 클래스에 대한 정보와 함께 클래스 변수(Static Variable)가 저장되는 영역이다.
  • JVM은 자바 프로그램에서 특정 클래스가 사용되면 해당 클래스의 클래스 파일(*.class)를 읽어들여, 해당 클래스에 대한 정보를 메서드 영역에 저장한다.


2) 힙(Heap) 영역

  • 힙 영역은 자바 프로그램에서 사용되는 모든 인스턴스 변수가 저장되는 영역이다.
  • JVM은 자바 프로그램에서 new 키워드를 사용하여 인스턴스가 생성되면, 해당 인스턴스의 정보를 힙 영역에 저장한다.
  • 힙 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당된다.


3) 스택(Stack) 영역

  • 스택 영역은 자바 프로그램에서 메서드가 호출될 때 메서드의 스택 프레임이 저장되는 영역이다.
  • JVM은 자바 프로그램에서 메서드가 호출되면, 메서드의 호출과 관계되는 지역 변수와 매개변수를 스택 영역에 저장한다.
  • 이렇게 스택 영역은 메서드의 호출과 함께 할당되며, 메서드의 호출이 완료되면 소멸한다.
  • 스택 영역에 저장되는 메서드의 호출 정보를 스택 프레임(Stack Frame)이라고 한다.
  • 스택 영역은 푸시(Push) 동작으로 데이터를 저장하고, 팝(Pop) 동작으로 데이터를 인출한다.
  • 이러한 스택은 후입선출(LIFO, Last-In First-Out) 방식에 따라 동작하므로, 가장 늦게 저장된 데이터가 가장 먼저 인출된다.
  • 스택 영역은 메모리의 높은 주소에서 낮은 주소의 방향으로 할당된다.


3. 1차원 배열

1) 배열(Array)이란?

  • 배열은 같은 타입의 변수들로 이루어진 유한 집합으로 정의할 수 있다.
  • 배열을 구성하는 각각의 값을 배열 요소(Element)라고 하며, 배열에서의 위치를 가리키는 숫자를 인덱스(Index)라고 한다.
  • 자바에서 인덱스는 언제나 0부터 시작하며, 0을 포함한 양의 정수만을 가질 수 있다.
  • 배열은 같은 종류의 데이터를 많이 다뤄야 하는 경우에 사용할 수 있는 가장 기본적인 자료 구조이다.
  • 배열은 선언되는 형식에 따라 1차원 배열, 2차원 배열뿐만 아니라 그 이상의 다차원 배열로도 선언할 수 있다.


2) 1차원 배열

  • 1차원 배열은 가장 기본적인 배열로 다음과 같은 문법에 따라 선언한다.


1. 타입[] 배열이름;
2. 타입 배열이름[];


  • 타입은 배열 요소로 저장되는 변수의 타입을 명시한다.
  • 배열 이름은 배열이 선언된 후에 배열에 접근하기 위해 사용된다.


Note

  • 자바에서는 배열을 만들기 위해 위의 두 가지 방법을 모두 사용할 수 있지만, 가능하면 첫 번째 방법만을 사용하는 것이 좋다.


  • 위와 같이 선언된 배열은 new 키워드를 사용하여 실제 배열로 생성할 수 있다.


배열이름 = new 타입[배열길이];


  • 배열의 길이는 해당 배열이 몇 개의 배열 요소를 가지게 되는지 명시한다.


  • 또한, 다음과 같이 배열의 선언과 생성을 동시에 할 수도 있다.


타입[] 배열이름 = new 타입[배열길이];


  • 자바에서는 이러한 배열도 모두 객체이므로, 각각의 배열은 모두 자신만의 필드와 메서드를 가지고 있다.


  • 다음 예제는 int형 데이터를 3개 저장할 수 있는 배열을 선언과 동시에 생성하고 있다.


int[] grade1 = new int[3];
int[] grade2 = new int[3];

grade1[0] = 85;
grade1[1] = 65;
grade1[2] = 90;

grade2[0] = 85;

for (int i = 0; i < grade1.length; i++) {
  System.out.print(grade1[i] + " ");
}

System.out.println();

for (int i = 0; i < grade2.length; i++) {
  System.out.print(grade2[i] + " ");
}

// 85 65 90
// 85 0 0


  • 위의 예제처럼 0부터 시작하는 인덱스를 이용하면 각각의 배열 요소에 따로 접근할 수 있다.
  • 또한, 배열 grade2처럼 배열의 길이보다 적은 수의 배열 요소만을 초기화할 경우, 나머지 배열 요소들은 배열의 타입에 맞게 자동으로 초기화될 것이다.


배열의 타입 초깃값
char '\u0000'
byte, short, int 0
long 0L
float 0.0F
double 0.0 또는 0.0D
boolean false
배열, 인스턴스 null


  • 하지만 다음 예제처럼 해당 배열의 길이를 초과하는 인덱스를 사용하면, ArrayIndexOutOfBounds 예외가 발생할 것이다.


int[] grade = new int[3];

grade[0] = 85;
grade[1] = 65;
grade[2] = 90;

System.out.println(grade[4]); // ArrayIndexOutOfBounds 예외 발생


3) 배열의 초기화

  • 자바에서는 변수와 마찬가지로 배열도 선언과 동시에 초기화할 수 있다.
  • 다음과 같이 {}(중괄호)를 사용하여 초깃값을 나열한 것을 초기화 블록(Initialization Block)이라고 한다.
  • 자바에서는 이러한 초기화 블록을 이용하여 배열을 선언과 동시에 초기화할 수 있다.


  • 초기화 블록을 이용한 배열의 초기화 방법은 다음과 같다.


1. 타입[] 배열이름 = {배열요소1, 배열요소2, ...};
2. 타입[] 배열이름 = new 타입[] {배열요소1, 배열요소2, ...};


  • 위의 두 가지 초기화 방법은 완전히 같은 결과를 반환하며, 초기화 블록에 맞춰 자동으로 배열의 길이가 설정된다.
  • 하지만 다음과 같은 경우에는 첫 번째 방법이 아닌 두 번째 방법만을 사용하여 초기화해야 한다.


1] 배열의 선언과 초기화를 따로 진행해야 할 경우

2] 메서드의 인수로 배열을 전달하면서 초기화해야 할 경우


int[] grade1 = { 70, 90, 80 }; // 배열의 선언과 동시에 초기화할 수 있음
int[] grade2 = new int[] { 70, 90, 80 }; // 배열의 선언과 동시에 초기화할 수 있음
int[] grade3;

grade3 = {70, 90, 80} // 이미 선언된 배열을 이 방법으로 초기화하면 오류 발생

int[] grade4;
grade4 = new int[] { 70, 90, 80 }; // 이미 선언된 배열은 이 방법으로만 초기화할 수 있음


  • 위의 예제처럼 초기화 블록의 타입과 배열의 타입은 반드시 일치해야 한다.


  • 다음 예제는 앞선 예제와 같은 배열을 선언과 동시에 초기화 블록으로 초기화하는 예제이다.


int[] grade = new int[] { 85, 65, 90 };

for (int i = 0; i < grade.length; i++) {
  System.out.print(grade[i] + " ");
}

// 85 65 90


  • 다음 예제는 배열 요소의 합과 평균을 구하는 예제이다.


int[] grade = new int[] { 85, 65, 90 };
int sum = 0;

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

System.out.println("모든 과목에서 받은 점수의 합은 " + sum + "입니다.");
System.out.println("이 학생의 평균은 " + (sum / grade.length) + "입니다.");

// 모든 과목에서 받은 점수의 합은 240입니다.
// 이 학생의 평군은 80입니다.


4. 다차원 배열(Multi-Dimensional Array)

  • 다차원 배열이란 2차원 이상의 배열을 의미하며, 배열 요소로 또 다른 배열을 가지는 배열을 의미한다.
  • 즉, 2차원 배열은 배열 요소로 1차원 배열을 가지는 배열이며, 3차원 배열은 배열 요소로 2차원 배열을 가지는 배열이다.


1) 2차원 배열(Two Dimensional Array)

  • 2차원 배열이란 배열의 요소로 1차원 배열을 가지는 배열이다.
  • 자바에서는 2차원 배열을 나타내는 타입을 따로 제공하지 않는다.
  • 대신에 1차원 배열의 배열 요소로 또 다른 1차원 배열을 사용하여 2차원 배열을 나타낼 수 있다.


  • 따라서 자바에서 2차원 배열은 다음과 같은 문법으로 선언할 수 있다.


1. 타입[][] 배열이름;
2. 타입 배열이름[][];
3. 타입[] 배열이름[];


  • 타입은 배열 요소로 저장되는 변수의 타입을 설정한다.
  • 배열 이름은 배열이 선언된 후에 배열에 접근하기 위해 사용된다.


  • 다음 그림은 2차원 배열을 이해하기 쉽도록 도식적으로 표현한 그림이다.


002


int[][] arr = new int[2][3];
int k = 10;

for (int i = 0; i < arr.length; i++) {
  for (int j = 0; j < arr[i].length; j++) {
    arr[i][j] = k;
    k += 10;
  }
}

for (int i = 0; i < arr.length; i++) {
  for (int j = 0; j < arr[i].length; j++) {
    System.out.print(arr[i][j] + " ");
  }

  System.out.println();
}

// 10 20 30
// 40 50 60


  • 위의 예제에서 사용된 arr는 2차원 배열이며, arr[i]arr의 각 배열 요소로 1차원 배열이 된다.
  • 따라서 arr.length는 2차원 배열인 arr의 배열 요소의 총 개수를 반환하며, arr[i].lengtharr의 각 배열 요소인 1차원 배열이 가지고 있는 배열 요소의 총 개수를 반환하게 된다.


2) 배열의 선언과 동시에 초기화하는 방법

  • 1차원 배열과 마찬가지로 2차원 배열도 선언과 동시에 초기화할 수 있다.
  • 자바에서는 2차원 배열의 모든 요소를 좀 더 직관적으로 초기화할 수 있다.


타입 배열이름[열의길이][행의길이] = {
 {배열요소[0][0], 배열오소[0][1], ...},
 {배열요소[1][0], 배열오소[1][1], ...},
 {배열요소[2][0], 배열오소[2][1], ...},

 ...

};


  • 다음 예제는 앞서 살펴본 예제를 2차원 배열의 초기화 형태로 초기화하는 예제이다.


int[][] arr = {
 {10, 20, 30},
 {40, 50, 60}
};


3) 가변 배열(Dynamic Array)

  • 자바에서는 2차원 배열을 생성할 때 열의 길이를 명시하지 않음으로써, 행마다 다른 길이의 배열을 요소로 저장할 수 있다.
  • 이렇게 행마다 다른 길이의 배열을 저장할 수 있는 배열을 가변 배열이라고 한다.


int[][] arr = new int[3][];
arr[0] = new int[2];
arr[1] = new int[4];
arr[2] = new int[1];


  • 위의 예제처럼 배열을 생성할 때 [](대괄호)를 생략하면 가변 배열을 만들 수 있다.
  • 또한, 가변 배열도 초기화 블록을 사용하여 배열을 선언과 동시에 초기화할 수 있다.


  • 다음 예제는 앞선 예제와 같은 가변 배열을 선언과 동시에 초기화 블록으로 초기화하는 예제이다.


int[][] arr = {
 {10, 20},
 {10, 20, 30, 40},
 {10}
};


5. 배열의 활용

1) 배열의 복사

  • 자바에서 배열은 한 번 생성하면 그 길이를 변경할 수 없다.
  • 따라서 더 많은 데이터를 저장하기 위해서는 더욱 큰 배열을 만들고, 이전 배열의 데이터를 새로 만든 배열로 복사해야 한다.
  • 이러한 배열의 복사를 위해 자바에서는 다음과 같이 여러 가지 방법을 제공한다.


1] System 클래스의 arraycopy() 메서드

2] Arrays 클래스의 copyOf() 메서드

3] Object 클래스의 clone() 메서드

4] for 문과 인덱스를 이용한 복사


  • 이 중에서 가장 좋은 성능을 보이는 것은 배열의 복사만을 위해 만들어진 arraycopy() 메서드이다.
  • 하지만 현재 배열의 복사를 위해 가장 많이 사용되는 메서드는 좀 더 유연한 방식의 copyOf() 메서드이다.
  • arraycopy(), copyOf() 메서드와 for 문을 이용한 복사는 배열의 길이를 마음대로 늘일 수 있다.
  • 하지만 clone() 메서드는 이전 배열과 같은 길이의 배열밖에 만들 수 없다.


  • 다음 예제는 다양한 방법을 배열을 복사하는 예제이다.


int[] arr1 = new int[] { 1, 2, 3, 4, 5 };
int newLen = 10;

// 1. System 클래스의 arraycopy() 메서드
int[] arr2 = new int[newLen];
System.arraycopy(arr1, 0, arr2, 0, arr1.length);

for (int i = 0; i < arr2.length; i++) {
  System.out.print(arr2[i] + " ");
}
System.out.println();

// 2. Arrays 클래스의 copyOf() 메서드
int[] arr3 = Arrays.copyOf(arr1, 10);

for (int i = 0; i < arr3.length; i++) {
  System.out.print(arr3[i] + " ");
}
System.out.println();

// 3. Object 클래스의 clone() 메서드
int[] arr4 = (int[]) arr1.clone();

for (int i = 0; i < arr4.length; i++) {
  System.out.print(arr4[i] + " ");
}
System.out.println();

// 4. for 문과 인덱스를 이용한 복사
int[] arr5 = new int[newLen];

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

for (int i = 0; i < arr5.length; i++) {
  System.out.print(arr5[i] + " ");
}

// 1 2 3 4 5 0 0 0 0 0
// 1 2 3 4 5 0 0 0 0 0
// 1 2 3 4 5
// 1 2 3 4 5 0 0 0 0 0


2) Enhanced for

  • 배열과 컬렉션의 모든 요소를 참조하기 위한 Enhanced for 문이라는 반복문이 추가되었다.
  • 이 반복문은 배열과 컬렉션 프레임워크에서 유용하게 사용된다.


  • 자바에서 Enhanced for 문은 다음과 같은 문법으로 사용한다.


for (타입 변수이름 : 배열이나컬렉션이름) {
 배열의 길이만큼 반복적으로 실행하고자 하는 명령문;
}


  • Enhanced for 문은 명시한 배열이나 컬렉션의 길이만큼 반복되어 실행된다.
  • 루프마다 각 요소는 명시한 변수의 이름을 저장되며, 명령문에서는 이 변수를 사용하여 각 요소를 참조할 수 있다.


  • 다음 예제는 Enhanced for 문을 사용하여 각 배열 요소의 값을 출력하는 예제이다.


int[] arr = new int[] { 1, 2, 3, 4, 5 };

for (int e : arr) {
  System.out.print(e + " ");
}


  • 하지만 Enhanced for 문은 요소를 참조할 때만 사용하는 것이 좋으며, 요소의 값을 변경하는 작업에는 적합하지 않다.


  • 다음 예제는 for 문과 Enhanced for 문을 이용하여 모든 배열 요소에 10을 더하는 예제이다.


int[] arr1 = new int[] { 1, 2, 3, 4, 5 };
int[] arr2 = new int[] { 1, 2, 3, 4, 5 };

for (int i = 0; i < arr1.length; i++) {
  arr1[i] += 10; // 각 배열 요소에 10을 더함.
}

for (int e : arr2) {
  e += 10; // 각 배열 요소에 10을 더함.
}

// arr1의 모든 배열 요소 출력
for (int i = 0; i < arr1.length; i++) {
  System.out.print(arr1[i] + " ");
}
System.out.println();

// arr2의 모든 배열 요소 출력
for (int i = 0; i < arr2.length; i++) {
  System.out.print(arr2[i] + " ");
}

// 11 12 13 14 15
// 1 2 3 4 5


  • 이렇게 Enhanced for 문 내부에서 사용되는 배열 요소는 배열 요소 그 자체가 아닌 배열 요소의 복사본이다.
  • 따라서 Enhanced for 문에서 배열 요소의 값을 변경해도 원본 배열에는 아무런 영향을 주지 못하게 된다.

References