배열은 자료형이 같은 자료가 연속으로 나열된 자료구조이다. 배열은 자료형이 같은 자료들을 한번에 관리할 수 있어 편리하다.
배열을 이루고 있는 자료들을 배열의 요소라고 하는데, 배열의 요소들은 실제 메모리 상에서도 이웃해있다. 물리적 위치와 논리적 위치가 같다고 표현하며, 배열의 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.java
package 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());
}