[Java 21] (5) - array

배열(Array)

 

같은 타입의 여러 변수를 하나로 묶은 것을 '배열(array)'이라고 합니다. 여기서 중요한 것은 같은 타입이어야 한다는 것이며, 서로 다른 타입의 변수들로 구성된 배열은 만들 수 없습니다. 배열은 변수의 선언과 달리 다뤄야할 데이터의 수가 아무리 많아도 단지 배열의 길이만 바꾸면 됩니다.

위 그림은 5개의 int 값을 저장할 수 있는 배열을 그림으로 나타낸 것입니다. 그림에서 알 수 있듯이, 변수와 달리 저장공간이 연속적으로 배치되어 있다는 특징이 있습니다.

 

배열의 선언과 생성

 

배열은 원하는 타입의 변수를 선언하고 변수 또는 타입에 배열을 의미하는 대괄호[ ]를 붙이면 됩니다.

int[] score;	// 타입[] 변수이름;
int score[];	// 타입 변수이름[];

 

배열을 선언한 다음에는 배열을 생성해야 합니다. 배열을 선언하는 것은 단지 생성된 배열을 다루기 위한 참조변수를 위한 공간이 만들어질 뿐이고, 배열을 생성해야만 비로소 값을 저장할 공간이 만들어집니다. 배열을 생성하려면 연산자 'new'와 함께 배열의 타입과 길이를 지정해 주어야 합니다.

int[] score;			// 선언
score = new int[5];		// 생성

int[] score = new int[5];		// 선언 + 생성

 

배열의 선언과 생성 과정을 자세히 살펴보겠습니다.

1.  int[] score;

int타입 배열 참조 변수 score를 선언합니다.

2.  score = new int[5];

연산자 'new'에 의해서 메모리의 빈 공간에 5개의 int타입 값을 저장할 수 있는 공간이 마련됩니다.

그리고 각 배열 요소는 자동적으로 int의 기본값인 0으로 초기화됩니다.

 

끝으로 대입 연산자(=)에 의해 배열의 주소(100번지)가 int배열 참조 변수 score에 저장됩니다.

참조 변수 score를 통해서만 배열에 값을 저장하거나 읽어 올 수 있습니다.

 

배열의 길이와 인덱스

 

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

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

 

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

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

 

배열을 다룰 때 한 가지 주의할 점은 index의 범위를 벗어난 값을 index로 사용하지 않아야 한다는 것입니다. 예를 들어 길이가 5인 배열이 선언되었을 때, index의 범위는 0~4인데 이 범위를 벗어나는 5를 사용하면 안됩니다.

 

유효한 범위를 벗어난 값을 index로 사용하는 것은 흔한 실수지만, 컴파일러는 이러한 실수를 걸러주지 못합니다. 왜냐하면 배열의 index로 변수를 많이 사용하는데, 변수의 값은 실행 시 대입되므로 컴파일러는 이 값의 범위를 확인할 수 없습니다.

int[] arr = new int[5];
int a = arr[5];		// IDE 힌트(Array index is out of bounds)

int i = 5;
int b = arr[i];		// 런타임 에러(ArrayIndexOutOfBoundsException)

그래서 유효한 범위의 값을 index로 사용하는 것은 전적으로 프로그래머의 책임이며, 유효하지 않은 값을 index로 사용하면, 무사히 컴파일을 마쳤더라도 실행 시에 에러(ArrayIndexOutOfBoundsException)가 발생합니다.

다만 배열의 길이나 인덱스가 상수로 확정된 경우에는 IDE가 미리 범위를 검사하여 힌트를 줄 수 있습니다.

 

배열의 길이

배열을 생성할 때는 대괄호[ ] 안에 배열의 길이를 적어야 하는데, 배열의 길이는 배열 요소의 개수, 즉 값을 저장할 수 있는 공간의 개수입니다. 당연히 배열의 길이는 양의 정수여야 하며 최댓값은 거의 제약이 없습니다.

 

그런데 길이가 0인 배열도 생성이 가능합니다. 길이가 0이라는 것은 값을 저장할 수 있는 공간이 하나도 없다는 뜻인데, 프로그래밍을 하다보면 길이가 0인 배열이 필요한 상황이 있고 나름 유용합니다. 이 부분은 나중에 뒤에서 알아보겠습니다.

 

