배열

이 글은 남궁성님의 자바의 정석 3/e을 기반으로 공부한 내용을 정리한 글입니다.

배열 array

1.1 배열(array)이란?

같은 타입의 여러 변수를 하나의 묶음으로 다루는 것을 배열(array)이라고 한다. 많은 양의 데이터를 다룰 때 유용하다.

배열을 만들 때 중요한 점은 ‘같은 타입’이어야 한다. 서로 다른 타입의 변수들로 구성된 배열은 만들 수 없다.

01

변수와 달리 배열은 각 저장공간이 연속적으로 배치되어 있다. 변수 scores는 배열을 다루는데 필요한 참조변수일 뿐 값을 저장하기 위한 공간은 아니다.

1.2 배열의 선언과 생성

배열을 선언하는 방법은 원하는 타입의 변수를 선언하고 변수 또는 타입에 배열임을 의미하는 대괄호 [ ]를 붙이면 된다. 대괄호 [ ]는 타입 뒤에 붙여도 되고 변수이름 뒤에 붙여도 된다.

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


배열의 생성

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

배열을 생성하기 위해서는 연산자 new와 함께 배열의 타입과 길이를 지정해 주어야 한다.

  타입[ ] 변수이름;
  변수이름 = new 타입[길이];

다음과 같이 배열의 선언과 생성을 동시에 하면 한 줄로 할 수 있다.

  타입[] 변수이름 = new 타입[길이];

1.3 배열의 길이와 인덱스

생성된 배열의 각 저장공간을 배열의 요소(element)라고 하며, 배열이름[인덱스]형식으로 배열의 요소에 접근한다. 인덱스(index)는 배열의 요소마다 붙여진 일련번호로 각 요소를 구별하는데 사용된다. 단, 인덱스는 1이 아닌 0부터 시작해서 ‘배열길이 - 1’까지이다.

배열에 값을 저장하고 읽어오는 것은 변수와 같다. 단지 변수이름 대신 ‘배열이름[인덱스]’를 사용한다는 점이 다르다. index로 상수 대신 변수나 수식을 사용할 수 있다.

  score[0] = 0;
  score[1] = 10;
  score[2] = 20;
  score[3] = 30;
  score[4] = 40;

위 코드를 아래와 같이 간단히 할 수 있다.

  for(int i = 0; i < 5; i++) {
    score[i] = i * 10;
  }

for문의 제어변수 i는 배열의 index로 사용하기에 적합하여, 배열을 다룰 때 for문은 거의 필수적이다. 괄호 안에 수식이 포함된 경우, 수식이 먼저 계산된다. 배열의 몇 번째 요소인지 알아야 하기 때문이다.

index의 범위를 벗어난 값을 index로 사용하지 않도록 주의해야 한다. 컴파일러는 이러한 실수를 걸러주지 못한다. 배열의 index로 변수를 많이 사용하는데, 변수의 값은 실행 시에 대입되므로 컴파일러는 이 값의 범위를 확인할 수 없다. 유효하지 않은 값을 사용하면, 컴파일을 마쳤더라도 실행 시에 ArrayIndexOfBoundException이 발생한다.

배열의 길이

배열의 길이는 배열의 요소의 개수, 즉 값을 저장할 수 있는 공간의 개수이다. 배열의 길이는 양의 정수이어야 하며 최대값은 int타입의 최대값, 약 20억이다. 배열 길이가 0일 수도 있다.

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

배열이름.length

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

배열은 한번 생성하면 길이를 변경할 수 없기 때문에, 이미 생성된 배열의 길이는 변하지 않는다. 즉, ‘배열이름.length’는 상수이다. 값을 읽을 수만 있고 변경할 수는 없다.

‘배열이름.length’는 배열의 길이가 변경되면 자동적으로 변경된 배열의 길이를 값으로 가지기 때문에, 배열과 함께 사용되는 for문의 조건식을 일일이 변경하지 않아도 된다.

  for(int i = 0; i < score.length; i++) { }

배열의 길이 변경하기

배열에 저장할 공간이 부족한 경우에는 더 큰 길이의 새로운 배열을 생성한 다음, 기존 배열에 저장된 값을 복사하면 된다. 처음부터 배열의 길이를 넉넉하게 잡아줘서 새로 배열을 생성하는 상황이 가능한 적게 해야한다. 그렇다고 너무 크게 잡으면 메모리를 낭비하게 되므로, 기존의 2배정도의 길이가 적당하다.

1.4 배열의 초기화

