『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처
한 변수에 여러 개의 값을 넣을 수 있는 것이 바로 배열이다. 이 배열은 가장 일반적인 자료 구조 중 하나이다.
자료 구조는 데이터를 저장하기 위한 구조를 이야기한다. 22장 Collection에서 자세히 나오는데, 아래와 같은 것들이 있다.
int[] array; // 권장
int array[];
위처럼 선언한 배열은 아직 몇 개의 데이터가 들어가는지 알 수가 없다. 따라서, 다음과 같이 초기화를 해 주어야만 한다.
// 1. 선언과 초기화를 같이 한다.
int[] array = new int[(int)크기];
// 2. 선언과 초기화를 따로 한다.
int[] array;
array = new int[(int)크기];
int 자체는 기본 자료형이지만, int[]는 int를 배열로 만든 참조 자료형이다. 따라서 new 예약어를 사용해 생성한다. 참고로, 꼭 new를 사용하여 정의하는 방법만 있는 건 아니다.
중괄호를 사용하면 한 번에 선언과 값 초기화를 할 수 있다. 이 중괄호 뒤에는 세미콜론을 필수로 붙인다.
// 선언과 초기화를 같이 한다.
int[] array = {1, 2, 3, 4, 5, 6, 7};
// 선언과 초기화를 따로 한다.
int[] array;
array = {1, 2, 3, 4, 5, 6, 7};
// 혹은 이렇게 사용하기도 한다.
int[] array = new int[]{1, 2, 3, 4, 5, 6, 7};
💡 배열 선언의 위치와 static
얼마나 자주 사용하는지, 어디에서 사용하는지를 확인하여 메소드에서 선언하여 사용할지, 클래스의 인스턴스 변수로 선언하여 사용할지 결정하면 된다.
배열을 인스턴스 변수로 선언 시 클래스의 객체를 생성할 때마다 배열이 생성된다. 이러한 단점을 해결하기 위해서 존재하는 자바의 예약어가 static 이다.
static 이라고 선언하면 “클래스 변수”가 되기 때문에, 클래스의 객체를 생성할 때마다 배열을 새로 생성하지 않는다.
하지만 static은 잘못하다가는 심각한 문제를 야기시킬 수도 있으니 절대 남용하지 말자.
참조 자료형은 아래와 같이 초기화할 수 있다.
String[] strings = new String[2];
MadeClassName classes = new MadeClassName[2];
strings[0] = "String은 쌍따옴표만으로 정의가 가능하다";
strings[1] = new String();
classes = new MadeClassName();
변수명 뒤에 대괄호를 열고 그 대괄호 안에 데이터를 넣고자 하는 위치를 지정한다. 이 위치를 인덱스(index)라고 한다.
인덱스는 0부터 시작하므로, 크기가 5라면 0번부터 4번까지 있다는 것을 명심하자.
int[] array = new int[5];
array[0] = 1;
array[1] = 2;
array[2] = 3;
array[3] = 4;
array[4] = 5;
array[5] = 6; // Exception java.lang.ArrayIndexOutOfBoundsException
System.out.println(array[5]); // Exception java.lang.ArrayIndexOutOfBoundsException
ArrayIndexOutOfBoundsException 오류는 “배열의 위치를 벗어난 예외가 발생했다”라는 뜻이다. 이 오류는 배열에 값을 할당할 때 발생하지만, 값을 참조할 때에도 발생한다.
기본 자료형 배열의 기본값은 각 자료형의 기본값과 동일하다. int의 경우 , boolean의 경우 false, float의 경우 0.0이 들어간다.
따라서 지역 변수는 초기화를 하지 않으면 사용이 불가능하지만, 배열의 경우에는 크기만 정해주면 기본값이 들어가므로 문제가 발생하지 않는다.
public void methode() {
int[] array = new int[1];
}
String과 같은 참조 자료형은 초기화를 하지 않을 경우 null이다. 자바에서 null이라는 것은 무소유의 개념이라고 보면 된다. 아무런 값도 설정되어 있지 않고 초기화도 되어 있지 않은 상태가 바로 null이다.
그래서, 배열과 같은 참조 자료형을 선언할 때 명시적으로 무소유의 상태임을 나타내기 위해서 다음과 같이 선언할 수도 있다.
String[] strings = null;
참조 자료형은 public String toString()이라는 메소드를 만들어주지 않으면 “타입이름@고유번호” 순으로 내용이 출력된다.
public class Test {
// main 생략
...
public void printArrays {
Test[] array = new Test[2];
array[0] = new Test();
System.out.println(array[0]);
System.out.println(array[1]);
}
}
/* -- result -- */
Test@123456
null
그렇다면, 배열을 출력했을 경우엔 어떨까?
public class Test {
// main 생략
...
public void printArrays {
System.out.println(new String[0]);
System.out.println(new Test[0]);
}
}
/* -- result -- */
[Ljava.lang.String;@123456
[LTest;@123456
기본자료형의 배열을 그냥 출력했을 경우엔, 대괄호 뒤에 해당 타입을 대표하는 문자가 출력된다.
boolean | byte | char | double | float | int | long | short |
---|---|---|---|---|---|---|---|
Z | B | C | D | F | I | J | S |
boolean 은 byte와 앞글자가 달라서, long은 참조 자료형의 배열과 구분이 안가기 때문에 남는 알파벳을 선정했다.
2차원, 3차원, 4차원처럼 N차원 배열도 만들 수 있다. 이걸 다차원 배열이라고 한다. 선언 및 초기화는 1차원 배열과 비슷하다.
int[][] array; // 권장
int[] array[];
int array[][];
1차원 배열에선 array[0] 의 값은 int였다. 하지만 2차원 배열에선 array[0]의 값은 배열이다.
// 2차원 배열 array[][]에서,
array[0] = int 배열
array[0][0] = int 값
1차원과 2차원의 크기 모두를 선언한다. 1차원의 크기만 지정하고, 2차원의 크기를 지정하지 않을 수도 있다.
하지만 1차원과 2차원의 크기를 모두 지정하지 않거나, 2차원의 크기만 지정하는 것은 불가능하다.
int[][] array = new int[2][3];
int[][] array = new int[2][];
int[][] array = new int[][]; // error
int[][] array = new int[][2]; // error
처음 선언할 때 2차원의 크기를 지정하면 모든 2차원의 크기가 똑같이 고정되는데, 다음과 같이 사용하면 2차원 배열의 크기를 서로 다르게 지정할 수 있다.
int[][] array = new int[2][];
array[0] = new int[3]; // 크기가 3인 2차원 배열
array[1] = new int[2]; // 크기가 2인 2차원 배열
중괄호를 이용한 배열 선언을 2차원 배열에서도 사용할 수 있다.
int[][] array = {{1, 2, 3}, {1, 2, 3}};
혹은 아래와 같이 사용할 수도 있다.
int[][] array = int[2][3];
array[0][0] = 1;
array[0][1] = 2;
array[0][2] = 3;
array[1][0] = 4;
array[1][1] = 5;
array[1][2] = 6;
2차원 배열은 출력할 for문을 사용하는 것이 편리하다. 그런데 for 문을 사용하려면 조건문에 들어갈(루프를 끝낼) 조건으로 배열의 길이가 필요하다.
자바에서는 배열 이름에 .length를 붙이면 배열의 길이를 알 수 있다.
int[] array1 = new int[3];
int[][] array = new int[4][2];
System.out.println(array1.length);
System.out.println(array2.length);
/* -- result -- */
3
4
array[0].length;
array[1].length;
💡 2차원 배열에서 array[0][1]은 값이 들어있는 공간이므로, length 를 사용할 수 없다.
💡 기본 자료형에는 .length와 같이 점을 찍어서 기능을 호출하거나 계산을 할 수 없다. 배열과 같이 참조 자료형에서만 점(.)을 찍을 수 있다.
for문으로 배열을 출력할 수 있는 방법이 몇 가지 있다.
length를 사용하면 배열 크기가 가변적이라고 할지라도 정확하게 데이터를 출력해줄 수 있다.
for(int one=0; one<array.length; one++) {
for(int two=0; two<array[one].length; two++) {
System.out.println(array[one][two];
}
}
하지만, 이렇게 length를 사용하면 for 루프가 수행될 때마다 길이를 얻어와서 성능적 측면에서는 별로 좋지 않다.
다음과 같이 별도의 크기를 알아내는 변수를 할당하여 사용하는 것이 가장 효과적이다.
int oneLength = array.length;
for(int one=0; one<oneLength; one++) {
int twoLength = array[one].length;
for(int two=0; two<twoLength; two++) {
System.out.println(array[one][two];
}
}
결과는 같지만 배열의 값을 for 루프의 조건에 직접 입력하는 하드 코딩은 반드시 피해야만 한다.
반드시 배열만이 아니라 자바에서 제공되는 Collection이라는 자료 구조를 처리할 때를 위해 for 루프를 보다 쉽게 사용할 JDK 5부터 개선된 부분이 있다.
💡 Collection과 자료 구조라는 것은 22장에서 자세히 다룬다.
💡 JDK는 Java Development Kit의 약자이며, 그 뒤에 붙은 숫자는 자바의 버전을 의미한다. 자세한 자바의 버전에 대한 설명은 19장 JVM을 참고한다.
JDK 5에서 for 루프를 편하게 사용할 수 있도록, 다음과 같은 문법이 추가되었다.
for(타입이름 임시변수명 : 반복대상객체) {
}
응용 예제
// 1차원 배열
int[] onedim = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int data : onedim) {
System.out.println(data);
}
// 2차원 배열
int[][] twodim = new int[] {{1, 2, 3}, {4, 5, 6}};
for (int[] dimArray : twodim) {
for (int data : dimArray) {
System.out.println(data);
}
}
1차원 배열과 2차원 배열의 위치를 모른다는 단점이 있다. 그래서 그 위치를 확인하려면 임시 변수를 두고, for 문 안에서 각각 ++(증가) 연산자로 계산해서 알아내야 한다.
지금까지 우리가 사용하던 main() 메소드에는 배열이 있다. 이 배열에는 값을 어떻게 전달할까?
main() 메소드 안에 for문으로 받아온 파라미터를 출력하는 코드를 짜면 확인해볼 수 있다.
public static void main(String[] args) {
if (args.length > 0) {
for(String arg:args) {
System.out.println(arg);
}
}
}
컴파일한 후, java 명령어로 실행할 때 클래스 이름 뒤에 공백으로 분리한 문자열을 나열하면 이 문자열들이 args라는 배열에 전달된다.
물론, 아무것도 적지 않아도 실행된다. 이 경우에는 args의 length가 0이므로 아무것도 출력되지 않는다.
java 클래스 a b c d
/* -- result -- */
a
b
c
d
Me: []
Me: 0
Me: false
Me: 배열의 크기를 벗어나는 값을 참조했을 때
Me: 2개
Me: for(변수타입 변수이름:배열이름)
Me: 공백으로 구분하여 나열한다.
Me: String 타입의 배열
💡 책에 있는 내용이 아닙니다.
책을 읽으며 설명이 더 필요하거나, 추가로 궁금한 점에 대해 질문 형식으로 작성 후, 답을 구해보고 있습니다.
참고한 사이트나 영상은 [출처]로 달아두었으며, 오류 지적은 언제나 환영합니다.
해시코드라고 한다. 간단히 설명하자면, 해시코드는 메모리의 주소를 16진수로 변환한 것이다.
이는 뒤에 equals()와 hashCode()를 배울 때 자세히 나올 것이다.
jshell로 돌려보니, 초기화는 가능하지만 안에 할당할 수는 없다. 당연하다, 길이가 0이니까.
인터넷에 검색해보니, 리스트를 배열로 변환해주는 toArray() 라는 함수를 쓸 때 길이가 0인 배열을 넘겨주면 리스트의 최소 길이를 가진 배열로 반환해준다고 되어 있다.
toArray()는 매개 변수로 넘긴 배열 객체의 크기가 리스트의 크기보다 작거나 같으면 리스트 크기만큼 담아주지만, 리스트의 크기보다 크면 남은 공간에 null이 들어간다.
그렇다고 리스트의 길이를 구해서 그와 같게 넣어주는 건 귀찮은 작업이기도 하고, 길이를 구해야 하는 작업이 추가로 들어간다.
따라서 크기를 0으로 지정해서 리스트의 길이만큼 반환되도록 사용하는 듯 하다.
💡 자바의 신에서는 2권에서 설명해준다.
int[][] array = new int[2][];
array[0] = {1, 2} // error
array[0] = new int[2] // init 0, 0
array[1] = new int {3, 4} // init 3, 4
1차원 배열일 때는 바로 중괄호로 초기화가 가능해서 테스트 해봤다. 아래 이미지를 보면 알겠지만, 2차원 배열일 때는 1차원이 null로 들어가 있다.
따라서 new int[]로 초기화를 해줘야 한다.