배열은 쉽게 생성하고 사용할 수 있지만, 저장할 수 있는 객체 수가 배열을 생성할 때 결정되기 때문에 불특정 다수의 객체를 저장하기에는 문제가 있다. 물론 배열의 길이를 크게 생성하면 되지만, 이는 좋은 방법이 아니다.
자바는 배열의 이러한 문제점을 해결하고, 자료구조를 바탕으로 객체들을 효율적으로 추가·삭제·검색할 수 있도록
java.util
패키지에 컬렉션과 관련된 인터페이스와 클래스들을 포함시켜 놓았는데, 이를 컬렉션 프레임워크(Collection Framework)라고 한다.
자바에서 컬렉션은 객체를 수집해서 저장하는 역할을 한다.
프레임워크란 사용 방법을 미리 정해 놓은 라이브러리를 말한다.
컬렉션 프레임워크의 주요 인터페이스는 List, Set, Map 이 있다.
아래 그림은 인터페이스별로 사용 가능한 컬렉션 클래스를 보여준다.
List와 Set은 객체를 추가·삭제·검색하는 방법에 많은 공통점이 있기 때문에, 공통된 메소들만 모아 Collection 인터페이스로 정의하고 있다.
Map은 키와 값을 하나의 쌍으로 묶어서 관리하는 구조로 되어 있다.
List 컬렉션은 객체를 일렬로 늘어놓은 구조를 가지고 있다.
List<String> list = ...;
list.add("Gildong"); //맨 끝에 객체 추가
list.add(1, "Seunghee"); //지정된 인덱스(1)에 객체 추가
String str = list.get(1); //인덱스(1)로 객체 찾기
list.remove(0); //인덱스(0)로 객체 삭제
list.remove("Gildong") //객체 삭제
만약 전체 객체를 대상으로 하나씩 반복해서 저장된 객체를 얻고 싶다면 다음과 같이 for문을 사용할 수 있다.
List<String> list = ...;
/* 인덱스 번호가 필요한 경우 */
for(int i=0; i<list.size(); i+++) {
String str = list.get(i); //i 인덱스에 저장된 String 객체를 가져옴
}
/* 인덱스 번호가 필요 없는 경우 */
for(String str : list) { ... } //이 방법이 더 편리할 수 있음
ArrayList는 List 인터페이스의 구현 클래스로, 객체를 추가하면 객체가 인덱스로 관리된다.
✅ 형태
List<타입> list = new ArrayList<타입>(용량(생략가능));
// ex) String 객체 30개를 저장할 수 있는 용량을 갖는 list 생성
// List<String> list = new ArrayList<String>(30);
🚩 따라서 빈번한 객체 삽입과 삭제가 일어나는 곳에서는 ArrayList를 사용하는 것은 바람직하지 않다 !
🚩 ArrayList는 인덱스 검색이나 맨 마지막에 객체를 추가하는 경우에 사용하는 것이 좋다 !
public class ArrayListExample {
public static void main(String... args) {
List<String> list = new ArrayList<String>();
/* 객체 추가 */
list.add("java");
list.add("spring");
list.add("jdbc");
list.add(2, "data");
list.add("js");
/* 저장된 총 객체 수 얻기 */
int size = list.size();
System.out.println("총 객체 수: " + size);
System.out.println();
/* 2번 인덱스의 객체 얻기 */
String skill = list.get(2);
System.out.println("2: " + skill);
System.out.println();
/* 저장된 총 객체 출력 */
for(int i=0; i<list.size(); i++) {
String str = list.get(i);
System.out.println(i + ":" + str);
}
System.out.println();
/* 객체 삭제*/
list.remove(2);
list.remove(2);
list.remove("data");
/* 저장된 총 객체 출력 */
for(int i=0; i<list.size(); i++) {
String str = list.get(i);
System.out.println(i + ":" + str);
}
System.out.println();
}
}
🚩 ArrayList를 생성하고 런타임 시 필요에 의해 객체들을 추가하는 것이 일반적이지만, 고정된 객체들로 구성된 List를 생성할 때도 있다.
Arrays.asList(T... a)
메소드를 사용하는 것이 간편하다.✅ 형태
List<타입> list = Arrays.asList(매개값1, 매개값2, ...);
public class ArraysAsListExample {
public static void main(String... args) {
List<String> list1 = Arrays.asList("a", "b", "c");
for(String alpha : list1) {
System.out.println(alpha);
}
List<Integer> list2 = Arrays.asList(1, 2, 3);
for(int number : list2) {
System.out.println(number);
}
}
}
Vector는 ArrayList와 동일한 내부 구조를 가지고 있으나, Vector는 동기화된 메소드로 구성되어 있다.
- 따라서 멀티 스레드가 동시에 메소드들을 실행할 수 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다.
이것을 스레드가 안전(Thread Safe)하다라고 말한다.
[ VectorExample.java] - Board 객체를 저장하는 Vector
public class VectorExample {
public static void main(String... args) {
List<Board> list = new Vector<Board>();
list.add(new Board("title1", "content1", "writer1"));
list.add(new Board("title2", "content2", "writer2"));
list.add(new Board("title3", "content3", "writer3"));
list.add(new Board("title4", "content4", "writer4"));
list.add(new Board("title5", "content5", "writer5"));
list.remove(2);
list.remove(3);
for(int i=0; i<list.size(); i++){
Board board = list.get(i);
System.out.println(board.subject + "\t" + board.content + "\t" + board.writer);
}
}
}
public class Board {
String subject;
String content;
String writer;
public Board(String subject, String content, String writer) {
this.subject = subject;
this.content = content;
this.writer = writer;
}
}
LinkedList는 List 구현 클래스이므로 ArrayList와 사용 방법은 똑같지만, 내부 구조는 완전히 다르다.
- ArrayList는 내부 배열에 객체를 저장해서 인덱스로 관리하지만,
- LinkedList는 인접 참조를 링크해서 체인처럼 관리한다.
✅ 형태
LinkedList<타입> list = new LinkedList<타입>();
실제로 객체를 삽입하는 데 걸린 시간을 측정하면 ArrayList보다 LinkedList가 빠른 것을 알 수 있다.
public class LinkedListExample {
public static void main(String... args) {
List<String> list1 = new ArrayList<>();
List<String> list2 = new LinkedList<>();
long startTime;
long endTime;
/* ArrayList */
startTime = System.nanoTime();
for(int i=0; i<10000; i++) {
list1.add(0, String.valueOf(i));
}
endTime = System.nanoTime();
System.out.println("ArrayList 걸린시간: " + (endTime-startTime) + "ns");
/* LinkedList */
startTime = System.nanoTime();
for(int i=0; i<10000; i++) {
list2.add(0, String.valueOf(i));
}
endTime = System.nanoTime();
System.out.println("LinkedList 걸린시간: " + (endTime-startTime) + "ns");
}
}
🚩 끝에서부터 순차적으로 추가/삭제하는 경우 → ArrayList 가 빠르고
🚩 중간에 추가/삭제하는 경우 → LinkedList가 더 빠르다.