15 min read

<Java> 배열1

<Java> 배열1
  • 배열의 경우, 앞서 몇번 언급이 된 적이 있는 주제입니다.정의를 내려보자면 같은 타입의 변수를 하나의 묶음으로 다루는 것을 배열(array)라고 합니다.조금 더 풀어보면 정수 100,000개를 변수 하나 하나로 선언하고 초기화하며 사용하는것은 너무 비효율적이라는 생각이 들지 않나요? 이러한 비효율성을 해결하기 위한 하나의 자료구조라 보시면 좋을 것 같습니다.
  • int[] point = new int[5]와 같은 방식으로 배열을 생성 할 수 있습니다. new 키워드 어디서 많이 보시지 않았나요? 그렇습니다. 저희가 Reference Type을 배울 때 해당 타입의 변수를 생성하고 초기화 할 때 사용한다고 언급했습니다.즉, 배열의 경우에도 참조형변수와 동일하게 어떠한 공간에 주솟값을 가지며, 해당 주솟값을 참조하면 데이터들이 나열되어 있다고 생각하시면 됩니다.
  • 위에 언급된 코드 한문장을 그림으로 나타내면 아래와 같습니다.특정한 주솟값을 가지는 point라는 변수는 주솟값을 변수로 가집니다. 그리고 해당 주솟값을 참조해서 따라가보면 int형 데이터가 나열된 공간이 나와있는 것을 알 수 있습니다.

배열의 선언과 생성

  • 배열을 선언과 생성하는 법은 이미 언급 되었지만, 코드를 통해 확실하게 알아봅시다.타입[] 변수이름 = new 타입[길이]와 같은 방식으로 이루어집니다.
  • 위의 방식은 크게 배열을 선언하는 부분(배열을 다루기 위한 참조변수 선언)과 실제 저장할 공간인 배열을 생성하는 부분으로 나누어 집니다.
  • 다시한번 강조하겠습니다. 배열은 참조형 변수와 마찬가지로 주솟값을 변수로 가지는 참조변수를 선언하고 해당 변수에 데이터가 저장된 공간의 주소를 저장합니다.

Index와 ArrayIndexOutOfBoundsException

  • 이번에 얘기할 주제는 배열의 길이와 관련이 깊습니다.먼저 index에 대해 설명하면 사전적 정의로는 색인이라고 하는데 쉽게 말하면 학급 번호와 같이 각각의 요소를 구별하기위한 고유한 번호라고 생각하면 됩니다.다만 프로그래밍에서 색인은 현실과 달리 0부터 시작하는 점만 고려해주면 됩니다.
  • 배열의 길이는 위의 사진을 참고하시면 크기가 5인 경우 길이도 5가 됩니다.즉 0부터 4까지의 index을 가지게 됩니다.
  • 그러면 이러한 index는 구체적으로 어떻게 활용될까요? 우리가 배열의 요소에 접근을 하고 싶을 경우 index를 사용합니다. point[2] = 100라고 명령을 내리면 point[2]라는 변수에 100이라는 값을 대입해줍니다. 즉, 2라는 index를 가지는 요소에 접근하여 수정할 수 있습니다.
  • 마지막으로 배열의 길이와 관련하여 주의해야 할 점을 알려드리겠습니다.상식적인 선에서 생각을 해봤을 때 index범위를 벗어난 값을 사용하려고 하면 에러가 발생할것 같지 않나요? 즉, 길이가 5인 배열에 6번째 요소에 접근하려고 하면 당연히 에러가 발생하는 것과 동일한 얘기입니다.
  • 이렇게 배열의 범위를 벗어나 접근하려 할 경우 발생하는 에러가 해당 주제의 제목에 나와있는 에러입니다.
  • 해당 에러를 일부러 발생시키는 예제를 알아봅시다.