배열의 길이는 '배열이름.length'를 통해서 알 수 있습니다.

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

배열은 한 번 생성하면 길이를 변경할 수 없기 때문에, 배열의 길이는 상수입니다.

 

배열의 길이는 변경할 수 없는데, 만약 배열의 저장할 공간이 부족한 경우에는 어떻게 해야 할까요?

바로 더 큰 길이의 배열을 생성한 다음, 기존의 배열에 저장된 값들을 새로운 배열에 복사해야 합니다. 그러나 이러한 작업들은 비용이 많이 들기 때문에, 처음부터 배열의 길이를 넉넉하게 잡아줘서 새로 배열을 생성해야 하는 일이 가능한 적게 발생하도록 해야 합니다. 

 

배열의 초기화

 

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

int[] score = new int[5];
score[0] = 50;
score[1] = 60;
score[2] = 70;
score[3] = 80;
score[4] = 90;

 

배열의 길이가 큰 경우에는 위와 같이 요소 하나하나에 값을 지정하기 보다는 for문을 사용하는 것이 좋습니다. 그러나 for문으로 배열을 초기화하려면, 저장하려는 값에 일정한 규칙이 있어야만 가능하기 때문에 자바에서는 다음과 같이 배열을 간단히 초기화 할 수 있는 방법을 제공합니다.

int[] score = new int[]{ 50, 60, 70, 80, 90 };	// 선언 + 생성 + 초기화
int[] score = { 50, 60, 70, 80, 90 };	// 선언 + 생성(생략) + 초기화

저장할 값들을 괄호{ } 안에 쉼표로 구분해서 나열하면 되며, 괄호{ } 안의 값의 개수에 의해 배열의 길이가 자동으로 결정되기 때문에 대괄호[ ] 안에 배열의 길이는 적지 않습니다. 그리고 2행과 같이 선언과 초기화를 함께 하는 경우 'new' 연산자로 생성하는 코드를 생략할 수도 있습니다.

 

다만 다음과 같이 배열의 선언과 초기화를 따로 하는 경우에는 생략할 수 없습니다.

int[] score;
score = new int[]{ 50, 60, 70, 80, 90 };	// O
score = { 50, 60, 70, 80, 90 };			// X (생략 불가능)

 

그리고 다음처럼 괄호{ } 안에 아무 것도 넣지 않으면, 길이가 0인 배열이 생성됩니다.

int[] score = new int[0];
int[] score = newint[]{};
int[] score = {};

 

배열의 출력

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

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

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

System.out.println(Arrays.toString(iArr));	// 방법 2

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

 

만약 배열의 요소가 아닌 '배열의 주소'를 가리키는 참조 변수(배열명) iArr의 값을 바로 출력하면 어떻게 될까요?

다음과 같이 '타입@식별자'의 형태로 출력됩니다.

자바에서는 메모리 주소를 직접 노출하지 않기 때문에 배열의 실제 메모리 주소가 아닌, 논리적인 식별자가 출력됩니다.

예외적으로 char 배열은 println 메서드로 참조 변수(배열명)를 출력하면 각 요소가 구분자 없이 그대로 출력됩니다.

 

배열의 복사

 

배열은 한 번 생성하면 그 길이를 변경할 수 없기 때문에 더 많은 저장공간이 필요하면 보다 큰 배열을 새로 만들고 이전 배열로부터 내용을 복사해야 합니다. 이때 배열을 복사하는 방법은 두 가지가 있습니다.

for문을 사용한 복사

int[] arr = {1, 2, 3, 4, 5};
int[] tmp = new int[arr.length * 2];

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

arr = tmp;

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

 

이 과정을 그림으로 살펴보겠습니다.

 

1. 배열 arr의 길이의 2배인 int배열 tmp가 생성되고, 배열 tmp의 각 요소는 int의 기본값인 0으로 초기화됩니다.

2. for문을 이용해서 배열 arr의 모든 요소에 저장된 값을 하나씩 배열 tmp에 복사합니다.

