💁♀️ 컬렉션(Collection)이란,
여러 개의 다양한 데이터들을 쉽고 효과적으로 처리할 수 있도록 표준화 된 방법을 제공하는 클래스들의 집합
(데이터를 효율적으로 저장하는 자료구조와 데이터를 처리하는 알고리즘이 미리 구현되어 있음)
💁♀️ 리스트(List)란,
메모리가 허용하는 한 계속 해서 추가 할 수 있도록 만든 자료형 클래스.
java.util.List 는 인터페이스 클래스이며, java.util.Collection 인터페이스를 구현한 것.
- 순서를 유지하고 중복 저장 가능 🔥중요🔥
📍 List 계열
1) ArrayList
2) LinkedList
3) Stack & Queue
💁♀️ ArrayList와 LinkedList에 모두 적용되는 메소드
: 다른 리스트계열이지만 메소드가 모두 같아 사용자 입장에서는 매우 편리
(인터페이스의 장점)
💁♀️ ArrayList란,
가장 많이 사용하는 컬렉션 클래스. 내부적으로는 배열을 이용해서 요소를 관리하며, 인덱스를 이용해 배열 요소에 빠르게 접근할 수 있음.
- 크기 변경 불가, 요소의 추가/수정/삭제/정렬이 복잡한 배열의 단점을 보완하기 위해 만들어졌음
- 크기 변경, 요소의 추가/수정/삭제/정렬 기능 등을 미리 메소드로 구현해서 제공
(단, 자동으로 수행되는 것이지 속도가 빨라진다는 의미는 아님)
[1] 인스턴스 생성 시 내부적으로 10칸짜리 배열을 생성해서 관리
ArrayList alist = new ArrayList();
[2] 다형성을 적용하여 상위 레퍼런스로 ArrayList 객체를 참조할 수 있음
(List 인터페이스 하위의 다양한 구현체들로 타입 변경이 가능하기 때문에 레퍼런스
타입은 List로 해두는 것이 '더 유연한 코드를 작성'하는 것)
List list = new ArrayList();
>>> List는 ArrayList의 부모 인터페이스이기 때문에 가능한 식
>>> 더 상위 타입인 Collection 타입을 이용할 수도 있음
Collection clist = new ArrayList();
>>> (하지만, 보편적으로 List를 사용)
[3] Object 클래스 하위 타입 인스턴스를 모두 저장할 수 있음 (add)
(int나 double은 Integer, Double로 오토 박싱)
alist.add("apple");
alist.add(123);
>>> 컬렉션은 객체만 저장하기 때문에 int값을 입력해도 Integer로 오토박싱
alist.add(45.67);
alist.add(new Date());
[4] toString 메소드가 오버라이딩 되어있어 저장 순번이 유지되고 있음
System.out.println("alist : " + alist);
>>> (toString이 오버라이딩 되어있어 주소값이 아닌 필드값 출력)
[5] size 메소드는 배열의 크기가 아닌 '요소의 갯수'를 반환 (size)
(내부적으로 관리되는 배열의 사이즈는 외부에서 알 필요가 없으므로 기능을 제공 X)
System.out.println("alist의 size : " + alist.size());
>>> 기본칸 10개가 아닌 가지고 있는 요소인 4개를 출력
[6] 내부 배열에 인덱스가 지정되어 있기 때문에 for문으로 접근 가능 (get)
for(int i = 0; i < alist.size(); i++) {
System.out.println(i + " : " + alist.get(i));
// < 출력문 >
// 0 : apple
// 1 : 123
// 2 : 45.67
// 3 : Wed Jan 04 11:49:33 KST 2023
} >>> 인덱스에 해당하는 요소를 가져올 때는 'get' 메소드를 사용
[7] 데이터의 중복 저장을 허용 (add)
(배열과 같이 '인덱스로 요소들을 관리'하기 때문에 인덱스가 다른 위치에 동일한 값을
저장하는 것이 가능)
alist.add("apple"); >>> 맨 뒤 배열에 "apple" 추가됨
System.out.println("alist : " + alist);
>>> 원하는 인덱스 위치에 값을 추가할 수도 있음
alist.add(1, "banana"); >>> 1번 인덱스 자리에 "banana" 추가됨
System.out.println("alist : " + alist);
>>> 값을 중간에 추가하는 경우 인덱스 위치에 덮어쓰는 것이 아니라
>>> 새로운 값이 들어가는 인덱스 위치에 값을 넣고 '이후 인덱스는 하나씩 뒤로 밀리게 됨'
[8] 지정된 값을 삭제 (remove)
(중간 인덱스의 값을 삭제하는 경우 자동으로 '인덱스를 하나씩 앞으로 당김')
alist.remove(2);
System.out.println("alist : " + alist);
>>> 2번 인덱스였던 "123"이 제거되고 나머지 값들은 앞으로 당겨짐
[9] 지정된 위치의 값을 수정 (set)
alist.set(1, true);
System.out.println("allist : " + alist);
>>> 1번에 있던 "banana"가 true로 변경됨
[10] 제네릭 타입을 지정하면 지정한 타입 외의 인스턴스는 저장하지 못 함
(모든 '컬렉션 프레임워크 클래스는 제네릭 클래스'로 작성되어있음)
List<String> stringList = new ArrayList<>();
stringList.add("banana");
stringList.add("orange");
stringList.add("mango");
stringList.add("grape");
// stringList.add(123);
>>> 제네릭을 String으로 선언했기때문에 int값은 불가
System.out.println("stringList : " + stringList);
// stringList : [banana, orange, mango, grape]
[11] 저장 순서를 유지하고 있는 stringList를 오름차순 정렬 (sort)
(사전순으로 정렬, 원본이 바뀜)
Collections.sort(stringList);
System.out.println("stringList : " + stringList);
// stringList : [banana, grape, mango, orange]
>>> sort 메소드를 사용하면 list가 정렬된 뒤 해당 상태가 유지
💁♀️ Iterator란,
컬렉션에 저장된 요소에 접근하는데 사용되는 인터페이스 (쉽게 값을 가져오고 제거가능)
- List와 Set계열에서만 사용
(Map의 경우 List 또는 Set화 시켜서 iterator()를 사용)
[12] ArrayList를 'LinkedList'로 변경하여 내림차순 정렬 (Iterator)
(ArrayList에는 역순 정렬 기능이 제공되지 않기 때문)
List<String> stringList2 = new LinkedList<>(stringList);
stringList = new LinkedList<>(stringList);
// : stringList가 포함된 새로운 LinkedList 만들기
Iterator<String> iter = stringList.iterator(); >>> stringList의 요소값들에게 접근
while(iter.hasNext()) { >>> 다음 요소를 가지고 있는 경우 true,
더 이상 요소가 없는 경우 false 반환
System.out.println(iter.next()); >>> 다음 요소를 반환
}
[13] 반대 순서로 출력 (Descending Iterator)
Iterator<String> dIter = ((LinkedList<String>)stringList).descendingIterator();
>>> LinkedList로 구체화하여 다운캐스팅
while(dIter.hasNext()) {
System.out.println(dIter.next());
}
>>> 'Iterator는 한번 꺼내면 다시 쓸 수 없음'
// while(dIter.hasNext()) {
// System.out.println(dIter.next());
// }
}
>>> 가격 오름차순이라는 '정렬 기준'을 작성하는 용도의 클래스
public class AscendingPrice implements Comparator<BookDTO> {
>>> Comparator 인터페이스를 상속 받으면 오버라이딩 해야하는 메소드가 강제화(compare 메소드)
>>> 제네릭 작성 시 Object가 아닌 구체화 된 타입으로 매개변수가 선언되어 다운 캐스팅할 필요가 없어짐
@Override
public int *compare(BookDTO o1, BookDTO o2) {
// 내가 정렬 기준으로써 뭘 삼고있는지를 식으로 구성
int result = 0;
if(o1.getPrice() > o2.getPrice()) {
result = 1;
>>> 오름차순을 위해 순서를 바꿔야 하는 경우 양수 반환
} else if(o1.getPrice() < o2.getPrice()) {
result = -1;
>>> 이미 오름차순으로 정렬 되어 있는 경우 음수 반환
} else {
result = 0;
>>> 두 값이 같은 경우 0을 반환
}
return result;
}
}
📌 Ref.
* compare : sort()에서 내부적으로 사용되는 메소드
비교 대상 두 인스턴스의 가격이 오름차순 정렬이 되기 위해서는 앞의 가격이 더 작은 가격이어야하고,
만약 뒤의 가격이 더 작은 경우 두 인스턴스의 순서를 바꿔야함
그때, 두 값을 바꾸라는 신호로 양수를 보내주게 되면 정렬 시 순서를 바꾸는 조건으로 사용
public class BookDTO implements Comparable<BookDTO>{
private int number;
private String title;
private String author;
private int price;
public BookDTO() {}
public BookDTO(int number, String title, String author, int price) {
super();
this.number = number;
this.title = title;
this.author = author;
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "BookDTO [number=" + number + ", title=" + title + ",
author=" + author + ", price=" + price + "]";
}
@Override
public int compareTo(BookDTO o) {
>>> this와 비교 대상 객체 BookDTO o를 비교하여 number 오름차순 정렬
return number - o.number;
}
}
// 여러 권의 책 목록을 관리할 ArrayList 인스턴스 생성
List<BookDTO> bookList = new ArrayList<>();
// 도서 정보 추가
bookList.add(new BookDTO(1, "홍길동전", "허균", 50000));
bookList.add(new BookDTO(2, "목민심서", "정약용", 40000));
bookList.add(new BookDTO(3, "동의보감", "허준", 30000));
bookList.add(new BookDTO(4, "삼국사기", "김부식", 20000));
bookList.add(new BookDTO(5, "삼국유사", "일연", 35000));
// 추가된 도서 정보 출력
for(int i = 0; i < bookList.size(); i++) {
System.out.println(bookList.get(i));
}
// 향상된 for문으로 도서 정보 출력
for(BookDTO book : bookList) {
System.out.println(book);
}
>>> 정렬 기준이 존재하지않아 정렬할 수 없는 상태
//Collections.sort(bookList);
Collections.sort(bookList, new AscendingPrice());
System.out.println("가격 오름차순 정렬 ----------------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
📌 Ref.
* 인터페이스를 구현할 클래스를 재사용하는 경우 AscendingPrice 클래스처럼 작성하면 되지만
한 번만 사용하고 더 이상 재사용할 일이 없을 경우에는 조금 더 간편한 방법을 이용할 수 있음
* 그것이 익명클래스(Anonymous)를 이용한 방법. 익명클래스는 뒤에 {}를 만들어서 마치
Comparator 인터페이스를 상속 받은 클래스인데 이름이 없다고 생각하고 사용하는 것
Collections.sort(bookList, new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
// 가격 내림차순 기준 작성
>>> 가격 오름차순과는 반대로 뒤에 있는 값인 o2의 price가 더 클 경우,
양수를 반환하여 순서를 바꾸게 함
// return o1.getPrice() - o2.getPrice(); >>> 오름차순
return o2.getPrice() - o1.getPrice(); >>> 내림차순
}
});
System.out.println("가격 내림차순 정렬 ----------------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
Collections.sort(bookList, new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
>>> 문자열이기 때문에 산수연산자를 사용할 수 없음
// return o1.getTitle() - o2.getTitle();
return o1.getTitle().*compareTo(o2.getTitle());
}
});
System.out.println("제목 오름차순 정렬 ----------------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
📌 Ref.
* compareTo() : 문자열은 대소비교를 할 수 없음. 문자 배열로 변경 후 인덱스 하나 하나를
비교해서 어느 것이 더 큰 값인지 확인해야 하는데 String 클래스의 compareTo() 메소드에서
이미 정의해 놓았기 때문에 산수연산자 대신 사용하면 됨
Collections.sort(bookList, new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
>>> 오름차순의 반대
return o2.getTitle().compareTo(o1.getTitle());
}
});
System.out.println("제목 내림차순 정렬 ----------------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
>>> BookDTO implements Comparable<BookDTO>를 작성하고나면 sort 메소드를 호출 할 수 있음
>>> (내부적인 정렬 기준이 정의 되었기 때문)
Collections.sort(bookList);
System.out.println("번호 오름차순 정렬 -----------------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
💁♀️ LinkedList란,
인접 참조를 링크해서 체인처럼 관리 (내부는 이중 연결 리스트로 구현)
- 특정 인덱스에서 인스턴스를 제거하거나 추가하게 되면 바로 앞/뒤 링크만 변경하면 되기 때문에 인스턴스 삭제와 삽입이 빈번하게 일어나는 곳에서는 ArrayList보다 성능이 뛰어남
🙋 잠깐 ! 단일 연결 리스트와 이중 연결 리스트의 차이점은 ?
💁♀️ 단일 연결 리스트란,
저장한 요소가 순서를 유지하지 않고 저장 되지만 이러한 요소들 사이를 링크로 연결하여 구성하며 마치 연결 된 리스트 형태인 것처럼 만든 자료 구조.
- 요소의 저장과 삭제 시 다음 요소를 가리키는 참조 링크만 변경하면 되기 때문에 요소의 저장과 삭제가 빈번히 일어나는 경우 ArrayList보다 성능 면에서 우수
- 하지만 단일연결 리스트는 다음 요소만 링크하기 때문에 이전 요소로 접근하기 어려움 (이를 보완하고자 만든 것이 이중 연결 리스트)
💁♀️ 이중 연결 리스트란,
단일 연결 리스트는 다음 요소만 링크하는 반면, 이중 연결 리스트는 이전 요소도 링크하여 이전 요소로 접근하기 쉽게 고안 된 자료 구조
[1] LinkedList 인스턴스 생성
List<String> linkedList = new LinkedList<>();
>>> List : 상위 타입 / LinkedList : 하위 타입
[2] 요소 추가 (add)
linkedList.add("apple");
linkedList.add("banana");
linkedList.add("orange");
linkedList.add("grape");
[3] 저장된 요소의 갯수 반환 (size)
System.out.println(linkedList.size()); // 4 출력
[4] 저장된 요소를 반환 (get)
>>> for문과 size()를 이용해서 반복문 사용 가능
>>> 요소를 꺼내올 때는 get()을 사용
for(int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
[5] 요소를 제거 (remove)
linkedList.remove(1); // 1번 인덱스였던 "banana" 사라짐
>>> 출력 시, for-each 문도 사용 가능
for(String str : linkedList) {
System.out.println(str);
}
[6] 요소를 수정 (set)
linkedList.set(0, "pineapple"); // 0번 인덱스의 "apple"을 "pineapple"로 변경
System.out.println(linkedList); // [pineapple, orange, grape] 출력.
[7] 리스트 내 요소를 모두 제거 (clear)
System.out.println(linkedList.isEmpty()); // false
linkedList.clear();
System.out.println(linkedList.isEmpty()); // true
📌 Ref.
* 모든 List에는 toString() 메소드가 오버라이딩 되어있어 주소값이 아닌 필드값 출력 (모든 요소의 정보를 쉽게 볼 수 있음)
💁♀️ Stack이란,
선형 메모리 공간에 제한적으로 접근할 수 있는 나열 구조로 데이터를 저장하는 후입 선출(LIFO - Last In First Out) 방식의 자료구조
(리스트 계열의 클래스 'Vector 클래스'를 상속받아 구현)
[1] Stack 인스턴스 생성
Stack<Integer> integerStack = new Stack<>();
>>> Integer타입만 저장하겠다는 제네릭
[2] Stack에 값을 넣을 때 (push)
>>> add()도 사용 가능하지만 Stack의 기능이므로 push()를 사용하는 것이 좋음
integerStack.push(1);
integerStack.push(2);
integerStack.push(3);
integerStack.push(4);
integerStack.push(5);
System.out.println(integerStack);
// [1, 2, 3, 4, 5] 출력
[3] Stack에서 요소를 찾을 때 (search)
>>> 인덱스가 아닌 '위에서부터의 순번을 의미'하며 가장 상단의 위치가 0이 아닌 1부터 시작
System.out.println(integerStack.search(5));
>>> : 이 스택에 5가 어디 위치에 있나요?
// 1 출력
>>> 왜냐하면 Stack은 아래에서부터 1, 2, 3, 4, 5로 쌓이는 형태 이므로 맨 위에 있는 5가 첫 번째
System.out.println(integerStack.search(1));
// 5 출력
>>> 따라서 1은 맨 아래 (다섯 번째 자리)
[4] Stack에서 값을 꺼낼 때 (peek, pop)
>>> peek() : 해당 스택의 가장 마지막에(상단에) 있는 요소 반환
>>> pop() : 해당 스택의 가장 마지막에(상단에) 있는 요소 반환 후 제거
System.out.println("peek() : " + integerStack.peek());
System.out.println(integerStack);
// 마지막에 있는 5 반환(출력)
System.out.println("pop() : " + integerStack.pop());
System.out.println(integerStack);
// 마지막에 있는 5 반환 및 제거
>>> pop()은 꺼내면서 요소를 제거하기 때문에 스택이 비어있는 경우 에러가 발생할 수 있음
System.out.println("pop() : " + integerStack.pop()); // 4 반환 후 제거
System.out.println("pop() : " + integerStack.pop()); // 3 반환 후 제거
System.out.println("pop() : " + integerStack.pop()); // 2 반환 후 제거
System.out.println("pop() : " + integerStack.pop()); // 1 반환 후 제거
// System.out.println("pop() : " + integerStack.pop());
>>> 비어있는 스택에서 더이상 제거할 요소가 없으므로, java.util.EmptyStackException 에러
💁♀️ Queue란,
선형 메모리 공간에 데이터를 저장하는 선입 선출(FIFO - First Input First Out) 방식의 자료구조
- Queue 인터페이스를 상속받는 하위 인터페이스들은 Deque, BlockingQueue, TrnasferQueue 등 다양하지만 대부분의 큐는 LinkedList를 이용
>>> Queue 자체로는 인터페이스이기 때문에 인스턴스 생성이 불가능
// Queue<String> que = new Queue<>();
>>> cannot instantiate the type Queue 오류
[1] 대신 LinkedList로 인스턴스 생성
Queue<String> que = new LinkedList<>();
[2] Queue에 값을 넣을 때 (offer)
que.offer("first");
que.offer("second");
que.offer("third");
que.offer("fourth");
que.offer("fifth");
System.out.println(que);
// [first, second, third, fourth, fifth] 출력
[3] Queue에서 값을 꺼낼 때 (peek, poll)
>>> peek() : 해당 큐의 가장 앞에 있는(먼저 들어온) 요소를 반환
>>> poll() : 해당 큐의 가장 앞에 있는(먼저 들어온) 요소를 반환 후 제거
System.out.println("peek() : " + que.peek());
System.out.println(que);
// first 반환(출력)
System.out.println("poll() : " + que.poll());
System.out.println(que);
// first 반환 및 제거
// [second, third, fourth, fifth] 출력
System.out.println("poll() : " + que.poll()); // second 반환 후 제거
System.out.println("poll() : " + que.poll()); // third 반환 후 제거
System.out.println("poll() : " + que.poll()); // fourth 반환 후 제거
System.out.println("poll() : " + que.poll()); // fifth 반환 후 제거
System.out.println("poll() : " + que.poll()); // 오류가 아닌 null 출력 (반환 값이 없을 경우 null로 반환)
>>> Stack과의 차이점 (Stack : 오류 / Queue : null값 출력)