1. 다차원 배열
2. 향상된 for문
3. 참조자료형
1. 다차원 배열
다차원 배열은 2차원 이상의 배열을 말합니다. 2차원 배열을 쉽게 생각하면 수학시간에 배웠던 행렬을 생각하거나, 배열 안에 배열이 존재한다고 생각하면 좀더 쉽게 이해할 수 있습니다. 3 * 3 행렬의 구조를 생각해볼까요.
3행 3열의 행렬인데, 각 좌표를 보면 (행번호,열번호)로 이루어져 있습니다. (2,0)은 행의 2번 인덱스, 열의 0번 인덱스가 좌표가 됩니다. 이제 코드로 3x3 행렬의 2차원 배열을 선언하고, 1~9까지의 값을 순서대로 대입해 보겠습니다.
int[ ][ ] matrix = new int[3][3];
matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[1][0] = 4;
matrix[1][1] = 5;
matrix[1][2] = 6;
matrix[2][0] = 7;
matrix[2][1] = 8;
matrix[2][2] = 9;
2차원 배열이므로 타입 뒤의 대괄호 [ ]가 두개가 되고, new int[3][3]은 3x3의 행렬 구조를 의미합니다. 왼쪽위부터 오른쪽으로 차례대로 값을 대입했는데, 2차원 행렬구조로 값을 확인해보면,
2차원 배열 변수의 [ ][ ] 안의 숫자는 행렬 좌표의 숫자와 같습니다. matrix[0][0]의 값은 행렬 (0,0)의 값과 같은 1입니다.
다른 표현으로 2차원 배열은 배열안의 배열이 존재하는 것과 같다고 했습니다. 이 2차원 배열을 조금 다르게 선언할 수도 있습니다.
int[ ][ ] matrix2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int[ ][ ] matrix3 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
matrix2 변수와 matrix3 변수는 완전히 동일한 값을 가지는 배열 변수입니다. matrix3는 가독성을 위해 줄바꿈을 해서 코딩한 것이고, matrix2는 한줄로 코딩한 것입니다. matrix2 배열을 보면 첫번째 0번 인덱스의 값은 {1,2,3} 입니다. 즉 하나의 값이 배열이 된 것입니다. matrix2[0] = {1,2,3} 이므로, matrix2[0][2]는 matrix2의 0번 인덱스의 값(배열)의 2번 인덱스가 됩니다. 따라서 값은 3이 되는 것입니다.
이제 이 3가지의 배열변수 matrix, matrix2, matrix3를 중첩 for문을 통해 행렬구조로 출력하는 예제를 작성해 보겠습니다.
public class ArrSample10 {
public static void main(String[] args) {
// 첫번째
int[][] matrix = new int[3][3];
matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[1][0] = 4;
matrix[1][1] = 5;
matrix[1][2] = 6;
matrix[2][0] = 7;
matrix[2][1] = 8;
matrix[2][2] = 9;
// 두번째
int[][] matrix2 = {{1,2,3}, {4,5,6}, {7,8,9}};
// 세번째
int[][] matrix3 = {
{1,2,3},
{4,5,6},
{7,8,9}
};
System.out.println("[첫번째]");
for (int i=0; i<matrix.length; i++) {
for (int j=0; j<matrix[i].length; j++) {
System.out.print(matrix[i][j]+" ");
}
System.out.println();
}
System.out.println("[두번째]");
for (int i=0; i<matrix2.length; i++) {
for (int j=0; j<matrix2[i].length; j++) {
System.out.print(matrix2[i][j]+" ");
}
System.out.println();
}
System.out.println("[세번째]");
for (int i=0; i<matrix3.length; i++) {
for (int j=0; j<matrix3[i].length; j++) {
System.out.print(matrix3[i][j]+" ");
}
System.out.println();
}
}
}
실행 결과
[첫번째]
1 2 3
4 5 6
7 8 9
[두번째]
1 2 3
4 5 6
7 8 9
[세번째]
1 2 3
4 5 6
7 8 9
첫번째, 두번째, 세번째 모두 2차원 배열로 선언된 형태입니다. 이 세개의 배열 변수를 for문으로 출력한 결과 역시 모두 똑같은 것을 알수 있습니다. for문을 잘 살펴보면 중첩 for문으로 되어 있는데, 바깥쪽 for문은 행(2차원 배열의 길이)이 반복되고, 안쪽 for문은 각 행 배열의 길이만큼 반복되면서 출력됩니다.
3차원 배열도 마찬가지인데요, 배열안에 배열이 또 이 배열안에 배열이 3겹으로 중첩된 것입니다. 다음 예제를 보면 대괄호 [ ]가 하나 더 추가된 것으로 이해하면 그렇게 어렵지 않을 것입니다.
public class ArrSample11 {
public static void main(String[] args) {
int[][][] arrInt = new int[3][3][3];
int value = 0;
// 값 대입
for (int i=0; i<=2; i++) {
for (int j=0; j<=2; j++) {
for (int k=0; k<=2; k++) {
arrInt[i][j][k] = value++;
}
}
}
// 값 출력
for (int i=0; i<=2; i++) {
for (int j=0; j<=2; j++) {
for (int k=0; k<=2; k++) {
System.out.print(arrInt[i][j][k]+"\t");
}
System.out.println();
}
}
}
}
실행 결과
0 1 2
3 4 5
6 7 8
9 10 11
12 13 14
15 16 17
18 19 20
21 22 23
24 25 26
배열이 세번 중첩되면서 복잡해 보일 수 있지만, 이 소스를 한번만 이해하면 앞에서 배운 1차원, 2차원 배열이 더 쉬어지니, 그냥 넘어가지 말고 꼭 예제를 실행해 보세요. 사실 3차원 이상의 다차원 배열은 그렇게 많이 사용되는 편은 아지만, 배열의 이해와 중첩 배열, 중첩 for문을 복습하는 차원에서 실행해보면 도움이 될 것입니다.
1차원 배열에서 배열 변수 선언, 초기화 시 배열의 길이가 정해지고, 변할 수 없다고 했습니다. 하지만 2차원 배열 이상에서는 길이를 미리 초기화하지 않아도 되는데요, 다차원 배열의 첫번째 차원값만 미리 정하고 나머지는 나중에 배열객체를 생성하면서 지정할 수 있습니다.
public class ArrSample12 {
public static void main(String[] args) {
int[][] arrInt = new int[3][];
arrInt[0] = new int[] {1};
arrInt[1] = new int[] {2,3};
arrInt[2] = new int[] {4,5,6};
for (int i=0; i<arrInt.length; i++) {
for (int j=0; j<arrInt[i].length; j++) {
System.out.print(arrInt[i][j]);
}
System.out.println();
}
}
}
실행 결과
1
23
456
arrInt 2차원 배열 생성 시 두번째 배열의 길이 값이 빠져 있습니다. 첫번째 차원은 3으로 지정되었기 때문에 변할 수 없지만 각 값들은 길이가 1인 배열, 길이가 2인 배열, 길이가 3인 배열로 모두 다른 길이의 2차원 배열이 가능합니다.
2. 향상된 for 문
향상된 for문은 기존의 for문에 비해 간단하게 사용할 수 있는 for문입니다. 이전 포스팅의 for문에서 다루지 않고 이번 배열에서 다루는 이유는 향상된 for문은 배열이나 뒤에서 배울 Collection 자료형의 요소를 하나씩 순서대로 처리할 수 있는 for문이라서 배열 챕터에서 다루게 되었습니다.
향상된 for문은 간편하게 사용할 수 있지만, 값을 읽을 수만 있고, 변경할 수 없습니다. 또한 인덱스를 사용할 필요가 없어 오히려 불편할 때가 있기도 합니다. 물론 while문처럼 별도의 변수를 생성해 인덱스처럼 사용할 수는 있습니다. 앞에서 배웠던 배열 변수를 인덱스를 이용해서 일반 for문을 이용해 출력하는 방법과 향상된 for문을 이용해 출력하는 코드를 비교해 보겠습니다.
public class ArrSample16 {
public static void main(String[] args) {
// 배열
int[] arrInt = {1,2,3,4,5};
// 기존 for문을 이용한 출력
for (int i=0; i<arrInt.length; i++) {
System.out.println(arrInt[i]);
}
// 향상된 for문을 이용한 출력
for (int number : arrInt) {
System.out.println(number);
}
}
}
실행 결과
1
2
3
4
5
1
2
3
4
5
향상된 for문의 구조는 for문 괄호 안에 변수가 선언 되어 있고, : (콜론) 뒤에 반복 가능한 배열이 있습니다. 이 배열의 첫번째 요소부터 차례대로 변수 number에 대입되는 것입니다. for문은 이처럼 반복하면서 배열 자료구조 같은 여러 값들을 하나의 실행문으로 처리하기 위해 많이 사용되는데, 굳이 인덱스가 따로 필요없는 경우에 사용하면 편합니다.
3. 참조 자료형
자바에서의 데이터 타입(자료형)은 기본자료형과 참조자료형으로 나누어집니다. 기본자료형(Primitive Type)은 앞에서 배웠던 정수나 실수, 논리 자료형의 리터럴 값을 저장하는 타입인데, 우리는 지금까지 대부분 기본자료형으로 예제들을 살펴봤습니다. 참조자료형(Reference Type)은 기본자료형과는 다르게 리터럴값을 직접 갖고 있는 것이 아니라 값이 저장된 위치의 주소를 참조하는 변수를 말합니다. 참조자료형은 앞에서 배운 배열외에도 클래스, 인터페이스가 이에 해당됩니다.
기본자료형 age 변수는 30이라는 리터럴값을 직접 저장하고 있지만 참조자료형 name과 name2 변수는 값을 직접 저장하고 있는것이 아니라 메모리 주소값만 저장하고 있습니다. name 변수에는 111이라는 주소값이, name2에는 112라는 주소값이 저장되어 있는 것이죠. 실제 값은 메모리에 저장하고 있고 변수는 메모리의 주소만 저장하고 있는 것입니다. 이름 그대로 참조만 하고 있는 형태입니다.
참조자료형은 변수만 선언하고 메모리 영역을 참조하지 않는 상태가 있는데, 이때는 null 값을 가지게 됩니다. 문자열도 String 타입의 객체이므로 new 연산자를 통해 객체를 생성할 수 있습니다. 먼저 문자열을 비교하는 예제를 살펴보겠습니다.
public class ReferenceType {
public static void main(String[] args) {
String name1 = "홍길동";
String name2 = "홍길동";
System.out.println(name1 == name2);
}
}
실행 결과
true
위 예제에서 name1과 name2 변수는 따로 선언하고 초기화했지만 “홍길동”과 “홍길동”을 == 비교하면 당연히 같다고 생각들겠죠? 하지만 값끼리 비교한 것이 아니라, 실제로는 메모리 주소를 참조하고 있는 주소값을 비교한 것입니다. 이번엔 같은 값을 new 연산자를 통해 객체를 생성해서 비교해 보겠습니다.
public class ReferenceType2 {
public static void main(String[] args) {
String name1 = new String("홍길동");
String name2 = new String("홍길동");
System.out.println(name1 == name2);
}
}
실행 결과
false
같은 “홍길동” 문자열 값을 통해 객체를 생성하였지만 name1 == name2 의 결과값은 false입니다. 즉 참조하고 있는 메모리 주소가 다른 것이죠. 이렇게 문자열인 경우는 ==로 비교하면 안되고 equals 라는 메서드를 사용해야 합니다.
public class ReferenceType3 {
public static void main(String[] args) {
String name1 = new String("홍길동");
String name2 = new String("홍길동");
System.out.println(name1.equals(name2));
}
}
실행 결과
true
== 연산자 말고, equals() 메서드를 이용해서 비교하면 문자열값 자체를 비교할 수 있습니다.
이제 배열 객체로 비교해 보겠습니다. 배열도 객체이고, 참조자료형이라는 점을 다시 한번 기억하시고, 코드를 작성하세요.
public class ReferenceType4 {
public static void main(String[] args) {
// 배열 변수 생성
int[] arr1 = {1,2,3};
int[] arr2 = {1,2,3};
System.out.println("arr1 == arr2 : " + (arr1 == arr2));
int[] arr3 = arr1;
System.out.println("arr1 == arr3 : " + (arr1 == arr3));
arr3[0] = 4;
System.out.println("arr3[0] : "+arr3[0]);
System.out.println("arr1[0] : "+arr1[0]);
}
}
실행 결과
arr1 == arr2 : false
arr1 == arr3 : true
arr3[0] : 4
arr1[0] : 4
변수 arr1, arr2는 모두 1,2,3 세 개의 정수 값을 담고 있는 정수타입의 배열입니다. 값은 같지만 == 로 비교하면 false 가 출력됩니다. 주소값이 다르기 때문이죠. 이번엔 arr3 변수에 arr1을 대입하고 arr1 == arr3으로 비교해 보면 true로 출력이 됩니다. 주소값이 같기 때문이죠. 이제 새로운 배열로 생성한 arr3의 0번 인덱스에 4의 값으로 대입을 하고 출력했는데, arr1변수의 0번 인덱스 값도 같이 4로 바뀐것을 알 수 있습니다. arr1과 arr3은 모두 같은 주소값을 가리키고 있기 때문에 변수하나로 값을 바꾸면 다른 변수의 값도 같이 바뀌게 됩니다. 정확히 말하면 같이 바뀌는게 아니라, 그 값을 참조하고 있는 모든 참조자료형 변수들은 바뀐 값을 참조하게 되는 것입니다. 조금 어려운 개념일 수 있지만, 앞으로 배울 모든 클래스들을 이용해 객체를 만들면 참조자료형이 되기 때문에 반드시 이해하고 넘어가도록 하세요.
그럼 이 배열 객체의 값을 그대로 이어받으면서 별개의 주소값을 갖는 참조자료형으로 만드려면 어떻게 해야 할까?
import java.util.Arrays;
public class ReferenceType5 {
public static void main(String[] args) {
// 배열 변수 생성
int[] arr1 = {1,2,3};
int[] arr2 = Arrays.copyOf(arr1, 3);
arr2[0] = 4;
System.out.println("arr1[0] : "+arr1[0]);
System.out.println("arr2[0] : "+arr2[0]);
}
}
실행 결과
arr1[0] : 1
arr2[0] : 4
Arrays.copyOf() 메서드를 사용하고 있는데, arr2 변수에 arr1 변수를 카피(복사)해서 대입한 것입니다. arr2[0]에 4를 대입하였지만, 실행 결과를 보면 이전 예제코드와는 다르게 arr1[0]은 그대로 1로 남아 있습니다.
그럼 위 코드를 약간 수정해서 배열 객체를 복사하고 나서 각각 for문으로 출력해 보면 모두 1,2,3으로 출력이 될 것입니다. 값은 같지만, 다른 주소에 담긴 객체라는 것입니다.
import java.util.Arrays;
public class ReferenceType5 {
public static void main(String[] args) {
// 배열 변수 생성
int[] arr1 = {1,2,3};
int[] arr2 = Arrays.copyOf(arr1, 3);
for (int a : arr1) {
System.out.println(a);
}
for (int a : arr2) {
System.out.println(a);
}
}
}
실행 결과
1
2
3
1
2
3