목차
- 컬렉션(collection)
- List 계열
2-1. ArrayList
2-2. Vector
2-3. LinkedList
2-4. stack
2-5. queue - Set 계열
3-1. HashSet 클래스
3-2. LinkedHashSet 클래스
3-3. TreeSet 클래스
1. 컬렉션(collection)
- java.util 패키지 하위에 속해 있다.
- 데이터를 효율적으로 저장하는 자료구조와 데이터를 처리하는 알고리즘이 미리 구현돼 있는 것이다.
- 여기서 자료구조란, 데이터를 메모리상에 효율적으로 저장하기 위한 방법론을 칭한다.
- 크게 단순구조(정수, 실수, 문자, 문자열 등 변수) / 선형구조(List 인터페이스 계열) / 비선형구조(Set 인터페이스 계열) / 파일구조로 나뉜다.
Collection.interface | Map.interface *Map은 Collection을 상속하지 X.
△
| 상속
List.interface | Set.interface
인터페이스 분류 | 취급 | 특징(순서) | 특징(중복) | |
Collection | List 계열 | value | 순서 유지하며 저장 | 중복 저장 O |
Set 계열 | value | 순서 없이 저장 | 중복 저장 X | |
Map 계열 | key, value | 쌍으로 저장 (key와 value) |
key는 중복 저장 X value는 중복 저장 O |
2. List 계열
저장순서O, 중복O
예. 웨이팅 리스트
❗ 구현클래스 - ArrayList, Vector, LinkedList, Stack
- List 인터페이스를 구현한 모든 클래스는 요소의 저장 순서가 유지된다.
- index[0]에 저장한 값과 index[n]에 저장한 값이 같아도 문제 없다. 즉 중복된 인스턴스 저장이 가능하다.
B. List 계열 주요 메소드
메소드 기능별로 인스턴스 추가/검색/삭제로 나눠볼 수 있다.
alist.add("apple");
alist.add(1, "banana");
- [추가]add(E element) : 주어진 인스턴스를 맨 끝에 추가한다. 다른 인덱스에 동일한 값을 저장하는 것이 가능하다.
- [추가]add(int index, E element) : 또, 원하는 인덱스 위치에 값을 추가할 수도 있다. 중간 값을 새롭게 추가한다면 그 이후 인덱스는 하나씩 뒤로 밀려나는 격이다.
alist.set(1, true);
- [추가]set(int index, E element) : 지정된 위치의 값을 수정할 때에 쓰인다.
- [검색]size() : 배열의 크기가 아닌 요소(element)의 개수를 반환한다. 내부적으로 관리되는 배열의 사이즈는 외부에서 알 필요가 없기 때문에 기능을 제공하지 않는다.
- [검색]get() : 인덱스에 해당하는 값을 가져올 때 for문 안에서 사용한다.
- [검색]iterator() : 저장된 인스턴스를 한 번씩 가져오는 반복자 리턴이다.
- [검색]isEmpty() : 컬렉션이 비어 있는지 묻는다.
- [삭제]clear() : 저장된 모든 인스턴스를 삭제한다.
- [삭제]remove(int index) : 지정된 값을 삭제한다. 중간 인덱스의 값을 삭제하는 경우 뒤로 남은 인덱스들이 하나씩 자동으로 앞당겨진다.
- toString() : 메소드가 오버라이딩 되어 있어 참조변수 이름만으로 값을 출력할 수 있다.
2-1. ArrayList
- ArrayList 컬렉션 클래스를 가장 많이 사용한다. JDK 1.2버전부터 표준화 과정을 적용해 제공되었다.
- 내부적으로 배열을 이용하여 요소(element)를 관리하고, 인덱스를 통해 인스턴스에 접근한다.
배열 단점 보완
- 배열은 크기를 변경할 수 없고, 요소의 추가, 삭제, 정렬 등이 복잡하다는 단점을 가지고 있다.
- ArrayList는 이러한 배열의 단점을 보완하기 위해 만들어졌다.
- ArrayList는 더 큰 배열을 새롭게 만들어 데이터 옮기는 등 크기 제약이 없어 크기 변경 가능하고, 요소의 추가, 삭제, 정렬 기능 등을 미리 메소드로 구현해 자동 수행되도록 제공하고 있다. 이 같은 기능들은 자동 수행되는 것일뿐 속도가 빨라지는 것은 아니다.
- 여러 타입의 데이터가 저장 가능하다. 기본자료형을 사용해야 하는 경우 Wrapper 클래스를 이용한다.
A. 객체 생성 시
다형성 적용해 상위 레퍼런스 List 사용
ArrayList alist = new ArrayList();
- ArrayList는 인스턴스를 생성하게 되면 내부적으로 10칸짜리 배열을 생성해서 관리한다.
alist.add("apple");
alist.add(123); *autoBoxing 처리됨 (값 → 객체)... int 값도 Wrapper 클래스 Integer로 저장됨
alist.add(45.67);
alist.add(new Date());
- ArrayList는 Object 클래스의 하위 타입 인스턴스를 모두 저장할 수 있다.
❗ 제네릭 선언 후 사용한다!
모든 컬렉션 프레임워크 클래스는 제네릭 클래스로 작성되어 있다.
<E> 타입이 명시되지 않은 경우 Object 타입으로 취급되기 때문에 모든 값에 다운캐스팅을 별도로 적용해야 하는 문제가 따른다: ((BookDTO)list).get(0);
단, 제네릭 타입을 지정하면 지정한 타입 외의 인스턴스는 저장하지 못한다.
List<E> list = new ArrayList<E>();
- 다형성을 적용하여 상위 레퍼런스인 List로 ArrayList 객체를 만든다.
- 그렇게 하면 List 인터페이스 하위의 다양한 구현체들로 타입 변경 역시 가능해지기 때문에, 레퍼런스 타입을 상위 레퍼런스인 List로 작성하는 것이 더 유연한 코드라 할 수 있다.
- 필요하다면 더 상위 타입인 Collection 타입을 사용할 수도 있다: Collection clist = new ArrayList();
B. Collections 클래스
오름차순 정렬
- Collection에서 사용되는 기능들을 static 메소드로 구현한 클래스이다.
- Collection 인터페이스가 아닌 Collections 클래스이다. 인터페이스명 뒤에 s가 붙은 클래스들은 관례상 비슷한 방식으로 작성된 클래스를 의미한다.
List<String> stringList = new ArrayList<>();
stringList.add("apple");
stringList.add("banana");
stringList.add("orange");
stringList.add("mango");
stringList.add("grape");
stringList : [apple, banana, orange, mango, grape]
Collections.sort(stringList);
stringList : [apple, banana, grape, mango, orange]
- sort() 메소드를 사용하면 list가 오름차순 정렬 처리된 후 정렬 상태가 유지된다.
- stringList 값 자체가 변경된 것이다.
C. Iterator
내림차순 정렬
- Collection 인터페이스의 iterator() 메소드를 이용해서 인스턴스를 생성할 수 있다.
- 반복자라고 불린다. 반복문을 이용해서 목록을 하나씩 꺼내는 방식으로 사용하는 데 쓰인다.
- 인덱스로 관리되는 컬렉션이 아닌 경우에는 반복문을 사용해서 요소에 하나씩 접근할 수 없기 때문에, 인덱스 없이도 반복문을 사용할 수 있도록 목록을 만들어주는 역할을 한다.
- hasNext() : 다음 요소를 가지고 있는 경우 true, 더 이상 요소가 없는 경우 false를 반환
- next() : 다음 요소를 반환
Iterator<String> dIter = ((LinkedList<String>)stringList).descendingIterator();
List<String> descList = new ArrayList<>();
while(dIter.hasNext()) {
descList.add(dIter.next());
}
System.out.println("descList : " + descList);
descList : [orange, mango, grape, banana, apple]
- 앞서 다뤘던 StringTokenizer와 비슷한 형태로 쓰인다. 값을 한 번 모두 꺼내고 나면 다시 쓰일 수 없다.
- 역순으로 정렬된 결과를 저장하기 위해서는 새로운 ArrayList를 만들어 저장한다.
public static void main(String[] args) {
/* 여러 권의 책 목록을 관리할 ArrayList 인스턴스 생성 */
List<BookDTO> bookList = new ArrayList<>();
/* 도서 정보 추가 */
bookList.add(new BookDTO(1, "홍길동전", "허균", 50000));
bookList.add(new BookDTO(2, "목민심서", "정약용", 30000));
bookList.add(new BookDTO(3, "동의보감", "허준", 40000));
bookList.add(new BookDTO(4, "삼국사기", "김부식", 46000));
bookList.add(new BookDTO(5, "삼국유사", "일연", 58000));
/* 정렬 전 책 리스트 출력 */
for(BookDTO book : bookList) {
System.out.println(book);
}
/* 여기서는 sort 사용이 안 된다. 우리가 만든 type의 경우는 바로 적용이 어렵다.
* 제네릭 타입 제한에 의해 Comparable 타입을 가지고 있는 경우에만 sort가 사용 가능하다. */
// Collections.sort(bookList);
/* 이때는 기준을 정해 정렬할 수 있다. */
/* 가격 순으로 오름차순 정렬 - AscendingPrice 추가 */
/* Comparator 인터페이스를 상속 받아 정렬 기준을 정해준 뒤
* List의 sort() 메소드의 인자로 정렬 기준이 되는 인스턴스를 넣어주게 되면
* 내부적으로 우리가 오버라이딩한 메소드가 동작하여 그것을 정렬 기준으로 삼는다.
* */
bookList.sort(new AscendingPrice());
System.out.println("가격 오름차순 정렬 ----------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
/* 인터페이스를 구현할 클래스를 재사용하는 경우 AscendingPrice 클래스처럼 작성하면 되지만
* 한 번만 사용하기 위해서는 조금 더 간편한 방법을 이용할 수 있다.
* 익명클래스(Anonymous)를 이용한 방법이다.
* */
/* 익명클래스는 뒤에 {}을 만들어서 마치 Comparator 인터페이스를 상속 받은 클래스인데
* 이름이 없다고 생각하고 사용하는 것이다.
* */
bookList.sort(new Comparator<BookDTO> () { //재사용 불가, 이름 없는 클래스
@Override
public int compare(BookDTO o1, BookDTO o2) {
/* 여기에 내림차순 정렬 조건을 넣어주면 된다.
* AscendingPrice와는 반대로 오름차순 정렬된 상태일 때 순서를 바꿔야 한다.
* 양수를 반환해서 순서를 바꾸라는 플래그로 이용했었다.
* */
return o2.getPrice() - o1.getPrice(); //결과에 대해 양수,음수,0이라는 리턴값이 생겨날 것이다.
}
});
System.out.println("가격 내림차순 정렬 ---------------------- ");
for(BookDTO book : bookList) {
System.out.println(book);
}
/* 제목순 오름차순 정렬 */
bookList.sort(new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
/* 문자열은 대소 비교를 할 수 없다.
* 문자 배열로 변경 후 인덱스 하나하나를 비교해서 어느 것이 더 큰 값인지 확인해야 하는데
* String 클래스의 compareTo() 메소드에서 이미 정의해 놓았다.
* */
/* 앞의 값이 더 작은 경우(즉, 바꾸지 않아도 되는 경우) 음수 반환
* 같으면 0 반환
* 앞의 값이 더 큰 경우(즉, 바꿔야 하는 경우) 양수 반환
* */
return o1.getTitle().compareTo(o2.getTitle());
}
});
System.out.println("제목 오름차순 정렬 ---------------------- ");
for(BookDTO book : bookList) {
System.out.println(book);
}
/* 제목 내림차순 정렬 */
bookList.sort(new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
// return o2.getTitle().compareTo(o1.getTitle()); //위치를 바꾸거나
return o1.getTitle().compareTo(o2.getTitle()) * -1; //-1을 곱해서 반대로 만든다
}
});
System.out.println("제목 내림차순 정렬 ---------------------- ");
for(BookDTO book : bookList) {
System.out.println(book);
}
}
2-2. Vector
- Vector의 경우 스레드 동기화 처리가 된다는 점만 다르고, ArrayList와 동일하게 동작한다.
- Vector는 JDK 1.0버전부터 사용해왔으며, ArrayList의 구 버전이라고 봐도 된다. 호환을 위해 남겨두었다.
- ArrayList는 동기화 처리가 불가한 반면 Vector는 동기화 지원한다. 성능적인 면에 있어서는 사용되지 않는다.
- 성능 문제로 현재는 사용하지 않는다. 가급적이면 ArrayList가 쓰인다.
2-3. LinkedList
ArrayList 단점 보완
- 배열에 기반한 ArrayList 특성상 생겨난 성능적 단점 보안을 위해 고안되었다. 배열을 통해 데이터를 관리하는 ArrayList의 경우 중간 인덱스 값을 삽입/삭제해야 한다면 인덱스를 앞뒤로 덮어씌우는 처리가 필요할 것이다.
- 하지만 LinkedList는 특정 인덱스에서 인스턴스 추가/제거하게 되면 바로 앞뒤 링크만 변경이 필요하다. 때문에 인스턴스 삭제와 삽입이 빈번하게 일어나는 데는 ArrayList보다 성능이 뛰어나다.
ArrayList는 변동사항이 생길 때마다 인덱스를 밀고 당기는 작업이 일어나야 하지만, LinkedList는 중간 값이 추가/삭제되면 그것의 앞뒤로 있던 것만 새로 연결될 뿐이다.
중간에 삽입/삭제가 빈번하게 발생할 때는 ArrayList보다 LinkedList를 사용함이 알맞다.
- 단일 연결 리스트
저장한 요소가 순서를 유지하지 않고 저장되짐나 이러한 요소들 사이를 링크로 연결해 구성하며
마치 연결된 리스트 형태인 것처럼 만든 자료구조이다.
요소의 저장과 삭제 시 다음 요소를 가리키는 참조 링크만 변경하면 되기 때문에
요소의 저장과 삭제가 빈번히 일어나는 경우 ArrayList보다 성능면에서 우수하다.
하지만 단일 연결리스트는 다음 요소만 링크하기 때문에 이전 요소로 접근하기 어렵다. 다음 값만 알고 이전 값은 모르기 때문이다.
이를 보완하고자 만든 것이 이중 연결 리스트이다.
- 이중 연결 리스트
단일 연결 리스트는 다음 요소만 링크하는 반면 이중 연결 리스트는 이전 요소도 링크하여, 이전 요소로 접근하기 쉽게 고안된 자료구조이다.
하지만 내부적으로 요소를 저장하는 방법에 차이가 있는 것이다.
각 컬렉션 프레임워크 클래스들의 특징을 파악하고 그에 따라 적합한 자료구조를 구현한 클래스를 선택하는 것이 좋다.
2-4. stack
- Last Input First Out(LIFO), 후임선출 방식의 자료구조를 말한다.
- Stack은 리스트 계열 클래스의 Vector 클래스를 상속 받아 구현하였다.
push(E item)
- 주어진 인스턴스를 스택에 넣는다.
- Stack에 값을 넣을 때는 push() 메소드를 이용한다.add()도 이용 가능하지만 Vector의 메소드이므로 push()를 사용하는 것이 좋다.
peek()
- 스택의 맨 위 인스턴스를 가져온다. 인스턴스를 스택에서 제거하지 않는다.
pop()
- 스택의 맨 위 인스턴스를 가져온다. 인스턴스를 스택에서 제거한다.
- pop은 꺼내면서 요소를 제거하기 때문에 스택이 비어 있는 경우 에러가 발생한다: java.util.EmptyStackException
search()
integerStack.search(5)
- 스택에서 요소를 찾을 때 search()를 이용할 수 있다.
- 인덱스가 아닌 위에서부터의 순번을 의미한다. 또한 가장 상단의 위치가 0이 아닌 1부터 시작한다.
2-5. queue
- First Input First Out(FIFO), 선입선출 방식의 자료구조를 말한다.
- Queue 인터페이스를 상속 받는 하위 인터페이스들은 Deque, BlockingQueue, TransferQueue 등 다양하지만, 대부분의 큐는 LinkedList를 이용한다.
Queue<String> que = new Queue<>();
- Queue 자체로는 인터페이스이기 때문에 인스턴스 생성이 불가능하다.
Queue<String> que = new LinkedList<>();
- LinkedList로 인스턴스 생성한다.
que.offer("first");
que.offer("second");
que.offer("third");
- offer() : 큐에 데이터를 넣을 때에는 offer()를 이용한다.
- peek() : 해당 큐의 가장 앞에 있는 요소(먼저 들어온 요소)를 반환한다.
- poll() : 해당 큐의 가장 앞에 있는 요소(먼저 들어온 요소)를 반환하고 제거한다.
3. Set 계열
저장순서X, 중복X
예. 네발동물 집합
❗ 구현클래스 HashSet, LinkedHashSet, TreeSet
- 저장 순서가 유지되지 않고, 중복 인스턴스도 저장하지 못하는 자료구조이다.
- null 값도 중복될 수 없어 하나의 null만 저장 가능하다.
3-1. HashSet 클래스
- Set 컬렉션 클래스에서 가장 많이 사용되는 클래스 중 하나이다.
- JDK 1.2부터 제공되고 있으며, 해시 알고리즘을 이용하여 검색 속도가 빠르다는 장점이 있다.
Set hset = new HashSet();
Collection hset = new HashSet();
- 다형성 적용하여 상위 인터페이스를 타입으로 사용 가능하다.
- 저장된 객체를 하나씩 꺼내는 기능이 없다. 따라서 아래 두 가지 반복문을 이용한 연속 처리 방법을 따른다.
Object[] arr = hset.toArray();
for(int i=0; i < arr.length; i++) {
System.out.println(i + " : " + arr[i]);
}
- A. toArray()로 배열로 변경한 뒤 for loop 사용
Iterator<String> iter = hset.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
- B. iterator()로 목록 만들어 연속 처리
3-2. LinkedHashSet 클래스
- HashSet의 모든 기능에, 저장 순서를 유지하는 특징이 접목된 클래스이다.
- JDK 1.4버전부터 제공하고 있다.
LinkedHashSet<String> lhset = new LinkedHashSet<>();
lhset.add("java");
lhset.add("oracle");
lhset.add("jdbc");
lhset.add("html");
lhset.add("css");
System.out.println("lhset : " + lhset);
lhset : [java, oracle, jdbc, html, css]
3-3. TreeSet 클래스
- Set 인터페이스의 특성들을 모두 가지지만, 정렬된 상태를 유지한다는 것이 가장 큰 차이이자 특징이다.
- 이진 검색 트리는 데이터를 추가하거나 제거하는 등의 기본 동작 시간이 매우 빠르다.
- JDK 1.2부터 제공되고 있다.
TreeSet<String> tset = new TreeSet<>(lhset);
System.out.println("tset : " + tset);
tset : [css, html, java, jdbc, oracle]
- 만들어진 LinkedHashSet을 가지고 TreeSet으로 객체를 생성하면 같은 타입의 객체를 자동으로 비교하여 오름차순으로 정렬한다.
- 기준값이 스스로 저보다 작은 값은 왼쪽, 큰 값은 오른쪽으로 저장하는 형식을 가졌다.
Iterator<String> iter = tset.iterator();
while(iter.hasNext()) {
System.out.println(iter.next().toUpperCase());
}
- iterator() 통해 목록 만들어 하나씩 대문자로 변경하는 예시이다.
Object[] arr = tset.toArray();
for(Object obj : arr) {
System.out.println(((String)obj).toUpperCase());
}
- 배열로 바꾸어 연속 처리할 수 있다.
- 다운캐스팅 후 대문자 변환... Object에서는 String 클래스가 가진 toUpperCase()로 곧장 접근 못한다.
Set<Integer> lotto = new TreeSet<>();
while(lotto.size() < 6) {
lotto.add((int)(Math.random() * 45) + 1);
}
System.out.println("lotto : " + lotto);
- 이러한 TreeSet의 특징 이용해 로또 번호 발생기를 만들어 볼 수 있다.
- 중복된 값이 나오더라도 어차피 취급 안 할 것이다.
- 출력 시에는 자동으로 정렬까지 해준다.
'Java' 카테고리의 다른 글
[자바의 정석] Ch 11. 컬렉션 프레임워크 강의 메모 (0) | 2022.01.11 |
---|---|
[자바의 정석] Ch 6. OOP I 예제 응용 학습 (0) | 2022.01.10 |
[자바의 정석] Ch 6. 객체 지향 프로그래밍 강의 메모 (0) | 2022.01.10 |
[자바의 정석] Ch 6. OOP I 연습문제 풀이 (0) | 2022.01.09 |
[JAVA] 11. 제네릭 클래스 | 와일드카드 (0) | 2022.01.08 |