배열은 자료 구조의 하나이며, 자료형의 기준으로 따지자면 참조형의 하나이다.
변수는 하나의 데이터만을 저장할 수 있는 반면, 배열은 여러 개의 데이터를 저장할 수 있는 집합 자료형이다.
배열에는 같은 자료형과 같은 성격을 가지고 있는 데이터를 집합으로 넣을 수 있다.
프로그래머에게 요구되는 필수 요소에는 문제해결능력인 알고리즘과 데이터를 관리하는 자료구조가 있는데, 배열은 이중 자료구조에 속하는 중요한 요소이다.
💡배열의 문법
자료형[] 배열명 = new 자료형[길이];
int[] nums = new int[3];
// 길이(방의 개수): 3
// 인덱스(방 번호): 0 ~ 2, 0 ~ 길이 - 1
// 방 1개(데이터): 요소(Element)
JVM에서 new 예약어를 사용하면 메모리 어딘가에 int[3]을 기준으로 공간을 확보한다.
이때 중요한 것은 확보되는 공간이 연속적이며, 서로 인접하다는 점이다.
자료형[] 은 '[](배열 첨자)'까지 하나의 자료형으로 봐야 한다. 따라서 int[]는 integer배열이라고 볼 수 있다.
배열 첨자가 1개일 경우 int[]이면 1차원 배열이며, int[][]로 2개일 경우 2차원 배열을 의미한다.
배열의 자동 초기화
int[] nums = new int[3];
System.out.println(nums[0]); // 0
double[] nums2 = new double[3];
System.out.println(Arrays.toString(nums2)); // [0.0, 0.0, 0.0]
char[] list1 = new char[3];
System.out.println(Arrays.toString(list1)); // [, , ]
boolean[] islist = new boolean[3];
System.out.println(Arrays.toString(islist)); // [false, false, false]
String[] islist2 = new String[3];
System.out.println(Arrays.toString(islist2)); // [null, null, null]
배열(참조형)은 생성 직후에 자동으로 초기화된다는 특징이 있다.
정수형 배열을 살펴보면 아무것도 넣지 않았는데도 '0'이 들어가 있는 것을 볼 수 있다.
0도 엄연한 데이터로, 이는 자바가 자동으로 가장 최소의 값으로 초기화한 것이다.
💡배열 사용 예시
배열을 사용하지 않을 경우
int kor1;
int kor2;
int kor3;
kor1 = 100;
kor2 = 90;
kor3 = 80;
int total = kor1 + kor2 + kor3;
double avg = total / 3.0;
System.out.printf("총점: %d\n", total);
System.out.printf("평균: %f\n", avg);
학생이 3명인 경우 배열을 사용했을 때 메리트는 크게 보이지 않는다.
하지만 학생이 3명이 아니라 300명이라면? 변수는 297개를 더 선언해야 하고, 점수도 297개 더 입력해야 하며, 계산식도 훨씬 길어질 것이다.
배열을 사용할 경우
int[] kors = new int[3];
kors[0] = 100;
kors[1] = 90;
kors[2] = 80;
int total = kors[0] + kors[1] + kors[2];
double avg = total / 3.0;
System.out.printf("총점: %d\n", total);
System.out.printf("평균: %f\n", avg);
하나씩 만들어서 따로 관리하는 방식은 인원이 늘어나면 증가 비용이 일정하게 올라가게 되는데, 이러한 작업에 배열을 사용하면 조직적이고 효과적으로 표현할 수 있다.
💡배열의 길이
new int[3]에서 3은 배열의 길이(배열의 방 개수)인데, 이를 이클립스에서 바로 확인하는 방법이 있다.
length변수는 나중에 배열의 길이를 잘 모르는 경우가 생길 때 사용할 수 있는 속성이다.
배열의 길이 확인하기 (length변수)
System.out.println(배열명.length);
int[] kors = new int[300];
int total = 0;
kors[0] = 100;
kors[1] = 90;
kors[2] = 80;
for (int i = 0; i < kors.length; i++) {
total += kors[i];
}
double avg = total / (double)kors.length;
System.out.printf("총점: %d\n", total);
System.out.printf("평균: %f\n", avg);
배열의 변수는 일련번호처럼 숫자가 부여되어 저장되므로, 일괄적으로 처리하는 게 가능하다.
상수를 적지 않고 length변수로 배열의 길이를 받아내면 배열의 길이가 바뀔 때마다 해야 하는 추가적인 수정 작업이 사라지게 된다.
java.lang.ArrayIndexOutOfBoundsException
int[] nums = new int[3];
nums[0] = 100;
nums[1] = 200;
nums[2] = 300;
nums[3] = 400; // java.lang.ArrayIndexOutOfBoundsException 오류 발생
위 오류는 배열의 없는 방 번호를 사용한 경우 발생하는 오류이다.
int[] nums = new int[3];
for (int i = 0; i < nums.length; i++){
nums[i] = 100 * (i + 1); // java.lang.ArrayIndexOutOfBoundsException 오류가 발생하지 않음
}
length변수를 사용하면 이러한 오류를 예방할 수 있다.
런타임 때 공간 지정
Scanner scan = new Scanner(System.in);
System.out.print("학생수: ");
int count = scan.nextInt();
int[] kors = new int[count]; // 런타임 때 배열의 길이가 정해진다.
메모리의 공간은 한번 할당되면, 절대로 공간을 더 늘리거나 줄일 수 없다. 때문에 변수의 크기를 예측해서 선언해야 하는데, 배열의 길이를 위와 같이 런타임 때 정해줄 수 있다.
배열 내 데이터의 유무 검색
String[] member = {"AAA", "BBB", "CCC", "DDD"};
String name = "AAA";
if (contains(member, name)) {
System.out.println(name + " 있음");
}
else {
System.out.println(name + " 없음");
}
private static boolean contains(String[] member, String name) {
for(int i=0; i<member.length; i++) {
if (member[i].equals(name)) {
// 발견
return true;
}
}
// 발견 실패
return false;
}
member배열에 name변수에 저장된 문자열 데이터가 저장되어 있는지를 검색하는 소스코드이다.
contains()메소드를 참조하여 'AAA' 데이터가 있을 경우 true를 반환하며, 없을 경우 false를 반환한다.
배열 내 데이터의 위치 검색
int index = indexOf(member, name);
System.out.println(index);
private static int indexOf(String[] member, String name) {
for(int i=0; i<member.length; i++) {
if (member[i].equals(name)) {
// 발견
return i; // 발견된 위치 == 방번호
}
}
// 발견 실패
return -1;
}
member배열에 name변수에 저장된 문자열 데이터가 저장되어 있다면, 문자열의 위치를 반환하는 소스코드이다.
indexOf()메소드를 참조하여 'AAA' 데이터가 있을 경우 위치값(i)를 반환하며, 없을 경우 -1을 반환한다.
이때 0을 반환하지 않는 이유는 member[0]에 'AAA'가 저장되어 있기 때문이다,
💡다차원 배열
2차원 배열
int[][] nums2 = new int[2][3];
우리가 생각하는 2차원 배열은 (1)번 그림처럼 생긴 것이고, 대체로 이렇게 표현하는 경우가 많다. 그러나 이 그림은 실제 자바에서의 2차원 배열과 논리적으로 차이가 있다.
자바에는 진정한 의미의 2차원 배열이 없으며, (2)번 그림과 같이 1차원 배열 안에 또 다른 1차원 배열이 있는 형태이다.
따라서 2차원 배열이 메모리 주소를 찾아가는 방식은 위와 같다고 할 수 있다.
겉으로 보기에는 2차원 배열이지만, 사실상 여러 개의 1차원 배열로 구성되어 있는 것을 볼 수 있다.
2차원 배열 사용 예시
int[] nums1 = {10, 20, 30};
for (int i=0; i<nums1.length; i++) {
System.out.println(nums1[i]);
}
int[][] nums2 = {{10, 20, 30}, {40, 50, 60}};
System.out.println(nums2.length); // 2
System.out.println(nums2[1].length); // 3
for (int i=0; i<2; i++) {
for (int j=0; j<3; j++) {
System.out.println(nums2[i][j]);
}
}
2차원 배열은 배열첨자를 2개 입력하여 사용할 수 있다.
💡Arrays 클래스
import java.util.Arrays;
int[] nums = new int[3];
nums[0] = 111;
nums[1] = 222;
nums[2] = 333;
System.out.println(nums); // 해시코드 ([I@17c68925)
System.out.println(Arrays.toString(nums)); // [111, 222, 333]
해시코드는 자바가 임의로 작업한 공간으로, [는 배열, I는 정수형, @ 이후 16진수는 메모리 주소를 나타낸다.
이로써 알 수 있는 점은 배열은 그대로 출력했을 때 의미가 없다는 것이다.
배열을 출력하기 위해서는 배열의 유틸리티 클래스인 Arrays 클래스를 이용할 수 있는데, 이를 사용하면 배열의 상태를 한 눈에 알아보기 쉽게 문자열로 돌려준다. 이를 덤프(dump)한다고 한다.
Arrays.copyOfRange() 메소드
Arrays.copyOfRange(원본 배열, 복사할 시작 인덱스, 복사할 끝 인덱스)
int[] nums = new int[3];
nums[0] = 111;
nums[1] = 222;
nums[2] = 333;
int[] copy;
copy = Arrays.copyOfRange(nums, 0, nums.length); // [111, 222, 333]
Arrays클래스의 copyOfRange()메소드를 이용하면 배열의 값에 의한 복사를 할 수 있다.
sort() 메소드
int[] nums = { 5, 3, 1, 4, 2 };
Arrays.sort(nums); // Quicksort implementations 퀵정렬
System.out.println(Arrays.toString(nums)); // [1, 2, 3, 4, 5]
sort()메소드를 이용하면 퀵 정렬이 가능하다. [5, 3, 1, 4, 2] 값으로 저장된 nums 배열이 자동으로 [1, 2, 3, 4, 5] 값으로 버블 정렬(오름차순)된다.