3. 참조변수 arr에 참조변수 tmp의 값을 저장합니다.

결국 참조변수 arr과 tmp는 같은 배열을 가리키며, 배열 arr과 배열 tmp는 이름만 다를 뿐 동일한 배열이 됩니다. 그리고 전에 arr이 가리키던 배열은 더 이상 사용할 수 없게 됩니다. 이렇게 쓸모없게 된 배열은 JVM의 가비지 컬렉터에 의해 자동으로 메모리에서 제거됩니다.

 

System.arraycopy()를 사용한 복사

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

System.arraycopy(arr, 0, tmp, 0, arr.length);

위 코드는 arr[0]에서 tmp[0]으로 arr.length개의 데이터를 복사하라는 의미입니다.

이때 복사하려는 배열의 여유 공간이 적으면 에러(ArrayIndexOutOfBoundsException)가 발생합니다.

 

 

String 배열

 

String 배열의 선언과 생성

 

배열의 타입이 String인 경우에도 int배열의 선언과 생성방법은 다르지 않습니다. 예를 들어 3개의 문자열(String)을 담을 수 있는 배열을 생성하는 문장은 다음과 같습니다.

String[] name = new String[3];

 

위 코드를 실행하면 3개의 String타입의 참조변수를 저장하기 위한 공간이 마련되고 참조형 변수의 기본값은 null이므로, 각 요소의 값은 null로 초기화됩니다.

 

참고로 변수에 타입에 따른 기본값은 다음과 같습니다.

타입 기본값
boolean false
char '\u0000'
byte, short, int 0
long 0L
float 0.0f
double 0.0
참조형 null

 

char 타입의 기본값인 '\u0000'은 유니코드 값이 0인 널 문자로, 화면에 보이지 않을 뿐 실제로는 값이 존재합니다.
이는 참조형에서 사용하는 null과는 다른 개념으로, null은 값 자체가 없음을 의미하지만 널 문자는 분명히 0이라는 값을 갖고 있습니다.

 

String 배열의 초기화

 

초기화 역시 int배열과 동일한 방법으로 합니다.

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

String[] name = new String[]{ "Kim", "Lee", "Park" };

String[] name = { "Kim", "Lee", "Park" };

특별히 String 클래스만 "Kim"과 같이 큰따옴표만으로 간략히 표현하는 것이 허용되지만, 원래 String은 클래스이므로 아래와 같이 'new' 연산자를 통해 객체를 생성해야 합니다.

name[0] = new String("Kim");

위 그림을 보면 배열에 실제 객체가 아닌 객체의 주소가 저장되어 있는 것을 볼 수 있습니다. 이처럼, 기본형 배열이 아닌 경우, 즉 참조형 배열의 경우 배열에 저장되는 것은 객체의 주소입니다. 

 

char배열과 String 클래스

 

지금까지 문자열을 저장할 때 String 타입의 변수를 사용했습니다. 사실 문자열은 문자를 연이어 늘어놓은 것으로 char 배열과 같은 뜻입니다. 그런데 자바에서 char배열이 아닌 String 클래스로 문자열을 처리하는 이유는 String 클래스가 char 배열에 여러 가지 기능(메서드)을 추가하여 확장한 것이기 때문입니다.

 

객체지향개념이 나오기 이전의 언어들은 데이터와 기능(메서드)을 따로 다루었지만, 객체지향언어에서는 데이터와 그와 관련된 기능을 하나의 클래스에 묶어서 다룰 수 있게 합니다. 

 

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

String str = "JAVA";
str = "KOTLIN";

위 코드를 보면 문자열 str의 값이 변경되는 것 같지만, 실제로는 새로운 문자열이 생성된 것입니다.

 

String클래스의 주요 메서드

String클래스의 가장 기본적인 메서드 몇 가지만 알아보겠습니다.

메서드 설명
char charAt(int index) 문자열에서 해당 위치(index)에 있는 문자를 반환
int length() 문자열의 길이를 반환
String substring(int from, int to) 문자열에서 해당 범위에 있는 문자열을 반환 (from<= x < to)
boolean equals(Object obj) 문자열의 내용이 같은 확인 후 true, false 반환
char[] toCharArray() 문자열을 문자 배열로 변환해서 반환

 

 

