[java] 배열

LIM JAEHO·2022년 1월 13일
0

Java 학습

목록 보기
1/19

0. 배열이란?


배열은 같은 타입의 여러 개의 변수의 묶음.

만약 우리가 많은 데이터를 저장하기 위해서 데이터의 수만큼 변수를 일일이 생성해야 한다면,

매우 고된일 일 것이다.

핵심은 같은 타입

선언은 아래와 같이 할 수 있다.

여기서 중요한 것은필요한 공간들이 모두 같은 타입이어야 한다는 점이다.

  • 변수 5개 선언
    int score1, score2, score3, score4, score5;
    [그림 5-1 참고] 위의 변수들은 그림과 같이 메모리 공간에서 각각 불연속적으로 배치되어있다.
  • 배열 생성
    int[] score = new int[5]; /* score[0] ~ score[4] 생성
    [그림 5-2 참고] 여기서 score는 값을 저장하는 변수가 아니라 메모리 주소를 참조하는 참조 변수이다. 그리고 score[0] ~ score[4]까지의 각각의 저장공간은 연속적으로 배치되어 있다.

1. 배열 사용방법


배열의 선언

Java에서 배열의 선언 방법은 2가지가 존재한다.

  • 대괄호를 타입 오른쪽에
    // 타입[] 변수이름;
    
    int[] i;
    char[] c;
  • 대괄호를 변수 오른쪽에
    // 타입 변수이름[];
    
    int i[];
    char c[];

회사를 다니거나 몸 담는 프로젝트에 레퍼런스가 없는 이상개발자의 선호도에 따르게 다르게 사용하면 된다고 한다.

배열의 생성

배열을 선언하는 것과 생성하는 것은 엄연히 다르다.

// 배열의 선언
int[] i;

// 배열의 생성
// 변수이름 = new 타입[길이 or 저장공간의 갯수];
i = new int[5];

물론 선언과 생성을 한번에 할 수도 있다.

int[] i = new int[5];

배열의 생성 시 자동초기화

Java에서 배열은 생성만 할 경우, 생성과 동시에 자동으로 초기화된다.

  • 정수형 배열 : 생성과 동시에 자동으로 0으로 초기화된다.
  • 실수형 배열 : 생성과 동시에 자동으로 0.0으로 초기화된다.
  • 문자형 배열 : 생성과 동시에 자동으로 ‘ ‘ (공백문자)로 초기화된다.

생성과 선언의 차이

배열을 선언하는 것은 단지 생성된 배열을 다루기 위한 참조변수 공간이 만들어지는 것을 의미하고,

배열을 생성하는 것은 값을 저장할 수 있는 공간이 만들어지고 참조변수가 저장할 수 있는 공간의 메모리 주소를 저장하는 것을 의미한다.

여기서 중요한 것은 참조변수는 배열의 저장공간 각각을 모두 참조하지 않고, 배열의 첫번째 저장공간만 참조한다.

배열의 초기화

배열의 초기화 방법에는 여러가지가 있다.

기본적으로 Loop를 통해 초기화를 많이 하는데 일정한 규칙성을 띄는 상황을 전제로 하기 때문에,

Java에서는 규칙성이 없는 상황에서 개발자가 임의로 초기화할 수 있는 방법을 제공하고 있따.

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

// 다양한 초기화 방법

// 1. 선언 후, 생성과 초기화 동시에
int[] score;
score = new int[]{0, 1, 2, 3, 4};

// 2. 선언, 생성, 초기화 동시에
int[] score = new int[]{0, 1, 2, 3, 4};

// 3. 선언, 생성, 초기화 동시에 (생략ver)
int[] score = {0, 1, 2, 3, 4};

// Error 발생
int[] score;
score = {0, 1, 2, 3, 4};

메서드와 함께 사용을 하는 경우도 있다.

add메서드를 예로 들어보자.

// add메서드 : int add(int[] arr){ ... }
int result = add(new int[]{0, 1, 2, 3, 4});    // 정상 작동
int result = add({0, 1, 2, 3, 4});    // error

헷갈리면 안돼요

double[] score = new double[5];    // A라는 새로운 객체 할당 -> 0으로 자동초기화
score = new double[]{0.1, 0.2};    // B라는 새로운 객체 할당
System.out.println(score[2]);    // error, 자동초기화가 사라지는게 아니라 애초에 score 참조변수에 각각 다른 객체를 할당

2. 배열의 인덱스

배열의 인덱스

배열에 저장된 값들을 읽고 쓰려면 어떻게 해야 할까?

바로 ‘인덱스’를 사용하면 된다.

int[] value = new int[5];

// 인덱스가 0인 공간에 값을 저장하겠다.
value[0] = 10;

// 인덱스가 0인 공간에 저장된 값을 출력하겠다.
System.out.println(value[0]);

// 인덱스가 0인 공간이란, 배열 value의 연속된 저장공간 중 첫번째 위치한 저장공간을 의미한다.

인덱스의 가장 중요한 점은 [ ] 안에 숫자 리터럴 뿐만 아니라 변수나 수식도 사용가능하다는 점이다.

충격적인 것은 문자 리터럴도 사용가능하다.

(이 경우, Java 컴파일러에서 자동 형변환을 해주는 것 같다.)

int[] arr = new int[5];
int n = 1;

for (int i = 0; i < 5; i++) {
	arr[i] = i;
}

System.out.println(arr[(char)('0' - 48)]);
System.out.println(arr[n]);
System.out.println(arr[2+1]);

인덱스를 사용할 때 주의할 점은 index의 범위를 벗어난 값을 index로 사용하지 않아야 한다.

컴파일은 되지만, Runtime Error가 뜬다.

음수를 인덱스로 사용해도 마찬가지이다.

3. 배열의 길이


배열의 길이

기본적으로 배열의 길이는 한 번 생성하면 절대 바꿀 수 없다.

만약 배열의 저장 공간이 부족한 경우에는 어떻게 해야할까?

더 큰 길이의 배열을 새로 생성해 기존 배열의 내용을 복사하는 방법이 있다.

참고로 배열의 참조변수에 저장된 값을 바꾸는 것은 가능하다.

이게 가능하기 때문에 내용 복사가 가능한 것인데, 자세한 내용은 아래 배열의 복사에서 다루도록 하겠다.

길이가 0인 배열

배열의 길이가 0인 배열도 생성가능하다.

int[] arr = new int[0];
int[] arr = new int[]{};
int[] arr = {};

참조변수는 선언만 하는 경우 기본 값으로 null을 가진다.

배열을 가리키는 참조변수는 null 대신 길이가 0인 배열로 초기화하기도 한다.

지금은 무슨 의미가 있겠나 싶겠지만 꽤 유용하게 사용한다고 한다.

자세한 예제는 천천히 알아보도록 하고 지금은 가능하다는 사실만 알아두도록 하자.

array.length : 배열의 길이

Java에서는 JVM이 모든 배열의 길이를 별도 관리한다.

따라서 array.length 라는 메서드(?)를 통해 배열의 길이에 대한 정보를 얻을 수 있다.

(괄호가 없는 걸로 봐서는 아직 메서드가 정확한 표현인지는 잘 모르겠다.)

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

tmp는 temporary의 약자로 ‘일시적인’이라는 의미를 갖는다고 한다.

중요한 점은 array.length 는 값을 임의로 변경할 수 없는 상수라는 것과 배열의 길이가 바뀌면 자동으로 length 값도 바뀐다는 것이다.

이런 점을 이용해 코드를 수정할 때 Loop를 돌고 있는 배열의 길이를 바꿔야 되는 상황이라면, 아래와 같이 코드를 짤 수 있다.

int[] arr = new int[4]; // int[] arr = new int[5]; 로 수정해도 아무 문제없이 실행된다.

for (int i = 0; i < arr.length; i++) {
	System.out.println(arr[i]);
}
// arr.length가 아니라 양수 리터럴이라면 배열의 길이를 수정할 경우, 
// 조건식에 있는 양수 리터럴도 이중으로 수정해줘야 한다.

4. 배열의 요소 출력


배열의 요소를 출력하는 방법에는 여러가지가 있다.

요소 전체를 출력할 수도 있고, 필요한 요소에 대해서만 출력할 수도 있다.

먼저 필요한 요소에 대해서만 출력하는 방법을 알아보도록 하겠다.

필요한 요소에 대해 출력

아주 쉽다. 그냥 인덱스를 이용해서 출력하기만 하면 된다.

int[] score = new int[5];

score = new int[]{0, 1, 2, 3, 4};

System.out.println(score[1]);    // 1

초기화되지 않는 요소 출력

Java의 배열에서는 원하는 크기의 배열을 생성만 해둘 수도 있다.

그리고 경우에 따라 일부만 초기화할 수도 있다.

그렇다면 일부만 초기화했을 경우, 초기화하지 않은 요소를 출력하면 어떻게 될까?

int[] score = new int[5];

score = new int[]{0, };

System.out.println(Arrays.toString(score));    // 정상 작동
System.out.println(score[1]);    // 컴파일은 되지만, Runtime Error

바로 컴파일은 되지만 Runtime Error가 발생하는 것을 확인할 수 있다.

다음은 요소 전체를 출력하는 방법에 대해 알아보자.

Loop

가장 쉽게 사용할 수 있는 방법으로 반복문을 활용하는 것이다.

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

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

Arrays.toString(array)

다른 방법으로 Arrays.toString(array) 메서드를 사용하는 방법이 있다.

이 메서드는 배열의 모든 요소를 [요소1, 요소2, 요소3, ...] 과 같은 형태로 반환해준다.

import java.util.*;

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

System.out.println(Array.toString(arr));

5. 배열의 참조 변수 출력


참조 변수 출력

배열의 참조 변수를 출력했을 때, 어떤 형식으로 출력되는지도 알고 있을 필요가 있다.

int[] arr = {0, 1, 2, 3};

System.out.println(arr);

// [타입 @ 메모리주소
// [I@4cdf35a9 => 1차원 Int형 @ 메모리주소

// 메모리 주소는 실제 주소가 아닌 내부 주소..?

예외적으로 char[] 타입의 배열에서는 String을 출력한다.

char[] arr = {'a', 'b', 'c', 'd'};

System.out.println(arr);

// abcd

6. 배열의 복사


앞서 말했지만 배열은 한번 생성하면 길이를 변경할 수 없기 때문에 더 많은 저장 공간이 필요할 경우,

더 큰 배열을 새로 생성하고 내용을 복사해야 한다.

배열을 복사하는 방법에는 2가지가 있다.

for문을 이용해 복사하는 방법

int[] arr = new int[]{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;

기본적으로 이러한 방법에는 비용이 많이 들기 때문에 처음부터 길이를 넉넉하게 잡아 배열을 새로 생성하는 상황이 적게끔 하는 것이 중요하다.

배열을 복사하는 과정과 비용

  1. 길이가 10인 배열 tmp를 생성하면 tmp의 각 요소는 int의 기본값인 0으로 초기화된다.
  2. for문을 이용해서 배열 arr의 모든 요소에 저장된 값을 하나씩 배열 tmp에 복사한다.
  3. 참조변수 arr에 참조변수 tmp의 값을 저장한다.
  4. 쓸모가 없게된 과거 arr이 참조하던 배열은 JVM의 가비지 컬렉터에 의해 자동으로 메모리에서 제거된다.

[ 193 ~ 194p 그림 참고 ]

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

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

for문은 요소 하나하나에 접근해 복사를 하지만, arraycopy()는 지정된 범위의 값들을 한번에 통째로 복사한다. 이것이 가능한 이유는 각 요소들이 연속적으로 저장되어 있기 때문이다.

(이 원리에 대해서는 좀 더 자세히 찾아보도록 하자.)

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

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

// arr[0]에서 arr[arr.length-1]까지의 데이터를 
// tmp[0]부터 tmp[arr.length-1]까지 복사하겠다. 

Question List


  • System.arraycopy()의 원리와 for문보다 빠른 이유?

0개의 댓글