배열은 생성과 동시에 자동적으로 자신의 타입에 해당하는 기본값으로 초기화되므로 따로 초기화를 해주지 않아도 되지만, 원하는 값을 저장하려면 각 요소마다 값을 지정해 줘야한다.

배열의 길이가 큰 경우에는 하나하나 지정하는 것보다 for문을 사용하는 것이 좋다. 하지만 for문으로 배열을 초기화하려면, 저장하려는 값에 일정한 규칙이 있어야만 가능하기 때문에 자바에서는 다음과 같이 생성과 동시에 초기화를 제공한다.

  int[] score = new int[]{50, 60, 70, 80, 90};

저장할 값들을 괄호 { } 안에 쉼표로 구분해서 나열하면 되며, 괄호 안의 값의 개수에 따라 배열의 길이가 결정되기 때문에 따로 길이를 안적어도 된다. 심지어 new int[]를 생략하여 더 간단히 할 수도 있다. 단, 선언과 생성을 따로 하는 경우에는 생략할 수 없다.

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

배열의 출력

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

  int[] iArr = {100, 95, 80, 70, 60};

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

만일 iArr의 값을 바로 출력하면 iArr은 참조변수이므로 변수에 저장된 값, 즉 ‘배열의 주소’가 출력될 것이라는 예상과 달리 타입@주소의 형식이 출력된다. [I는 1차원 int배열이라는 의미이고, @ 뒤에 나오는 16진수는 배열의 주소인데 실제 주소가 아닌 내부 주소이다.

1.5 배열의 복사

배열은 한 번 생성하면 길이를 변경할 수 없기 때문에 더 많은 저장공간이 필요하다면 보다 큰 배열을 새로 만들고 이전 배열로부터 내용을 복사해야한다. 배열을 복사하는 방법은 두 가지가 있는데, 하나는 for문을 이용하는 방법이고 다른 하나는 System.arraycopy()를 사용하는 것이다.

먼저 for문을 이용하는 코드이다.

  int[] arr = new int[5];
  ...
  int tmp = new int[arr.length * 2];

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

  arr = tmp;

길이가 기존 배열 arr의 2배인 tmp 배열을 생성했다. for문을 이용해서 arr배열의 모든 요소를 tmp 배열에 복사하고, 참조변수 arr에 참조변수 tmp의 값을 저장한다. 결국 참조변수 arrtmp는 같은 배열을 가리키게 된다. 즉, 두 배열은 이름만 다를 뿐 동일한 배열이다.

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

for문 대신 System 클래스의 arraycopy()를 사용하면 보다 간단하고 빠르게 배열을 복사할 수 있다. for문은 배열의 요소 하나하나에 접근해서 복사하지만, arraycopy()는 지정된 범위의 값들을 한 번에 통째로 복사한다. 각 요소들이 연속적으로 저장되어 있다는 배열의 특성덕분에 이렇게 처리하는 것이 가능하다.

배열의 복사에 사용된 for문을 arraycopy()로 바꾸면 다음과 같다.

  for(int i = 0; i < num.length; i++) {
    newNum[i] = num[i];
  }
  System.arraycopy(num, 0, newNum, 0, num.length);

arraycopy()를 호출할 때는 어느 배열의 몇 번째 요소에서 어느 배열로 몇 번째 요소로 몇 개의 값을 복사할 것인지 지정해줘야 한다.

1.6 배열의 활용

class ArrayEx {
  public static void main(String[] args) {
    int sum = 0;
    float avg = 0f;

    int[] score = {100, 88, 100, 100, 90};

    for(int i = 0; i < score.length; i++) {
      sum += score[i];
    }
    avg = sum / (float)score.length;

    System.out.println("총점 : " + sum);
    System.out.println("평균 : " + avg);
  }
}

배열의 합과 평균을 구하는 예제이다. 평균을 구하기 위해 전체 합을 배열의 길이 score.length로 나누었다.

class ArrayEx {
  public static void main(String[] args) {
    int[] score = {79, 88, 91, 33, 100, 55, 95};

    int max = score[0];
    int min = score[0];

    for(int i = 1; i < score.length; i++) {
      if(score[i] > max) {
        max = score[i];
      } else if(score[i] < min) {
        min = score[i];
      }
    }

    System.out.println("최대값 : " + max);
    System.out.println("최소값 : " + min);
  }
}

배열에 저장된 값 중에서 최대값과 최소값을 구하는 예제이다. 배열의 첫 번째 요소 score[0]의 값으로 최대값과 최소값을 초기화한다. 그 다음 반복문을 통해 요소와 최대값, 최소값을 각각 비교하여 조건을 만족하면 값을 입력한다.

class ArrayEx {
  public static void main(String[] args) {
    int[] numArr = new int[10];

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

    for(int i = 0; i < 100; i++) {
      int n = (int)(Math.random() * 10);
      int tmp = numArr[0];
      numArr[0] = numArr[n];
      numArr[n] = tmp;
    }

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

길이가 10인 배열 numArr을 생성하고 0~9의 숫자로 차례대로 초기화하여 출력한다. 그 다음 random()을 이용해서 배열의 값을 섞는 것을 100번 반복한다.

class ArrayEx {
  public static void main(String[] args) {
    int[] ball = new int[45];

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

    int tmp = 0;
    int j = 0;

    for(int i = 0; i < 6; i++) {
      j = (int)(Math.random() * 45);
      tmp = ball[i];
      ball[i] = ball[j];
    }
  }
}

로또번호를 생성하는 예제이다. 길이가 45인 배열에 1부터 45까지의 값을 담은 다음 반복문을 이용해서 배열의 첫 번째 값과 random()에 의해서 결정된 임의의 위치에 있는 값의 위치를 바꾸는 것을 6번 반복한다.

임의의 값으로 배열 채우기

배열을 연속적인 범위의 임의의 값으로 채우는 것은 다음과 같이 하면 된다.

  for(int i = 0; i < arr.length; i++) {
    arr[i] = (int)(Math.random() * 5);
  }

불연속적인 범위의 값들로 배열을 채울려면 배열을 하나 더 사용하면 된다. 불연속적인 값들로 채운 배열에서 임의의 index를 선택하여 나머지 배열의 요소들로 채우면 되는 것이다.

import java.util.*;

class ArrayEx {
  public static void main(String[] args) {
    int[] code = {-4, -1, 3, 6, 11};
    int[] arr = new int[10];

    for(int i = 0; i < arr.length; i++) {
      int tmp = (int)(Math.random() * arr.length);
      arr[i] = code(tmp);
    }

    System.out.println(Arrays.toString(arr));
  }
}

위로

String배열

2.1 String배열의 선언과 생성

배열의 타입이 String인 경우에도 int배열의 선언과 생성방법은 다르지 않다. String타입의 참조변수를 위한 공간이 마련되고 참조형 변수의 기본값은 null이므로 각 요소의 값은 null로 초기화 된다.

| 참고 | null는 어떠한 객체도 가리키고 있지 않다는 뜻이다.

변수의 타입에 따른 기본값은 다음과 같다.

자료형 기본값
boolean false
char ’\u0000’
byte, short, int 0
long 0L
float 0.0f
double 0.0d 또는 0.0
참조형 변수 null


2.2 String배열의 초기화

초기화 역시 동일하다. 배열의 각 요소에 문자열을 지정하면 된다. 또는 괄호 { }를 사용해서 간단히 초기화 할 수도 있다.

String클래스만 특별하게 큰따옴표 ““를 사용해 간략히 표현하는 것이 허용된다. 하지만 원래 String은 클래스이므로 new 연산자를 통해 객체를 생성해야 한다.

  String[] name = new String[3];
  name[0] = new String("Kim"); // name[0] = "Kim";
  name[1] = new String("Park"); // name[1] = "Park";
  name[2] = new String("Lee"); // name[2] = "Lee";

참조형 배열의 경우 배열에 저장되어 있는 것은 객체의 주소이다. 참조형 배열을 객체 배열이라고도 한다.

| 참고 | 참조형 변수를 참조변수라고도 하며, 모든 참조형 변수에는 객체가 메모리에 저장된 주소인 4byte의 정수값(0x0~0xffffffff) 또는 null이 저장된다.

2.3 char배열과 String클래스

String클래스는 char배열에 기능(메서드)를 추가한 것이다.

char배열과 String클래스의 중요한 차이는, String객체(문자열)는 읽을 수만 있을뿐 내용을 변경할 수 없다.

String클래스의 주요 메서드

charAt메서드는 문자열에 지정된 index에 있는 한 문자를 가져온다. 배열에서 ‘배열이름[index]’로 index에 위치한 값을 가져오는 것과 같다고 생각하면 된다. 배열과 마찬가지로 index값은 0부터 시작한다.

substring()은 문자열의 일부를 뽑아낼 수 있다. 주의할 점은 범위의 끝은 포함되지 않는다는 것이다.

  String str = "012345";
  String tmp = str.substring(1, 4);
  System.out.println(tmp); // 123이 출력된다.

equals()는 문자열의 내용이 같은지 확인하는데 사용된다. 기본형 변수의 값은 ==연산자를 사용하지만, 문자열의 내용을 비교할 때는 equals()를 사용해야 한다. 이 메서드는 대소문자를 구분한다는 점을 주의해야 한다. 대소문자를 구분하지 않을려면 equalsIgnoreCase()를 사용해야한다.

char배열과 String클래스의 변환

  char[] chArr = {'A', 'B', 'C'};
  String str = new String(chArr); // char배열 -> String
  char[] tmp = str.toCharArray(); // String -> char배열

2.4 커맨드 라인을 통해 입력받기

Scanner클래스의 nextLine()외에도 사용자로부터 값을 입력받을 수 있는 방법이 있다. 커맨드라인을 이용하는 방법인데, MainTest라는 클래스가 있다고 가정했을 때 다음과 같이 실행하면 된다.

  > java MainTest abc 123

커맨드라인을 통해 입력된 두 문자열(‘abc’, ‘123’)은 String배열에 담겨서 MainTest클래스의 main메서드의 매개변수(args)에 전달된다. 그리고 main메서드 내에서 args[0], args[1]과 같은 방식으로 전달받은 문자열에 접근할 수 있다. 여기서 args[0]은 abc이고 args[1]은 123이다.

커맨드라인에 입력된 매개변수는 공백문자로 구분하기 때문에 입력될 값에 공백이 있는 경우 큰 따옴표로 감싸주어야 한다. 또한 숫자를 입력해도 문자열로 처리된다. 커맨드라인에 매개변수를 입력하지 않으면 크기가 0인 배열이 생성되어 args.length는 0이 된다.

위로

다차원 배열

3.1 2차원 배열의 선언과 인덱스

2차원 배열을 선언하는 방법은 1차원 배열에 괄호를 하나 더 추가하는 것이다. 2차원 배열은 주로 테이블 형태의 데이터를 담는데 사용된다.

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


| 참고 | 3차원 이상의 고차원 배열의 선언은 차원의 수만큼 대괄호 []를 추가해주면 된다.

2차원 배열의 index

2차원 배열은 행(row)와 열(column)로 구성되어 있기 때문에 index도 행과 열에 각각 하나씩 존재한다. 행index의 범위는 ‘0 ~ 행의 길이 - 1’이고 열index의 범위는 ‘0 ~ 열의 길이 - 1’이다. 배열의 각 요소에 접근하는 방법은 배열이름[행index][열index]이다.

3.2 2차원 배열의 초기화

2차원 배열도 괄호 { }를 사용해서 생성과 초기화를 동시에 할 수 있다. 단, 1차원 배열보다 괄호를 한 번 더 써서 행별로 구분해 준다.

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

크기가 작은 배열은 한 줄로 써도 되지만, 가능하면 행 별로 줄바꿈을 해주는 것이 이해하기 쉽다.

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

2차원 배열은 배열의 배열로 구성되어 있다. 즉, 여러 개의 1차원 배열을 묶어서 배열로 만든 것이다. 2차원 배열에서 배열의 길이는 참조변수가 참조하고 있는 배열의 길이를 세어보면 된다. 예를 들어 다음과 같은 배열이 있다.

  int[][] score = {
    {100, 100, 100},
    {20, 20, 20},
    {30, 30, 30},
    {40, 40, 40},
    {50, 50, 50}
  };

score.length는 5이지만, score[0].length는 3이다. 배열 참조변수가 다르기 때문이다.

3.3 가변 배열

2차원 이상의 다차원 배열을 생성할 때 전체 배열 차수 중 마지막 차수의 길이를 지정하지 않고, 추후에 각기 다른 길이의 배열을 생성함으로써 고정된 형태가 아닌 보다 유동적인 가변 배열을 구성할 수 있다.

  int[][] score = new int[5][3];

위 코드는 5 x 3 길이의 직사각형 테이블 형태로 생성되는 2차원 배열이다. 이 코드는 다음과 같이 표현할 수 있다.

  int[][] score = new int[5][];
  score[0] = new int[3];
  score[1] = new int[3];
  score[2] = new int[3];
  score[3] = new int[3];
  score[4] = new int[3];

2차원 배열은 다음과 같이 각 행마다 다른 길이의 배열을 생성하는 것이 가능하다.

  int[][] score = new int[5][];
  score[0] = new int[4];
  score[1] = new int[3];
  score[2] = new int[2];
  score[3] = new int[2];
  score[4] = new int[3];

score.length값은 5이지만, score[0].length값과 score[1].length값이 4와 3으로 서로 다르다.

가변배열도 중괄호 { }를 이용해서 생성과 초기화를 동시에 하는 것이 가능하다.

위로