커맨드 라인 입력

 

Scanner클래스의 nextLine()외에도 화면을 통해 사용자로부터 값을 입력받을 수 있는 간단한 방법이 있습니다. 바로 커맨드라인을 이용한 방법인데, 프로그램을 실행할 때 클래스 이름 뒤에 공백문자로 구분하여 여러 개의 문자열을 프로그램에 전달할 수 있습니다.

 

만일 실행할 프로그램의 main메서드가 담긴 클래스의 이름이 MainTest라고 가정하면 다음과 같이 실행할 수 있습니다.

$ java MainTest abc 123

위와 같이 커맨드라인으로 입력된 문자열 "abc"와 "123"은 String배열에 담겨서 MainTest 클래스의 main메서드의 매개변수(args)에 전달됩니다. 그러면 main메서드 내에서 차례로 args[0], args[1]과 같은 방식으로 커맨드라인으로부터 전달받은 문자열에 접근할 수 있습니다.

 

커맨드라인에 입력된 매개변수는 공백 문자로 구분하기 때문에 입력값에 공백이 있는 경우 큰따옴표로 감싸주어야 합니다. 그리고 커맨드라인에서 숫자를 입력해도 숫자가 아닌 문자열로 처리됩니다.

 

그리고 커맨드라인에 매개변수를 입력하지 않으면 크기가 0인 배열이 생성되어 args.length의 값은 0이 됩니다. 입력된 매개변수가 없다고 null이 되는 것이 아닌 빈 배열을 생성합니다.

 

 

다차원 배열

 

2차원 배열의 선언

 

2차원 배열을 선언하는 방법은 1차원 배열과 같습니다. 다만 대괄호[ ]가 하나 더 들어갈 뿐입니다.

int[][] score;		// 타입[][] 변수이름;
int score[][];		// 타입 변수이름[][];
int[] score[];		// 타입[] 변수이름[];

 

2차원 배열은 주로 테이블 형태의 데이터를 담는데 사용되며, 만일 4행 3열의 데이터를 담기 위한 배열을 생성하려면 다음과 같이합니다.

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

위 문장이 수행되면 아래의 그림처럼 4행 3열의 데이터, 모두 12개의 int값을 저장할 수 있는 공간이 마련됩니다.

위 그림에서는 각 요소, 즉 저장 공간의 타입을 적어놓은 것이고, 실제로는 배열 요소의 타입인 int의 기본값인 0이 저장됩니다.

2차원 배열은 행(row)과 열(column)로 구성되어 있기 때문에 index도 행과 열에 각각 하나씩 존재합니다. 예를 들어 1행 1열의 경우 score[0][0]으로 접근할 수 있습니다.

 

2차원 배열의 초기화

 

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

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

크기가 작은 배열은 2열과 같이 한 줄로 써도 되지만, 3행과 같이 줄 바꿈을 해주는 것이 보기도 좋고 이해하기 쉽습니다.

 

위 코드들이 수행된 후에, 2차원 배열 arr이 메모리에 어떤 형태로 만들어지는지 그림으로 알아보겠습니다.

2차원 배열은 '배열의 배열'로 구성되어 있습니다. 여러 개의 1차원 배열을 묶어서 또 하나의 배열로 만든 것입니다.

 

이 2차원 배열의 행과 열의 길이는 위와 같이 구할 수 있습니다.

arr.length		// 행의 길이
arr[0].length		// 열의 길이

 

가변 배열

 

자바는 2차원 이상의 배열을 '배열의 배열'의 형태로 처리한다는 사실을 이용하면 보다 자유로운 형태의 배열을 구성할 수 있습니다.

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];

 

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

'Lang > Java' 카테고리의 다른 글

[Java 21] (7) - Object-oriented Programming 2  (0) 2025.10.03
[Java 21] (6) - Object-oriented Programming 1  (0) 2025.09.29
[Java 21] (4) - statement  (0) 2025.09.22
[Java 21] (3) - operator  (0) 2025.09.18
[Java 21] (2) - variable  (0) 2025.09.15