같은 자료형의 변수를 하나의 묶음으로 다룰 때 배열을 이용한다. 배열은 대괄호[ ]를 사용 한다.
매소드처럼 호출하면 자동으로 메모리 공간이 생성되고 매소드 종료 시 자동 소멸되는 것이 아니고 데이터를 저장할 길이(개수)만큼 사용자가 직접 할당을 해줘야 한다. 컴퓨터는 값이 얼마나 입력될지 모르기 때문에 배열의 길이를 스스로 정할 수 없다.
이 때 배열의 길이를 인덱스라고 하고 인덱스 번호는 0부터 시작한다.
위에서 말했던 배열은 선언 후, 꼭 할당을 해주어야한다.
배열은 자료형[ ] 배열명;
또는 자료형 배열명[ ];
으로 선언할 수 있다.
’할당한다는 것‘은 쉽게 말해 배열의 공간을 확보한다는 뜻이다. 자료형[ ] 배열명 = new 자료형[지정할 배열 크기];
으로 할당할 수 있다.
int[] arr=new int[5]; //선언과 동시에 할당
//int 자료형 5개를 넣을 수 있다.
String strArr[];//배열 선언
strArr=new String[3];//배열 할당 ->문자열 3개를 저장할 수 있다.
배열은 기본으로 생성했을 때 각 저장소에 자료형의 기본값으로 초기화한다. 그 외에 아래 같은 방법으로도 배열을 초기화 시킬 수 있다.
인덱스 이용
arr[0]=1;
strArr[1]=" ";
초기화 할 변수가 저장된 인덱스 번호를 입력해서 초기화할 수 있다.
for문 이용
for(int i=0; i<arr.length; i++){
arr[1]=1;
}
배열은 인덱스가 순차적으로 증가하고 인덱스 명이 반복되기 때문에 초기화할 리터럴이 규칙적이라면 for문을 활용해서 초기화할 수 있다.
💡 배열은 데이터의 길이를 클래스 자체에 이미 정해놨기 때문에 .length
기능을 사용해서 인덱스의 길이를 반환할 수 있다.
선언과 동시에
String[] names={"KIM","LEE","PARK"};
💡 배열 선언 시 대입할 값이 정해져 있다면 배열 선언과 동시에 값을 초기화할 수 있다. 단, 초기화를 먼저 하고 다른 값을 대입할 수 없다. 배열은 힙 영역에 값이 저장되어있고 스텍 영역에는 힙 영역의 주소가 저장되어 있는데 그 주소를 변경할 수 없기 때문이다.
new 생성자를 이용해서 다시 초기화 해줄 수 있지만 그 전에 선언, 할당했던 배열에 덮어씌워지는 것과 같기 때문에 이전 배열을 불러올 수 없다.
names=new {”LEE”,”KANG”,”JO”};
(이전에 입력했던 값은 개발자가 지워줘야 하지만(힙 영역이라서) 자바에는 GC가 알아서 지워준다.)
배열은 한 번 선언된 길이는 변경되지 않는다. 이미 길이를 할당한 배열에 값을 추가로 넣으려고 입력해도 자동으로 길이가 증가하지 않는다. index 길이를 초과해서 오류가 발생하기 때문이다.
그래서 배열을 선언할 때는 예상되는 범위의 최대값을 할당해주는 것이 좋다.
그렇다면 배열은 한 번 길이를 할당하고 나면 추가로 값을 입력하고 싶을 때마다 길이를 늘린 배열을 만들고 그 값을 옮겨야하는 수고가 생길 수 있을 것이다. 이런 고생을 덜 하고자 배열을 복사하는 기능이 존재한다.
배열의 복사는 저장소의 구분에 따라 얕은 복사와 깊은 복사로 나눌 수 있다.
배열에 지정된 힙 영역 주소를 복사해서 참조형 변수에 저장하고 원본 값을 공유하는 방식이다. 원본 값 자체를 수정하려고 할 때 사용할 수 있다.
int[] num= {1,2,3};
int[] copyNum=num;
//스택 영역에 똑같은 힙 주소를 가져오는 변수가 하나 선언된 것
System.out.println(num); //[I@6d03e736 ->그냥 주소값이 출력된다
System.out.println(Arrays.toString(num)); //[1, 2, 3] 출력
System.out.println(Arrays.toString(copyNum)); //[1, 2, 3] 출력
num[0]=100;
System.out.println("num[0] : "+num[0]); //num[0] : 100 출력
System.out.println("copyNum[0] : "+copyNum[0]); //copyNum[0] : 100 출력
💡 Arryas.toString(배열)
은 배열을 String형으로 출력하는 Arrays 클래스의 기능이다.
import java.util.Arrays;
위에 기능을 사용하기 위해 선언해준다.
✅ 주소를 공유했을 뿐이라서 얕은 복사를 한 배열의 저장 공간은 결국 하나인셈이다. 때문에 원본 배열에서 값을 변경하면 복사된 값을 출력해도 똑같이 변경된 값이 출력된다.
실제로 이런 구성은 아니겠지만 이해하면 위 그림과 같다.
깊은 복사는 새로운 배열 객체를 생성해서 기존 배열의 데이터를 복사하는 것이다. 인덱스의 길이와 값이 복사된 새로운 저장소가 생기는 것이다. 때문에 원본과 복사본의 구분이 필요한 경우에 사용한다.
깊은 복사는 반복문을 사용해서 값을 하나씩 복사할 수도 있지만 copyOf()
, clone()
,System.arraycopy()
기능을 이용해서도 사용 가능하다.
Arrays.copyOf(복사할 배열, 복사할 배열의 길이);
같은 방법으로 사용할 수 있다. 복사할 배열의 길이를 2만 입력하면 맨 앞에 값부터 두 개만 복사된다.int[] num={1,2,3};
int[] deepCopy=Arrays.copyOf(num,num.length);
System.out.println("copy2"+Arrays.toString(copy2));
deepCopy[1]=100;
System.out.println("원본 : "+Arrays.toString(num)); //원본 : [1,2,3] 출력
System.out.println("사본 : "+Arrays.toString(deepCopy)); //사본 : [1,100,3] 출력
int[] num={5,6,7};
int[] copy=new int[num.length];
copy=num.clone();
System.out.println(Arrays.toString(copy)); //[5,6,7] 출력
System.arraycopy(원본배열, 원본의 시작 인덱스 번호, 복사될 배열, 복사의 시작 인덱스 번호, 복사할 인덱스 길이);
방식으로 선언할 수 있다. 인덱스의 길이에 주의해서 사용해야한다. 복사할 배열에 원하는 인덱스 위치부터 값을 넣을 때 유용하다.String[] names= {"KIM","LEE","PARK"};
String[] extend=new String[names.length+5]; //길이 8
System.arraycopy(names, 0, extend, 0, names.length);
for (int i=0;i<extend.length;i++) {
System.out.print(extend[i]+" ");
//KIM LEE PARK null null null null null
}
System.out.println();
System.arraycopy(names, 0, extend, 3, names.length);
for (int i=0;i<extend.length;i++) {
System.out.print(extend[i]+" ");
//null null null KIM LEE PARK null null
}
자료형이 같은 1차원 배열의 묶음이다. 2차원 배열은 할당된 공간마다 인덱스 번호가 행,열로 두 개가 부여된다.
2차원 배열명[행 길이][열 길이] 같은 형태로 나타낸다.
바둑판 같은 구조를 생각하면 이해가 쉽다.
2차원 배열 선언은 총 3가지 방법이 있다. 자료형[][] 배열명;
, 자료형 배열명[][];
, 자료형[] 배열명[]
; 으로 표현 가능하고 보통 첫 번째 방법을 자주 쓴다.
배열이 선언됐을 때 대괄호[ ]가 두 개가 있다면 2차원 배열이라고 생각하면 된다.
배열 할당은 배열 선언=new 자료형[행크기][열크기];
로 할 수 있다. 대입 연산자 앞에 배열 선언은 3가지 방법 아무거나 상관없다.
int[][] intArr;
intArr=new int[3][3];
System.out.println(intArr); //[[I@6d03e736 -> 주소에 대괄호가 두 개
System.out.println(intArr[0]); //[I@568db2f2 ->첫번재 행 주소를 불러온다
System.out.println(intArr[1]); //[I@378bf509 ->두번째 행 주소를 불러온다.
System.out.println(intArr[0][1]);
//0 출력. 첫번째 열 배열에서 두 번째에 저장되어있는 변수 리터럴을 반환
행 배열과 열 배열의 할당을 따로
String[][] names=new String[3][];
//3길이를 갖는 행 배열. 열이 해당되는 배열은 비어있어서 행 배열이 null 을 반환한다.
names[0]=new String[5];
//null인 행의 배열 첫번째 공간에 5길이를 갖는 열 배열을 넣는다.
names[0][0]="KIM";
null이 들어있던 names[0][]에 인덱스 길이가 5인 열 배열의 주소가 들어가고 names[0][0]에는 KIM이라는 값이 들어간다.
인덱스를 이용
초기화 할 데이터 자리의 인덱스 번호를 입력해서 하나씩 초기화 할 수 있다.
intArr[0][0]=1;
intArr[0][1]=2;
intArr[0][2]=3;
for문을 이용
int[][] intArr;
intArr=new int[3][3];
for (int i=0;i<intArr.length;i++) { //행의 배열 길이만큼 실행
for (int j=0;j<intArr[i].length;j++) { //열의 배열 길이만큼 실행
System.out.print(intArr[i][j]); //기본 초기화 값인 0이 출력
}
선언과 동시에
int[][] arr={{1,2,3}{1,2,3,4}};//첫번째 대괄호가 행, 두번째가 열
String[][] drink={{"아메리카노","라떼","카페모카"}{"말차라떼","초코라떼","레몬에이드"};
배열 데이터에 순회(하나씩 접근)할 때 편리하게 사용할 수 있는 기능이다. for문과 같은 기능을 하도록 java에서 만들어진 기능이다.
for(자료형 변수명 : 배열명 또는 CollectionFramework) { 실행할 문장; }
으로 선언할 수 있다.
int[] intArr= {1,2,3,4,5,6};
for(int e : intArr) {
if(e%2==0) {
System.out.print(e+" ");
}
}
System.out.println();
💡 forEach문은 배열에 있는 값을 수정할 때는 사용할 수 없다.
String[] hobby= {"영화감상","음악감상","게임","코딩"};
for(String h : hobby) {
System.out.println(h);
if (h.equals("코딩")) h="운동"; //값이 바뀌지 않음.
//for문 안에서 선언된 지역변수 h를 수정했기 때문에 hobby자체가 수정되지 않는다.
}
배열 활용해서 로또 당첨 번호 만들기
1~45 사이의 값을 중복 없이 6개 받아내는 기능을 만든다.
int[] rotto=new int[6];
for (int i=0;i<rotto.length;i++) {
rotto[i]=(int)(Math.random()*45)+1;
for(int j=0;j<i;j++) {
if(rotto[i]==rotto[j]) {
i--;
break;
}
}
}
for(int r:rotto) {
System.out.print(r+" ");
}
✅ Math.random()*45
는 0~44 사이의 랜덤값을 반환하기 때문에 +1을 해준다. 그리고 Math.random() 기능은 double 자료형으로 값이 반환됨으로 int형으로 강제 형변환을 해서 배열의 자료형과 맞춰준다.
객체는 하나의 자료형으로 사용 가능하기 때문에 객체를 저장하는 배열을 만들 수 있다. 객체를 따로 따로 생성한 경우에는 객체를 추가로 만들었을 때 로직을 계속 추가해줘야 한다거나 오류가 생기면 로직 전체를 수정해줘야 하기 때문에 프로그램의 유지 보수를 더 편하게 하기 위해서 사용한다.
클래스명[] 배열명 = new 클래스명[인덱스 길이]; 형태로 선언한다.
객체 배열도 다른 배열과 마찬가지로 선언을 하고 길이를 할당해줘야 한다. 선언, 할당하는 방법은 다른 배열과 동일하고 선언과 동시에 할당 및 초기화를 하는 경우에는 클래스명[] 배열명 = {new 클래스명(), new 클래스명()}; 형태로 쓸 수 있다.
Person[] person=new Person[5];
Scanner sc=new Scanner(System.in);
for(int i=0;i<person.length;i++) {
person[i]=new Person();
System.out.print(i+1+"이름 : ");
person[i].setName(sc.next());
System.out.print(i+1+"나이 : ");
person[i].setAge(sc.nextInt());
sc.nextLine();
System.out.print(i+1+"주소 : ");
person[i].setAddress(sc.nextLine());
System.out.print(i+1+"전화번호 : ");
person[i].setPNumber(sc.nextLine());
}
for(int i=0;i<person.length;i++) {
System.out.println(person[i].getName()+" "+person[i].getAge()
+" "+person[i].getAddress()+" "+person[i].getPNumber());
}
✅ 객체 배열 역시 배열 명은 일치하고 인덱스는 일정 간격으로 증가하기 때문에 순차적으로 값을 입력 받으려고 할 때 반복문을 활용할 수 있다.
💡 객체 배열은 배열을 선언 하고 길이를 할당하고 나면 해당 인덱스에 객체를 생성해서 넣어주어야 한다. 객체는 생성되기 전에는 참조형의 기본 값인 null을 가지고 있기 때문에 접근 연산자( . )를 사용했을 때 오류가 발생한다.
⚠️NullPointerException →접근 연산자 앞에 주소에 null이 들어가 있는 경우 뜨는 에러코드
for(Person p:person) {
System.out.println(p.getName()+" "+p.getAge()
+" "+p.getAddress()+" "+p.getPNumber());
}
💡 객체 배열에서 forEach문을 사용할 수 있다. 단, 지역 변수로 선언한 것과 다르게 객체 배열에서 사용한 forEach문은 선언한 변수 명이 객체의 주소를 의미한다는 점이다. 때문에 p.setName("LEE");
를 중괄호 안에 넣으면 name의 데이터를 수정할 수 있다.