public class ArrayEx1 {
    public static void main(String[] args) {
        int[] score = new int[5];
        int k = 1;

        score[0] = 50;
        score[1] = 60;
        score[k+1] = 70;
        score[3] = 80;
        score[4] = 90;

        int tmp = score[k + 2] + score[4];

        for (int i = 0; i < score.length; i++) {
            System.out.printf("score[%d] : %d \n", i, score[i]);
        }
        System.out.printf("tmp:%d \n", tmp);
        System.out.printf("score[%d] : %d \n",7,score[7]);
    }
}
  • 아마 코드를 실행시켜보시면 맨 아래에 ArrayIndexOutOfBoundsException가 발생했을 것입니다.이는 당연히 index의 범위가 0에서 4이 배열에 index 7로 접근하려고 해서 발생한 에러입니다.
  • 처음 문법을 공부하시는 분들은 종종 만나는 에러니 익숙해지면 좋을 것 같습니다.
  • 또 한가지 더 눈여겨 볼 문법이 for문에 있습니다.for문의 조건식에 처음보는 문법을 사용했습니다.바로score.length인데요.이는 score라는 배열의 길이값을 정수로 가지고 있는 상수입니다.즉, 배열의 길이에 대해서 자바문법내에서 자체적으로 관리를 하고 있다는 의미입니다.
  • 배열의 길이에 대해서 좀 더 설명드리면, 기본적으로 배열은 한번 생성하면 길이를 변경할수 없는 구조이기에 length의 경우에도 상수로 취급되어 읽기만 가능할뿐, 수정은 불가합니다.
  • 참고로 배열의 길이는 조금 이상하지만 0인 경우도 가능합니다.즉, length의 값이 0인 경우라고 볼 수 있겠죠. 추후에 다루니 간단히 언급만 하고 넘어가겠습니다.

배열의 길이 변경하기

  • 위에 언급된 내용대로 배열의 경우 한번 선언되면 길이를 변경할수 없습니다.그러면 저장할 공간이 부족해지는 경우 어떻게 변경할수 있을까요?
  • 공간을 이사가면 된다고 생각하셔도 됩니다.즉, 더 큰 길이의 새로운 배열을 생성한 다음 기존의 배열에 저장된 값들을 새로운 배열에 복사하면 됩니다.
  • 하지만 이러한 과정은 메모리에 대한 비용이 크기 때문에 처음부터 넉넉하게 배열의 공간을 만들어두는게 가장 좋은 방법이라고 볼 수 있습니다.(사실 공간을 너무 크게 잡으면 idle한 공간이 늘어나는 것도 문제라는것도 알아만 둡시다!)

배열의 초기화

  • 배열은 생성과 동시에 자동적으로 자신의 타입에 해당하는 기본값으로 초기화되므로 배열을 사용하기 전에 따로 초기화를 해주지 않아도 되지만, 원하는 값을 저장하려면 아래과 같은 방식을 사용하면 됩니다.
int[] score = new int[]{50,60,39,11,34};
int[] score = {50,60,39,11,34}; // new int[] 생략가능
  • 만약 아래와 같이 괄호 안에 아무것도 넣지않으면, 길이가 0인 배열이 생성됩니다.
  • 참조변수가 가르키는 값은 길이가 0인 배열의 주솟값입니다.
	int[] score = new int[0];
	int[] score = {};
    int[] score1 = new int[]{};

배열 주소값,요소 출력하기

  • 크게 나누면 배열의 요소를 출력하는것과 배열의 참조변수의 값, 즉 주소값을 출력하는 방식을 말씀드리겠습니다.
  • 아래예제를 통해 바로 보겠습니다.
