🌑 배열 (Array)
배열이 왜 필요할까?
- 같은 타입의 변수를 반복해서 선언하고 반복해서 사용하는 문제를 해결하기 위해 !
int student1 = 90;
int student2 = 80;
int student3 = 70;
int student4 = 60;
int student5 = 50;
System.out.println("학생1 점수: " + student1);
System.out.println("학생2 점수: " + student2);
System.out.println("학생3 점수: " + student3);
System.out.println("학생4 점수: " + student4);
System.out.println("학생5 점수: " + student5);
- 배열이란, 같은 타입의 여러 변수를 사용하기 변하게 하나로 묶어둔 것 !
int[] students; // 배열 변수 선언
students = new int[5]; // 배열 생성
// 변수 값 대입
students[0] = 90;
students[1] = 80;
students[2] = 70;
students[3] = 60;
students[4] = 50;
// 변수 값 사용
System.out.println("학생1 점수: " + students[0]);
System.out.println("학생2 점수: " + students[1]);
System.out.println("학생3 점수: " + students[2]);
System.out.println("학생4 점수: " + students[3]);
System.out.println("학생5 점수: " + students[4]);
배열의 "선언"과 "생성"
배열 변수 선언
- 배열을 다루기 위한 참조변수를 선언하는 것 !
타입[] 변수이름;
타입 변수이름[];
int[] students;
💡 배열 변수 선언과 배열 생성은 다른 것 !
배열 변수를 선언했다고 해서 사용할 수 있는 배열이 만들어진 것은 아니다!
배열 생성(≠ 초기화)
- 실제 저장공간을 생성하는 것 !
배열변수이름 = new 타입[길이];
students = new int[5];
➙ 5개의 `int`형 변수가 만들어진 것과 같다.
➙ Java는 배열을 생성할 때 그 내부값을 자동으로 초기화한다 !
( 숫자는 `0`, `boolean`은 `false`, `String`은 `null`로 초기화된다. )
- 배열 참조값 보관
➙ `int[] students` 변수는 `new int[5]`로 생성한 배열의 참조값을 저장하고 있다.
➙ 즉, 이 변수를 이용해서 메모리에 있는 실제 배열에 접근하고 사용하는 방식이다 !
💡 배열 변수를 그대로 출력해보면 ?
'타입@주소'
➙ 생성된 배열의 참조값(실제 주소는 아니고 내부 주소(실제 주소를 가리키는 별명?))이 출력되는 것을 확인해볼 수 있다 !
int[] students = new int[5]; // 배열은 선언과 생성을 동시에 !
System.out.println(students); // I@4517c264: I는 int형 배열을, @뒤에 16진수는 참조값을 의미 !
💡 `new int[]`를 생략 가능한 경우 ?
선언과 초기화를 동시에 하는 경우에 생략 가능하다 !
int[] example = new int[]{1, 2, 3, 4, 5};
int[] example = {1, 2, 3, 4, 5}; // 선언과 초기화를 동시에 하는 경우엔, new int[] 생략 가능
int[] exampleError;
exampleError = { 1, 2, 3, 4, 5 }; // Error : new int[] 생략 불가능
exampleError = new int[] { 1, 2, 3, 4, 5 };
int add(int[] arr) { /* 내용 생략 */ } // add 메서드
int sum = add(new int[]{1, 2, 3, 4, 5}); // OK
int sum = add({ 1, 2, 3, 4, 5 }); // Error : new int[] 생략 불가능
💡 배열의 길이가 0일 수도 있다 !
int[] arr = new int[0];
int[] arr = new int[]{};
int[] arr = {};
배열 사용
인덱스(index)
- 배열의 요소마다의 위치를 나타내는 숫자이다.
➙ 인덱스(index)의 범위는 0부터 '배열길이 - 1'까지이다 !
int[] students = new int[5];
➙ 이 경우에 배열 길이는 5이며(5개의 요소를 가지는 `int`형 배열), 인덱스(index)는 `0, 1, 2, 3, 4`가 존재한다.
💡 인덱스 허용 범위를 넘어설 때 발생하는 오류
`int[] students = new int[5];`
➙ `students[5]`와 같이 접근 가능한 배열의 인덱스 범위를 넘어가면 다음과 같른 오류가 발생한다.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5 at array.Array1Ref1.main(Array1Ref1.java:14)
💡 `배열이름.length`
해당 배열의 길이값을 얻을 수 있다 !
int[] students = new int[5];
int arrLength = students.legnth; // 5
💡 이미 생성한 배열에 저장할 공간이 부족하다면 ?
배열은 한 번 선언되고 나면 길이를 변경할 수 없다.
➙ 따라서, 더 큰 배열을 새로 생성한 후에, 기존 배열의 데이터를 새로운 배열에 복사해주면 된다 !
➙' 배열의 복사 '
( 배열의 길이를 기존의 2배 정도의 길이 정도로 넉넉하게 잡아주도록 하자..! )
배열에 값 대입
- 참조값(배열 변수)을 통해서 실제 배열에 접근하고, 인덱스를 사용해서 해당 위치의 요소에 접근하여 값을 대입해주면 된다 !
students[0] = 90; // 1. 배열에 값을 대입
x001[0] = 90; // 2. 변수에 있는 참조값을 통해 실제 배열에 접근
// -> 인덱스를 사용해서 해당 위치의 요소에 접근
// -> 값 대입
배열 값 읽기
- 참조값(배열 변수)을 통해서 실제 배열에 접근하고 인덱스틀 통해 원하는 요소를 찾아 읽으면 된다 !
//1. 변수 값을 읽는다.
System.out.println("학생1 점수: " + students[0]);
//2. 변수에 저장된 참조값을 통해 실제 배열에 접근하고, 인덱스를 사용해서 해당 위치의 요소에 접근한다.
System.out.println("학생1 점수: " + x001[0]);
//3. 배열의 값을 읽어온다.
System.out.println("학생1 점수: " + 90);
💡 '배열이름.length'를 활용하여 모든 요소 출력하기 !
int[] students
students = new int[5];
//변수 값 대입
students[0] = 90;
students[1] = 80;
students[2] = 70;
students[3] = 60;
students[4] = 50;
//변수 값 사용
for (int i = 0; i < students.length; i++) { // students.length == 5
System.out.println("학생" + (i + 1) + " 점수: " + students[i]);
}
💡 `char`형 배열은 for문을 사용하지 않고도 `println` 메서드로 모든 요소를 출력 가능하다 !
다른 타입의 배열의 경우엔 배열의 참조값을 출력하지만, `char`형 배열은 실제 요소값을 바로 출력 가능하다 !
int[] iArr = {1, 2, 3, 4};
char[] chArr = {'a', 'b', 'c'};
// 배열 iArr의 모든 요소(element) 출력
System.out.println(iArr); // I@14318bb : iArr의 실제 주소가 아닌 내부 주소 출력
System.out.println(chArr); // {a, b, c} : println 메서드가 char 배열일 때만 가능하도록 되어있다.
System.out.println(Arrays.toString(iArr)); // {1, 2, 3, 4}
💡 기본형(Primitive Type) vs 참조형(Reference Type)
Java의 변수 데이터 타입을 가장 크게 보면 기본형과 참조형으로 분류할 수 있다 !
- 기본형(Primitive Type): 값을 직접 넣을 수 있는 데이터 타입(`int`, `long`, `double`, `boolean` 등).
- 참조형(Reference Type): 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입(`int[] students`).
➙ 뒤에 나올 객체나 클래스를 담을 수 있는 변수들도 모두 참조형이다 !
배열의 복사
`for`문을 이용한 배열 복사
- 기존 배열의 요소에 하나씩 접근해서 새로운 배열에 일일이 복사해주고, 기존 배열을 가리키던 변수가 새로운 배열을 가리키게 바꿔주는 방식이다 !
➙ 새로운 배열은 기존의 2배 정도의 길이로 생성해주면 된다 !
int[] arr = new int[5];
for(int i = 0; i < arr.length; i++)
arr[i] = i + 1;
int[] tmp = new int[arr.length*2];
for(int i = 0; i < arr.length; i++)
tmp[i] = arr[i];
arr = tmp; // tmp이 가리키는 배열의 주소값을 arr에도 저장해준다.
`System.arraycopy()`를 이용한 배열 복사
- 지정된 범위의 값들을 한 번에 통째로 복사하기 때문에 for문보다 효율적 !
➙ 단, 복사 대상 배열에 `arrayName.length`만큼의 여유 공간이 없으면 에러가 발생한다.
System.arraycopy(fromArrayName, 0, toArrayName, 5, fromArrayName.length);
// fromArrayName[0] ~ fromArrayName[fromArrayName.length - 1]의 범위에 해당하는 데이터를
// toArrayName[5]의 위치로 복사한다.
String 배열
String 배열의 선언과 생성
- `int`배열의 선언과 생성방법과 다르지 않다 !
String[] name = new String[3]; // 3개의 문자열을 담을 수 있는 배열 생성.
// `null`로 초기화된다 !
String 배열에 값 대입
- 사실, String은 '클래스'이므로 new 연산자를 이용해서 객체를 생성해야 한다 !
➙ 특별히 String 클래스만 "큰따옴표"만으로 가능하게 허락해주는 것..!
String[] name = new String[3];
name[0] = "Shin"; // name[0] = new String("Shin");
name[1] = "Park";
name[2] = "Kim"
💡 String 배열은 기본형 배열이 아닌 '참조형 배열'이다 !
`int`배열 같은 기본형 배열과 달리 배열에 객체의 주소가 저장된다 !
💡 char배열 대신에 String클래스을 사용하자 !
char배열에 기능(메서드)가 추가된 것이 String클래스이기 때문이다 !
( 단, String객체(문자열)은 값을 변경할 수 없고, 변경되는 것처럼 보여도 사실 새로운 문자열이 생성되어 저장되는 것이다 ! )
String str = "Java";
str = str = "21"; // "Java8"이라는 새로운 문자열이 str에 저장되는 것 !
System.out.println(str); // "Java21"
💡 String클래스의 메서드
char charAt(int index): 문자열에서 해당 위치(index)에 있는 문자 반환.
int length(): 문자열의 길이 반환.
String substring(int from, int to): 문자열에서 해당 범위(from ~ to)에 있는 문자열 반환 (`to`는 범위에 포함 X).
boolean equals(obj): 문자열의 내용이 obj와 같으면 true, 다르면 false 반환.
boolean equalsIgnoreCase(obj): equals()과 같은 기능이지만, 대소문자 구분 X.
char[] toCharArray(): 문자열을 문자배열(char[])로 변환해서 반환.
🌒 다차원 배열
2차원 배열
2차원 배열의 선언과 생성
- 2차원 배열을 말 그대로 1차원 배열에서 하나의 차원이 추가된 것 !
( 수학에서의 행렬 개념처럼 생각하면 된다 ! )
➙ 배열을 선언하고 생성하는 방법도 '괄호[]'가 추가된 것 말고는 1차원 배열과 같다.
2차원 배열 선언 - 타입[][] 변수이름; / 타입 변수이름[][]; / 타입[] 변수이름[];
2차원 배열 생성 - 변수이름 = new 타입[][]
int[][] arr = new int[2][3] // arr[row][column]
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열
2차원 배열의 초기화
- 아래처럼 행별로 줄 바꿈을 해주면 가독성이 좋아진다 !
int[][] arr = new int[][] {{1, 2, 3}, {4, 5, 6}};
int[][] arr = {{1, 2, 3}, {4, 5, 6}}; // new int[][] 생략 가능
int[][] arr = {
{1, 2, 3},
{4, 5, 6}
};
2차원 배열 출력
- 일반적인 for문 이용
- 첫 번째 for문은 '행(row)'을 탐색한다.
- 내부에 있는 두 번째 for문은 '열(column)'을 탐색한다.
// 2x3 2차원 배열 생성.
int[][] arr = new int[2][3]; //행(row), 열(column)
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열
for (int row = 0; row < 2; row++) {
for (int column = 0; column < 3; column++) {
System.out.print(arr[row][column] + " ");
}
System.out.println(); //한 행이 끝나면 라인을 변경한다.
}
리팩토링 1
// 2x3 2차원 배열 생성.
int[][] arr = new int[2][3]; //행(row), 열(column)
int[][] arr = {
{1, 2, 3},
{4, 5, 6}
};
for (int row = 0; row < arr.length; row++) {
for (int column = 0; column < arr[row].length; column++) {
System.out.print(arr[row][column] + " ");
}
System.out.println();
}
리팩토링 2
// 2x3 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++; // `arr[row][column] = ++i`이면?
}
}
// 2차원 배열의 길이를 활용
for (int row = 0; row < arr.length; row++) {
for (int column = 0; column < arr[row].length; column++) {
System.out.print(arr[row][column] + " ");
}
System.out.println();
}
- 향상된 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);
}
//향상된 for문, for-each문
for (int number : numbers) {
System.out.println(number);
}
💡 증가하는 index 값이 필요한 경우엔 '향상된 for문' 이용 X
//for-each문을 사용할 수 없는 경우, 증가하는 index 값 필요
for(int i = 0; i < numbers.length; ++i) {
System.out.println("number" + i + "번의 결과는: " + numbers[i]);
}
- 향상된 for문 사용 - 2차원 배열
int[][] arr = new int[2][3]; //행(row), 열(column)
int[][] arr = {
{1, 2, 3},
{4, 5, 6}
};
int sum = 0; // 모든 요소의 합을 저장할 변수
for (int[] row : arr) { // arr의 각 요소(1차원 배열 주소)를 tmp에 저장
for (int column : row) { // row - 1차원 배열을 가리키는 참조변수
sum += column;
}
}
가변배열
- 2차원 이상의 다차원 배열을 생성할 때 전체 배열 차수 중 마지막 차수의 길이는 지정하지 않아도 오류가 발생하지 않는다 !
➙ 즉, 각 행(row)마다 다른 길이(column)의 배열을 생성하는 것이 가능 !
int[][] arr = new int[3][];
arr[0] = new int[5];
arr[1] = new int[2];
arr[2] = new int[3];
- 가변배열 역시 아래처럼 중괄호{}를 이용해서 생성과 초기화를 동시에 하는 것도 가능하다 !
int[][] score = {
{1, 2, 3, 4, 5},
{6, 7},
{8, 9, 10}
}