배열은 자료형이 같은 자료가 연속으로 나열된 자료구조이다. 배열은 자료형이 같은 자료들을 한번에 관리할 수 있어 편리하다.
배열을 이루고 있는 자료들을 배열의 요소라고 하는데, 배열의 요소들은 실제 메모리 상에서도 이웃해있다. 물리적 위치와 논리적 위치가 같다고 표현하며, 배열의 i번째 요소는 j번째 인덱스로부터 실제 메모리 상에서 |i - j| * 자료형의 크기(byte) 만큼 떨어져 있다는 의미다.
int 형 배열인 intArray 라는 이름의 배열이 있다고 한다면, intArray[1] 가 intArray[0]로부터 4byte 다음 메모리 주소에 위치하고 있는 것이다.
자바의 배열은 클래스로 구현되어 있으므로, 배열 객체를 생성하여 이용할 수 있다. 다른 클래스처럼 배열 객체 또한 배열 클래스 내에 있는 속성과 메서드를 활용할 수 있다.
배열은 하나의 클래스로, new 를 활용하여 배열 객체를 생성할 수 있고, 배열 변수에 배열 인스턴스의 주소값을 담을 수 있다. 만약 배열 객체를 생성하지 않고 변수만 선언 하게 되면, 변수는 null 값을 가지게 된다.
int[] intArray = new int[5];
// int[] 자료형으로 intArray 라는 배열 변수를 선언한다.
// new 예약어를 활용하여 공간이 5개인 int형 배열을 생성한다.
// intArray 변수에는 배열 객체가 생성된 힙 메모리의 주소가 저장된다.
int[] intArray;
/* 배열 객체를 생성하여 intArray 변수에 배열 객체의 주소값을 담기 전까지
intArray 변수는 null로 초기화 되어있다. */
배열 객체의 생성은 힙 메모리에서 이루어지고, 변수는 참조만 하기 때문에 하나의 배열 객체를 가리키는 변수가 다수일 수 있다.
간단히 말하면 다음과 같이 동일한 메모리 주소를 담고 있는 변수가 여러개일 수 있다는 것이다.
int[] intArray1 = new int[5];
int[] intArray2 = intArray1;
/* intArray2 = intArray1 에 의해
intArray2와 intArray1는 동일한 메모리 주소를 가리키고 있다. */
배열을 선언할 때는 초기값을 정해줄 수도 정하지 않을 수도 있다. 만약 초기값을 따로 지정해주지 않으면 배열의 자료형에 따라 배열 요소의 초기값이 설정된다. 자료형이 int 라면, 0 으로, double 이나 float 라면 0.0, 객체 배열이라면 null 로 초기화 된다.
int[] math = new int[] {78, 46, 98, 22, 100};
int[] english = {10, 20, 30, 40, 50};
new int[]의 대괄호에 자료형의 개수를 적으면 오류가 발생하므로 적지 말것.new int[]을 생략해도 되지만, 만약 선언과 초기값의 대입이 다른 라인에서 이루어진다면 new int[]을 생략할 수 없다.배열을 선언할 때 사용했던 []를 인덱스 연산자라고 한다. 배열은 []을 통해 배열의 요소에 접근할 수 있다.
math[i] 는 math 라는 배열의 i 번째 요소라는 뜻이다. 인덱스 연산자를 활용하여 i번째 값을 얻거나 바꿀수 있다.
배열의 요소가 객체인 배열로, 기본 자료형 배열과 사용법이 약간 다르다.
기본 자료형 배열 요소에는 그 자료형에 해당하는 실제 값이 담기지만 객체 배열에는 생성된 인스턴스의 주소가 담긴다.
Student 클래스package array;
public class Student {
private String id;
public Student() {}
public Student(String id) {
this.id = id;
}
public String getStudentID() {
return this.id;
}
public void setStudentID(String id) {
this.id = id;
}
}
Student[] cadet = new Student[5];
위와 같이 클래스 명과 []을 사용하여 객체 배열을 선언한다. new를 사용하여 선언하지만, 이때는 Stduent 인스턴스가 생성되는 게 아니라 인스턴스의 메모리 주소가 담길 공간이 []안의 개수 만큼 생성된다.
객체 배열의 요소는 모두 null로 초기화 되어으며, 인스턴스를 생성하여 그 메모리 주소를 배열의 요소에 담아주는 방식으로 사용한다.
cadet[0] = new Student("Kang");
cadet[1] = new Student("Choi");
cadet[2] = new Student("Kim");
cadet[3] = new Student("Park");
cadet[4] = new Student("Lee");
ArrayTest.javapackage array;
public class ArrayTest {
public static void main(String[] args) {
Student[] cadet = new Student[5];
cadet[0] = new Student("Kang");
cadet[1] = new Student("Choi");
cadet[2] = new Student("Kim");
cadet[3] = new Student("Park");
cadet[4] = new Student("Lee");
System.out.println(cadet);
for (int i = 0; i < cadet.length; i++) {
System.out.println(cadet[i] + ", " + cadet[i].getStudentID());
}
}
}
[Larray.Student;@41975e01
array.Student@1ee0005, Kang
array.Student@75a1cd57, Choi
array.Student@3d012ddd, Kim
array.Student@6f2b958e, Park
array.Student@1eb44e46, Lee
배열의 복사에는 두가지 방법이 있다.
첫번째로는 for 반복문을 사용하여 각 배열의 요소값을 복사하는 방법이 있다. 두번째는 System.arraycopy() 메서드를 활용하는 것이다.
System.arraycopy(src, srcPos, dest, destPos, length)
| 매개 변수 | 의미 |
|---|---|
src | 복사할 배열 이름 |
srcPos | 복사할 배열의 첫 번째 인덱스 위치 |
dest | 복사해서 붙여 넣을 대상 배열 이름 |
destPos | 복사해서 대상 배열에 붙여 넣기를 시작할 인덱스 위치 |
length | src에서 dest로 복사할 요소의 개수 |
System.arraycopy() 사용 예제package array;
public class ArrayTest {
public static void main(String[] args) {
int[] math = {78, 46, 98, 22, 100};
int[] english = {10, 20, 30, 40, 50};
System.arraycopy(math, 1, english, 2, 2);
/* `math` 배열의 `1`번째 요소부터 `2`개의 요소를 복사해
`english` 배열의 `2`번째 요소부터 차례로 넣어준다. */
for (int i = 0; i < math.length; i++) {
System.out.print(math[i] + " ");
}
System.out.println();
for (int i = 0; i < english.length; i++) {
System.out.print(english[i] + " ");
}
}
}
78 46 98 22 100
10 20 46 98 50
기본 자료형 배열과 마찬가지로 객체 배열 또한 System.arraycopy() 메서드로 배열 요소를 복사할 수 있다.
다만 객체 배열의 요소는 객체의 메모리 주소이므로, 기본 자료형 배열처럼 실제 자료값이 복사되는게 아니라 객체의 메모리 주소가 복사된다. 즉, src와 dest의 각 배열요소가 같은 객체를 참조하게 되는 것이다.
그렇기 때문에 dest 배열은 src 배열처럼 배열 요소마다 객체를 생성해 넣지 않아도 오류없이 복사가 가능하다.
System.arraycopy() 메서드로 복사해보기package array;
public class ArrayTest {
public static void main(String[] args) {
Student[] cadet = new Student[5];
Student[] student = new Student[5];
cadet[0] = new Student("Kang");
cadet[1] = new Student("Choi");
cadet[2] = new Student("Kim");
cadet[3] = new Student("Park");
cadet[4] = new Student("Lee");
for (int i = 0; i < cadet.length; i++) {
System.out.println(cadet[i] + ", " + cadet[i].getStudentID());
}
for (int i = 0; i < student.length; i++) {
System.out.println(student[i]);
}
System.out.println();
System.arraycopy(cadet, 0, student, 0, 5);
for (int i = 0; i < cadet.length; i++) {
System.out.println(cadet[i] + ", " + cadet[i].getStudentID());
System.out.println(student[i] + ", " + student[i].getStudentID());
}
}
}
array.Student@41975e01, Kang
array.Student@1ee0005, Choi
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
null
null
null
null
null
array.Student@41975e01, Kang
array.Student@41975e01, Kang
array.Student@1ee0005, Choi
array.Student@1ee0005, Choi
array.Student@75a1cd57, Kim
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
array.Student@6f2b958e, Lee
System.arraycopy() 메서드를 사용했을 때와 같이, 객체의 메모리 주소걊만 복사하는 것을 얕은 복사라고 한다.
얕은 복사를 할 경우, src와 dest가 모두 같은 객체를 참조하고 있기 때문에, 배열의 객체 값이 변경되면 두 배열 모두 영향을 받게 된다.
package array;
public class ArrayTest {
public static void main(String[] args) {
Student[] cadet = new Student[5];
Student[] student = new Student[5];
cadet[0] = new Student("Kang");
cadet[1] = new Student("Choi");
cadet[2] = new Student("Kim");
cadet[3] = new Student("Park");
cadet[4] = new Student("Lee");
System.arraycopy(cadet, 0, student, 0, 5);
for (int i = 0; i < cadet.length; i++) {
System.out.println(cadet[i] + ", " + cadet[i].getStudentID());
}
System.out.println();
for (int i = 0; i < cadet.length; i++) {
System.out.println(student[i] + ", " + student[i].getStudentID());
}
cadet[1].setStudentID("Woo Jiwon");
System.out.println();
for (int i = 0; i < cadet.length; i++) {
System.out.println(cadet[i] + ", " + cadet[i].getStudentID());
}
System.out.println();
for (int i = 0; i < cadet.length; i++) {
System.out.println(student[i] + ", " + student[i].getStudentID());
}
}
}
array.Student@41975e01, Kang
array.Student@1ee0005, Choi
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
array.Student@41975e01, Kang
array.Student@1ee0005, Choi
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
array.Student@41975e01, Kang
array.Student@1ee0005, Woo Jiwon
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
array.Student@41975e01, Kang
array.Student@1ee0005, Woo Jiwon
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
만약 src와 dest의 배열요소가 서로 영향을 미치는 것(정확히는 같은 객체를 참조하고 있기 때문에 발생하는 현상)을 막고 싶다면, 얕은 복사를 해서는 안된다.
인스턴스의 메모리 주소가 아니라 인스턴스의 속성값을 복사하고 싶다면, dest 의 배열 요소에도 객체를 새로 생성하고, for 반복문을 사용하여 그 객체의 속성값에 src 의 객체 속성값을 직접 대입해야한다. 이렇게 요소의 값만 같게 복사하는 것을 깊은 복사라고 한다.
package array;
public class ArrayTest {
public static void main(String[] args) {
Student[] cadet = new Student[5];
Student[] student = new Student[5];
cadet[0] = new Student("Kang");
cadet[1] = new Student("Choi");
cadet[2] = new Student("Kim");
cadet[3] = new Student("Park");
cadet[4] = new Student("Lee");
student[0] = new Student();
student[1] = new Student();
student[2] = new Student();
student[3] = new Student();
student[4] = new Student();
for (int i = 0; i < cadet.length; i++) {
System.out.println(cadet[i] + ", " + cadet[i].getStudentID());
}
System.out.println();
for (int i = 0; i < student.length; i++) {
System.out.println(student[i] + ", " + student[i].getStudentID());
}
for (int i = 0; i < student.length; i++) {
student[i].setStudentID(cadet[i].getStudentID());
}
System.out.println();
for (int i = 0; i < cadet.length; i++) {
System.out.println(student[i] + ", " + student[i].getStudentID());
}
System.out.println();
cadet[1].setStudentID("Woo Jiwon");
System.out.println(cadet[1] + ", " + cadet[1].getStudentID());
System.out.println(student[1] + ", " + student[1].getStudentID());
}
}
array.Student@41975e01, Kang
array.Student@1ee0005, Choi
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
array.Student@1eb44e46, null
array.Student@6504e3b2, null
array.Student@515f550a, null
array.Student@626b2d4a, null
array.Student@5e91993f, null
array.Student@1eb44e46, Kang
array.Student@6504e3b2, Choi
array.Student@515f550a, Kim
array.Student@626b2d4a, Park
array.Student@5e91993f, Lee
array.Student@1ee0005, Woo Jiwon
array.Student@6504e3b2, Choi
앞에서 다룬 기본 배열은 길이를 정하고 시작해야하는데, 이 길이는 변경할 수 없기 때문에 이후에 길이를 수정하고 싶어도 그럴 수 없다. 그리고 만약 배열의 요소를 제거하게 된다면, 그 부분은 앞으로 당겨지는 게 아니라 비워진 채로 존재하게 된다.
자바에서는 ArrayList 라는 객체 배열 클래스를 통해 이러한 단점을 극복할 수 있다.
ArrayList 클래스는 java.util 패키지에 구현되어 있는 클래스 이므로 ArrayList를 사용하기 위해서는 java.util 패키지를 import 해서 사용해야한다.
ArrayList의 기본 선언 형태import java.util.ArrayList;
ArrayList<E> 배열 이름 = new ArrayList<E>();
E는 자료형을 의미하고, <E>와 같은 형태를 제네릭 자료형이라고 한다.
ArrayList 와 일반 배열의 비교/* ArrayList */
ArrayList<Student> cadet = new ArrayList<Student>();
/* 일반 배열 */
Student[] cadet = new Student[5];
ArrayList은 배열의 길이 설정으로부터 비교적 자유롭기 때문에 선언할 때 길이를 선언해줄 필요가 없다.
| 메서드 | 설명 |
|---|---|
boolean add(E e) | 요소 하나를 배열에 추가하고 성공적으로 추가했다면 true, 아니라면 false 반환 |
int size() | 배열에 추가된 요소 전체 개수를 반환 |
E get(int index) | 배열의 index 위치에 있는 요소 값(객체 배열의 경우 인스턴스의 주소값)을 반환 |
E remove(int index) | 배열의 index 위치에 있는 요소 값(객체 배열의 경우 인스턴스의 주소값)을 제거하고 그 값을 반환 |
boolean isEmpty() | 배열이 비어 있는지 확인하고, 비어있다면 true, 비어있지 않다면 false 반환 |
E는 요소의 자료형을, e는 요소의 변수명을 의미한다.ArrayList 사용 예제package array;
import java.util.ArrayList;
public class ArrayTest {
public static void main(String[] args) {
ArrayList<Student> cadet = new ArrayList<Student>();
System.out.println(cadet.isEmpty() + ", " + cadet.size());
System.out.println();
System.out.println(cadet.add(new Student("Kang")));
System.out.println(cadet.add(new Student("Choi")));
System.out.println(cadet.add(new Student("Kim")));
System.out.println(cadet.add(new Student("Park")));
System.out.println(cadet.add(new Student("Lee")));
System.out.println();
System.out.println(cadet.isEmpty() + ", " + cadet.size());
System.out.println();
for (int i = 0; i < cadet.size(); i++) {
Student student = cadet.get(i);
System.out.println(student + ", " + student.getStudentID());
}
System.out.println();
System.out.println(cadet.remove(1));
System.out.println();
System.out.println(cadet.isEmpty() + ", " + cadet.size());
System.out.println();
for (Student student : cadet) {
System.out.println(student + ", " + student.getStudentID());
}
}
}
true, 0
true
true
true
true
true
false, 5
array.Student@7dc5e7b4, Kang
array.Student@1ee0005, Choi
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
array.Student@1ee0005
false, 4
array.Student@7dc5e7b4, Kang
array.Student@75a1cd57, Kim
array.Student@3d012ddd, Park
array.Student@6f2b958e, Lee
ArrayList는 일반 배열과 달리 인덱스 연산자(cadet[i])로 접근할 수 없다. 대신 get(i)을 사용하여 객체의 인스턴스에 접근 가능하다.for (Student student : cadet)는 Student 자료형 변수인 student에 ArrayList의 요소를 순서대로 넣는다는 의미이다.for 문아래의 for 반복문은 모두 같은 것을 출력해준다.
for (int i = 0; i < cadet.size(); i++) {
System.out.println(cadet.get(i) + ", " + cadet.get(i).getStudentID());
}
for (int i = 0; i < cadet.size(); i++) {
Student student = cadet.get(i);
System.out.println(student + ", " + student.getStudentID());
}
for (Student student : cadet) {
System.out.println(student + ", " + student.getStudentID());
}