public class ArrayEx2 {
    public static void main(String[] args) {
        int[] iArr1 = new int[10];
        int[] iArr2 = new int[10];
        //int[] iArr3 = new int[]{100, 95, 80, 70, 60};
        int[] iArr3 = {100, 95, 80, 70, 60};
        char[] chArr = {'a', 'b', 'c', 'd'};

        for (int i = 0; i < iArr1.length; i++) {
            iArr1[i] = i + 1;
        }
        for (int i = 0; i < iArr2.length; i++) {
            iArr2[i] = (int) (Math.random() * 10) + 1;
        }
        for (int j : iArr1) {
            System.out.print(j + ",");
        }
        System.out.println();
        System.out.println(Arrays.toString(iArr2));
        System.out.println(Arrays.toString(iArr3));
        System.out.println(Arrays.toString(chArr));
        System.out.println("iArr1 = " + iArr1);
        System.out.println("iArr2 = " + iArr2);
        System.out.println("iArr3 = " + iArr3);
        System.out.println(chArr);
    }
}
  • 요새를 각각 출력하는 방법은 크게 두가지로 나뉩니다.for문을 사용하거나 Arrays.toString 메서드를 활용해서 출력할 수 있습니다.사실 후자의 경우가 더 간편한 편이라 배열의 모든 요소를 확인 할 경우 자주 사용됩니다.
  • 배열의 변수 자체를 출력하는 부분이 있는데 이는 해당 참조변수가 가지고 있는 주소값을 출력시켜줍니다.[I@6442b0a6와 같은 구조의 주소값이 출력이 될것 입니다. 이 출력값의 @뒷부분의 16진수를 10진수로 변경하면 참조변수의 해시코드 값과 동일한 값이 나옵니다.즉, 실제로 저장된 메모리 주소를 암호화한 해시코드의 16진수 변환값이라고 보시면 됩니다. 만약 궁금하신 분들은 iArr1.hashcode()를 통해 직접 알아보셔도 좋을 것 같습니다.
  • 만약 실제 주솟값이 궁금하시다면, 브레이킹 포인트를 찍고 디버거를 돌려보시면 알 수 있습니다.

배열 복사하기

  • 앞서 언급되었다시피 배열의 경우 한번 생성되면 그 크기를 변경할 수 없습니다.그래서 배열의 크기를 변경하고 싶다면 복사를 통해서 새롭게 공간를 할당받아 그 곳에 기존 배열의 요소들을 복사해야합니다.지금부터 배열 복사를 진행하는 두가지 과정을 알아보겠습니다.

for문을 활용한 복사하기

int[] number = {1,2,3,4,5};
int[] newArray = new int[10];
for(int i = 0 ;i < number.length(); i++){
	newArray[i] = number[i];
}
number = newArray;
  • for문을 통해 각각의 요소에 접근하여 복사해야할 값들을 대입하는 것까지는 쉽게 이해할 수 있습니다.결과적으로 길이가 10인 배열에 기존의 값들을 복사하면 {1,2,3,4,5,0,0,0,0,0}을 가지는 배열이 생길 것입니다.
  • 이후 마지막 문장이 상당히 중요한 의미를 가지고 있습니다. newArray라는 참조변수는 현재 길이가 10인 공간을 가르키고 있습니다.이 말인 즉슨 해당 공간에 대한 주솟값을 가지고 있다는 것과 일맥상통합니다.이 주솟값을 대입 연산자를 통해 number라는 참조변수에 대입시켜줍니다.그러면 기존에 number가 가르키고 있던 메모리 공간에 대한 주소가 사라지며, 길이가 10인 메모리 공간에 대한 주소가 대입될 것입니다.즉, 서로다른 참조 변수가 2개가 같은 메모리 공간을 가르키고 있는 상황입니다.
  • 여기서부터는 위의 내용이 이해가 가지 않으시는 분들은 그냥 넘어가도 됩니다.위의 상황에서 할당된 메모리 공간이 총 2개입니다.또한 현재 참조변수들은 길이 10짜리 메모리 공간을 동시에 가르키고 있습니다.그러면 길이 5짜리 공간의 경우 아무런 참조변수로부터 참조를 받고 있지 않은 상황입니다.즉, 프로그램이 접근 할 수 없는 메모리 공간이 되어버린 것입니다.상식적으로 아무도 접근하지 못하는 메모리 공간이 있다는 것 자체가 비효율적이지 않나요? 이처럼 자신을 가르키는 참조변수가 없는 메모리는 JVM의 Garbage Collector에 의해서 수거됩니다.GC가 하는 대표적인 일 중 하나이니 기억해두시면 좋을 것 같습니다.

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

  • for문 대신 System클래스의 arraycopy 메서드를 이용하면 보다 빠르고 간단하게 배열을 복사 할 수 있습니다.for문의 경우 배열의 요소 하나하나에 모두 접근하지만, arraycopy()의 경우 지정된 범위의 값들을 한번에 통째로 복사합니다.각 요소들이 연속적으로 저장되어 있다는 배열의 특성때문에 이러한 방식이 가능합니다.
  • arraycopy()을 호출할 때는 어느 배열의 몇 번째 요소에서 어느 배열로 몇 번째 요소로 몇개의 값을 복사할 것인지를 메서드의 인자로 넣어주면 됩니다.(인자라는 단어의 경우 메서드의 괄호안에 들어가는 값이라고 일단은 생각하시면 됩니다!)
  • 사용방법의 경우 아래 예제를 통해 알아봅시다.
public class ArrayEx4 {
    public static void main(String[] args) {
        char[] abc = {'A', 'B', 'C', 'D'};
        char[] num = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
        System.out.println(abc);//char date type => 16진수 hashcode 값 나오지 않음
        System.out.println(num);

        char[] result = new char[abc.length + num.length];
        System.arraycopy(abc, 0, result, 0, abc.length);
        System.arraycopy(num, 0, result, abc.length, num.length);
    }
}

배열 활용하기

  • 현재 내용까지 배열의 기본적인 문법에 대해 전부 학습했습니다. 그러면 이제 예제들을 따라하면서 우리가 배운 배열이 어떻게 활용 될 수 있는가에 대한 감을 잡으실 수 있을 것입니다.
  • 첫번째 예제는 배열의 모든 요소를 더해서 총합과 평균을 구하는 문제입니다.
public class ArrayEx5 {
    public static void main(String[] args) {
        int sum = 0;
        float average = 0f;

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

        for (int j : score) {
            sum += j;
        }
        average = sum / (float)score.length;

        System.out.println("sum = " + sum);
        System.out.println("average = " + average);
    }
}
  • 평균과 합을 구하는 수식은 직관적으로 이해될 것입니다.다만 여기서 enhanced for문을 조금 눈여겨보시고 익숙해지는 연습을 하시면 해당 구조에 대한 이해도가 증가할 것입니다.
  • 다음 문제는 배열 내에서 최대값과 최솟값을 구하는 예제입니다.실제로 코딩테스트에서 기초적으로 알아야 하는 알고리즘으로 종종 출제됩니다. max와 min 모두 0번째 값으로 두고 for문을 통해 하나씩 비교하면서 최대 최소 값을 구하면 됩니다.
public class ArrayEx6 {
    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("min = " + min);
        System.out.println("max = " + max);
    }
}
  • 다음으로 볼 예제는 배열의 각요소간의 shuffle을 진행하는 문제입니다.
public class ArrayEx7 {
    public static void main(String[] args) {
        int[] numArr = new int[10];
        for (int i = 0; i < numArr.length; i++) {
            numArr[i] = i;
        }
        System.out.println(Arrays.toString(numArr));
        for (int i = 0; i < numArr.length; i++) {
            int n = (int) (Math.random() * 10);
            int tmp = numArr[0];
            numArr[0] = numArr[n];
            numArr[n] = tmp;
        }
        System.out.println(Arrays.toString(numArr));
    }
}
  • 0번째 index에는 0처럼 각각의 index값을 요소 값으로 초기화해줍니다.
  • 이후, 앞선 포스팅에서 배운 swap 알고리즘을 이용해서 각 요소간 값을 변경해줍니다. 이 과정에서 Math.random을 사용해서 얻어낸 값을 index로 사용합니다.
  • 나머지 배열의 활용에 대한 예제들은 깃헙에 있으니 따라해보시며 배열에 대한 이해도를 높이시면 될것 같습니다.

Ref: 자바의 정석-남궁성