JAVA - 배열
배열을 알기 전 왜 배열이 필요한 이유??
먼저 아래의 코드를 보자.
int student1 = 90;
int student2 = 80;
int student3 = 70;
int student4 = 60;
int student5 = 50;
System.out.println("student1 = " + student1);
System.out.println("student2 = " + student2);
System.out.println("student3 = " + student3);
System.out.println("student4 = " + student4);
System.out.println("student5 = " + student5);
5명이니까 이렇게 쓸 수 있는 거지(사실 귀찮다..) 100명, 1000명이 되어버린다면 코딩 양이 너무 증가한다.
이렇게 같은 타입의 변수를 반복해서 선언하고 반복해서 사용하는 문제를 해결하는 것이 바로 배열이다.
단계적으로 구조를 변경해나가보자.
int[] students; // 1. 배열 변수 선언
students = new int[5]; // 2. 배열 생성
//변수 값 대입
students[0] = 90;
students[1] = 80;
students[2] = 70;
students[3] = 60;
students[4] = 50;
//변수 값 사용
System.out.println("student1 = " + students[0]);
System.out.println("student2 = " + students[1]);
System.out.println("student3 = " + students[2]);
System.out.println("student4 = " + students[3]);
System.out.println("student5 = " + students[4]);
여기서 '배열 변수 선언'과 '배열 생성'이라는 것을 했다.
1. 배열 변수 선언
- int[] students;
- 배열을 사용하려면 int[] students; 와 같이 배열 변수를 선언해야 한다.
- 일반적인 변수와 차이점은 int[] 처럼 타입 다음에 대괄호([])가 들어간다는 점이다.
- 배열 변수를 선언한다고 해서 아직 사용할 수 있는 배열이 만들어진 것은 아니다.
- int a 에는 정수를, double b에는 실수를 담을 수 있다.
- int[] students와 같이 배열 변수에는 배열을 담을 수 있다. (배열 변수에는 10, 20과 같은 값이 아니라 배열이라는 것을 담을 수 있다.)
2. 배열 생성
- students = new int[5];
- 배열을 사용하려면 배열을 생성해야 한다.
- new int[5] 라고 입력하면 총 5개의 int 변수가 만들어진다.
- new 라는 뜻은 새로 생성한다는 뜻이고, int[5]는 int형 변수 5개라는 뜻이다.
- 앞서 int student1,2,3,4,5 이런 식으로 선언했던 것을 배열을 통해서 깔끔하게 처리할 수 있게 된다.
배열의 초기화
- new int[5] 라고 하면 총 5개의 int형 변수가 만들어진다. 자바는 배열을 생성할 때 그 내부값을 자동으로 초기화한다.
- 숫자는 0, boolean은 false, String은 null로 초기화된다.
3. 배열 참조값 보관
int[] students = new int[5]; //1. 배열 생성
int[] students = x001;// 2. new int[5]의 결과로 x001 참조값 반환(x001는 예시임)
students = x001;// 3. 최종 결과
- new int[5] 로 배열을 생성하면 배열의 크기만큼 메모리를 확보한다.
- int형 배열 5개 사용하면 4byte * 5 = 20byte를 확보한다.
- 배열을 생성하고 나면 자바는 메모리 어딘가이 있는 이 배열에 접근할 수 있는 참조값(주소라고 생각하면 이해하기 쉬울 거 같다.)을 반환한다.
- 앞서 선언한 배열 변수인 int[] students에 생성된 배열의 참조값(x001)을 보관한다.
- int[] students 변수는 new int[5]로 생성된 배열의 참조값을 가지고 있다.
- 이 변수는 참조값을 가지고 있다. 이 참조값을 통해 배열을 참조할 수 있다. 쉽게 이야기해서 참조값을 통해 메모리에 있는 실제 배열에 접근하고 사용할 수 있다.
만약 실제 참조값을 확인하고 싶다면 배열의 변수를 출력해 보자.
System.out.println("students = " + students); // students = [I@764c12b6
배열 사용
인덱스
배열은 변수와 사용법이 비슷한데, 차이점이 있다면 다음과 같이 [] 사이에 숫자 번호를 넣어주면 된다. 배열의 위치를 나타내는 숫자를 인덱스(index)라 한다.
//변수 값 대입
students[0] = 90;
students[1] = 80;
//변수 값 사용
System.out.println("student1 = " + students[0]);
System.out.println("student2 = " + students[1]);
배열은 0부터 시작하므로 헷갈리지 않게 잘 사용해야 하며, 인덱스의 허용 범위를 넘어설 때 발생하는 오류는 다음과 같다.(참고만)
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of
bounds for length 5 at array.Array1Ref1.main(Array1Ref1.java:14)
기본형 vs 참조형
- 자바의 변수 데이터 타입을 가장 크게 보면 기본형과 참조형으로 분류할 수 있다. 사용하는 값을 직접 넣을 수 있는 기본형, 그리고 방금 본 배열 변수와 같이 메모리의 참조값을 넣을 수 있는 참조형을 분류할 수 있다.
- 기본형(primitive type): 우리가 지금까지 봤던 int, long, double, boolean처럼 변수에 사용할 값을 직접 넣을 수 있는 테이터 타입을 기본형이라 한다.
- 참조형(reference type): int[] students와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입을 참조형이라 한다. 앞으로 배울 객체나 클래스를 담을 수 있는 변수들도 모두 참조형이다.
- 기본형은 선언과 동시에 크기가 정해진다. 따라서 크기를 동적으로 바꾸거나 할 수는 없다. 반면에 앞서본 배열과 같은 참조형은 크기를 동적으로 할당할 수 있다. 예를 들어서 Scanner를 사용해서 사용자 입력에 따라 siza변수의 값이 변하고, 생성되는 배열의 크기도 달라질 수 있다. 이런 것을 동적 메모리 할당이라 한다. 기본형은 선언과 동시에 사이즈가 정적으로 정해지지만, 참조형을 사용하면 이처럼 동적으로 크기가 변해서 유연성을 제공할 수 있다.
- 기본형은 사용할 값을 직접 저장한다. 반면에 참조형은 메모리에 저장된 배열이나 객체의 참조를 저장한다. 이로 인해 참조형은 더 복잡한 데이터 구조를 만들고 관리할 수 있다. 반면에 기본형은 더 빠르고 메모리를 효율적으로 처리한다.
객체 개념을 배울 때 기본형과 참조형에 대해 더 자세하게 배워보는 걸로 하자.
배열 리펙토링
앞서 제일 앞에 있던 코드를 리펙토링 해보자.
** 리펙토링이란 : 기존의 코드 기능은 유지하면서 내부 구조를 개선하여 가독성을 높이고, 유지보수를 용이하게 하는 과정을 말한다. 코드의 중복을 제거하고, 복잡성을 줄이며, 이해하기 쉬운 코드로 만들기 위해 수행된다. 프로그램의 성능을 향상시킬 수도 있으며, 코드의 설계를 개선하는 데에도 도움이 된다.
배열 리펙토링 - 변수 값 사용
먼저 반복문을 통해 변수 값 사용하는 부분을 바꿔보자.
//변수 값 사용
for (int i = 0; i < students.length; i++) {
System.out.println("student" + ( i + 1 ) + " = " + students[i]);
}
- students.length
- 배열의 길이를 제공하는 특별한 기능.
- 참고로 이 값은 조회만 할 수 있다.
- for문 조건이 i < students.length 이므로 i는 0, 1, 2, 3, 4까지만 반복한다.
배열 리펙토링 - 초기화
int[] students = new int[]{90, 80, 70, 60, 50};// 배열 변수 선언, 배열 생성과 초기화
변수 선언과 배열의 생성 및 초기화를 한 줄로 나타낼 수 있다.
배열 리펙토링 - 간단한 배열 생성
int[] students = {90, 80, 70, 60, 50};// 배열 생성과 초기화
사실 이렇게 new int[]도 생략이 가능하다.
2차원 배열 - 시작
방금까지의 배열은 1차원 배열이다. 지금 배우는 것은 2차원 배열이다.
int[][] arr = new int[2][3];
앞에 있는 [2]가 row(행)이고, [3]이 column(열)이다.
// 2*3 2차원 배열은 만든다.
int[][] arr = new int[2][3];
arr[0][0] = 1;//0행, 0열
arr[0][1] = 2;//0행, 1열
arr[0][2] = 3;//0행, 2열
arr[1][0] = 4;//1행, 0열
arr[1][1] = 5;//1행, 1열
arr[1][2] = 6;//1행, 2열
//0행 출력
System.out.print(arr[0][0] + " "); //0열 출력
System.out.print(arr[0][1] + " "); //1열 출력
System.out.print(arr[0][2] + " "); //2열 출력
System.out.println(); //한 행이 끝나면 라인을 변경한다.
//1행 출력
System.out.print(arr[1][0] + " "); //0열 출력
System.out.print(arr[1][1] + " "); //1열 출력
System.out.print(arr[1][2] + " "); //2열 출력
System.out.println(); //한 행이 끝나면 라인을 변경한다.
2차원 배열 - 리펙토링 1
구조 개선 - 행 출력 반복
구조변경
- 출력 부분에 자꾸 비슷한 부분이 반복된다. 2차원이므로 row, column이므로 2중 for문을 쓰면 쉽게 나타낼 수 있다.
for (int row = 0; row < 2; row++) {
for (int column = 0; column < 3; column++) {
System.out.print(arr[row][column] + " "); //0열 출력
}
System.out.println(); //한 행이 끝나면 라인을 변경한다.
구조 개선 - 값 입력
- 배열에 직접 1,2,3 숫자를 적어서 값을 할당하는 것이 아니라, 배열의 크기와 상관없이 순서대로 1씩 증가하는 값을 입력하도록 만들어보자.
// 2*3 2차원 배열은 만든다.
int[][] arr = new int[2][3];
int i = 1;
// 순서대로 1씩 증가하는 값을 입력한다.
for (int row = 0; row < arr.length; row++) {
for (int column = 0; column < arr[row].length; column++) {
arr[row][column] = i++ ;
}
}
향상된 for문
- 앞서 배운 반복문에서 배우지 않은 부분이기도 했고, 내가 부트캠프 수업을 들으면서 잘 이해가 되지 않았던 부분이 있었는데 바로 향상된 for문(Enhanced For Loop)이다. 향상된 for문을 이해하려면 배열을 먼저 알아야 한다. 각각의 요소를 탐색한다는 의미로 for-each문이라고도 많이 부른다. 이 것을 쓰면 좀 더 편리하고 간결한 코드 작성이 가능하다.
for (변수 : 배열 또는 컬렉션) {
// 배열 또는 컬렉션의 요소를 순회하면서 수행할 작업
}
예시를 보며 확인해 보자.
int[] numbers = {1, 2, 3, 4, 5};
//일반 for문
for (int i = 0; i < numbers.length; ++i) {
int number = numbers[i];
System.out.println("number = " + number);
}
//향상된 for문, for-each문
for (int number : numbers) {
System.out.println("number = " + number);
}
//for-each문을 사용할 수 없는 경우, 증가하는 index 값 필요
for (int i = 0; i < numbers.length; i++) {
System.out.println("number" + i + "번의 결과는 : " + numbers[i]);
}
여기서 향상된 for문, for-each문에 대해 설명해 보자면,
- for-each문은 배열의 인덱스를 사용하지 않고, 종료 조건을 주지 않아도 된다. 단순히 해당 배열을 처음부터 끝까지 탐색하다.
- : 오른쪽에 number와 같이 탐색할 배열을 선택하고, : 왼쪽에 있는 int number와 같이 반복할 때마다 찾은 값을 저장할 변수를 선언한다. 그러면 배열의 값을 하나씩 꺼내서 왼쪽에 있는 number에 담고, for문을 수행한다. for문의 끝에 가면 다음 값을 꺼내서 number에 담고 for문을 반복 수행한다. numbers 배열의 끝에 도달해서 더 값이 없으면 for문이 종료된다.
- for-each문은 배열의 인덱스를 사용하지 않고도 배열의 요소를 순회할 수 있기 때문에 코드가 간결하고 가독성이 좋다.
for-each문을 사용하지 못하는 경우
- for-each문은 인덱스가 숨겨져 있다. 따라서 int i와 같이 증가하는 인덱스 값을 직접 사용해야 하는 경우에는 for-each문을 사용할 수 없다.
int i = 0;
for (int number : numbers) {
System.out.println("number" + i + "번의 결과는 : " + number);
i++;
}
물론 이런 식으로 쓸 수 있지만 이럴 거면 for-each문을 굳이 써야 할까??라는 생각이 든다.