Collection Framework
Collection Frameworksms는 Collection interface 를 최상위로 하는 자료구조 interface이다.
List, Set, Map interface를 implements 하여 각 collection으로 활용한다.
Collection Framework 는 다양한 자료구조를 가지고 있고, 초기 선언 시 크기를 지정하지 않아 유연한 사용이 가능하다.
List는 한계가 없다. 즉 넣고 싶은 만큼을 다 넣을 수 있다.
Set은 순서가 상관 없고, 중복이 불가능하다. (로또 번호 추첨을 생각하면 된다.)
Map은 key와 key에 해당하는 value로 구성되어 있다.
Collection Framework 에서 데이터 추가, 삭제, 검색 방법은 거의 비슷하며 그 이유는 같은 인터페이스를 상속받았기 때문이다.
인터페이스 구현 시 장점은 필수 method를 강제할 수 있다.
List Collection
List를 구현한 Array List, Linkde List, Vector 은 List 메소드 외에 별도 구현한 메소드가 없기 때문에 다형성을 활용해도 문제가 없다.
또 하나의 장점은 System.out.println(); 로도 배열을 확인할 수 있다는 것이다.
ArrayList 는 index로 객체를 관리한다는 점에서 Array와 유사하지만 index 유연하다는 차이점이 있다.
Array List는 객체를 삭제하거나 추가할 때 인덱스가 1씩 당겨지거나 미뤄지게 되기 때문에 빈번한 객체의 추가, 삭제가 일어날 때 다소 무리가 가게된다.
만약 100명이 있고 그 중 50번이 갑자기 빠진다면 그 뒤에 있는 50명이 모두 한 자리씩 앞으로 이동을 해야한다.
만약 1번이 자리를 비우게 된다면 99명이 자리를 옮겨줘야 한다.
이게 list의 최대 단점이고 이를 보완하기 위해 만든 것이 Linkde List이다.
Linkde List는 Array List와 사용 방법은 같지만 좌우 데이터의 주소를 기억하는 구조를 갖는다. (양 옆에 누가 있는지를 확인한다.)
그렇기 때문에 중간에 데이터가 추가/삭제되는데 높은 효율을 자랑한다.
하지만 단점은 계속 내 옆에 누가 있는지를 확인해줘야 한다는 점이 있다.
Vector 와Array List의 차이점은 하나이다.
바로 특정 Thread 가 접근 시 다른 Thread가 접근할 수 없다는 것이다.
(본인이 사용할 경우 문을 잠그고 다른 사람의 접근을 허용하지 않는다.)
이러한 list의 사용 방법은 다음과 같다.
List<E> list = new ArrayList<E>(); // 다형성으로 부모 타입의 형태로 선언 가능
<> 에는 class 형태의 타입을 선언해주면 된다.
(<> 안에 타입을 선언하면 이제 이 타입만 사용할 것이다 라는 의미가 된다.)
list에 값을 추가해주는 방법은 .add()가 있다.
특정 인덱스 번호를 선언하지 않으면 순차적으로 뒤에 들어가 붙고, 인덱스 번호를 선언해주면 해당 인덱스에 들어가게 된다.
(만약 선언한 인덱스에 이미 데이터가 들어가 있다면 뒤로 한 칸씩 밀리게 된다.)
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add(1, "C");
System.out.println(list); // A, C, B 출력
list의 크기 확인 및 값을 꺼내오는 메소드는 다음과 같다.
.size() : 배열의 크기를 알 수 있다.
.get(index) : 특정 인덱스의 값을 꺼내올 수 있다.
for(int i = 0; i < list.size(); i++){
System.out.println(lise.get(i));
}
list에 있는 값을 지울 때에는 .remove() 를 사용해주면 된다.
이 메소드는 2가지 특징이 있다.
1. index 번호를 넣을 경우 해당 index에 있는 값을 리턴해준다.
2. 값을 넣을 경우 boolean 값을 리턴 (동일 값이 있을 경우 최초 발견된 값만 제거)
list.remove(3); // index 3에 있던 값을 반환
list.remove("A"); // 지우는게 성공했는지 t/f를 반환
모든 값을 삭제할 때에는.clear() 을 사용해준다.
하지만 clear을 사용하면 잘 안지워지는 경우도 있고, 동시 접근을 하는 경우 섞이는 경우가 있기 때문에 차라리 객체를 다시 선언해주는 것을 더 추천한다.
list = new ArrayList<String>();
.isEmpty() : 데이터가 있는지 없는지 확인
list의 값을 수정할 때에는
.set(index, 값) : 특정 인덱스의 값을 덮어쓰고, 변경하기 전의 값을 알려준다.
특정값을 검색할 때에는
.contains(값) : 값이 있을 경우 true를 반환
.indexOf(값) : 해당 값이 있는 index 위치 반환 (없을 경우 -1 반환)
A와 B가 자신의 달리기가 더 빠르다고 했을 때 어떻게 확인을 해주면 공정하게 정확한 결과를 얻을 수 있을까?
동일한 조건을 주고 한명씩 달려보게 하면 알 수 있을 것이다.
프로그램에서도 속도를 비교할 때 하나씩 실행시키고 시간을 비교한다.
1회만 비교하면 정확한 값이 아닐 수 있으므로 그 행동을 수십번 반복해준다. (cpu에 따라 다르게 나올 수 있다.)
public class Main {
public static void main(String[] args) {
// 1. 선수 등장
ArrayList<String> arr = new ArrayList<String>();
LinkedList<String> lnk = new LinkedList<String>();
// 2. 장소(조건) 준비
for (int i = 0; i <= 100; i++) {
arr.add("data");
lnk.add("data");
}
long startTime = 0; // 출발시간을 넣기 위한 변수 선언
long endTime = 0; // 도착시간을 넣기 위한 변수 선언
// 3. A 출발 (시작시간 체크)
startTime = System.currentTimeMillis();
// 4. A 달린다.
for (int i = 1; i <= 100000; i++) {
arr.add(55, "add data");
}
// 5. A 도착 (도착시간 체크)
endTime = System.currentTimeMillis();
// 6. 결과 (도착시간 - 시작시간)
System.out.println("ArrayList 결과: " + (endTime - startTime) + "ms");
// 3. B 출발 (시작시간 체크)
startTime = System.currentTimeMillis();
// 4. B 달린다.
for (int i = 1; i <= 100000; i++) {
lnk.add(55, "add data");
}
// 5. B 도착 (도착시간 체크)
endTime = System.currentTimeMillis();
// 6. 결과 (도착시간 - 시작시간)
System.out.println("LinkdeList 결과: " + (endTime - startTime) + "ms");
}
}
뒤에다 넣기 시작할 때는 ArrayList가 더 빠르지만 중간에 넣기 시작할 때는 LinkdeList가 더 빠르다.
Set Collection
Set은 로또볼처럼 순서가 없지만 중복을 허용하지 않는다고 했다.
중복을 허용하지 않는건 2가지 개념이 있는데
1. 중복값이 들어오면 덮어쓴다.
2. 중복값이 들어오는 것을 막아버린다.
Set은 중복값이 들어오는 것을 아예 막아버린다.
또한 Set은 검색기능이 없는 대신에 하나씩 꺼낼 수 있는 Iterator를 제공한다.
검색 기능을 제공하지 않는 이유는 당연히 순서가 없기 때문이다.
Set은 덩어리로 되어있기 때문에 Iterator를 통해 하나씩 쪼개준뒤 next()를 이용해 값을 하나씩 꺼내준다.
Set에 데이터를 추가, 삭제, 비었는지 확인 등의 작업을 할 때 사용하는 메소드는 List와 동일하다.
Set에 객체도 넣을 수 있는데, 객체는 복사본이기 때문에 일련번호가 달라 중복값으로 인식하지 않는다.
// Member class가 있다고 가정
Set<Member> member = new HashSet<Member>();
member.add(new Member);
member.add(new Member);
System.out.println(member.size()); // 중복이 아니므로 2가 출력된다.
Set 객체를 선언해주고 출력해주기 위해선 Iterator 와 next()가 필요하다고 했는데, 자세한 사용방법을 알아보자.
추가로 .hasNext() : 다음 값이 있으면 true를 반환 하는 메소드를 사용해줘야한다.
// Set 객체인 set이 있다고 가정
// 1. Iterator 객체 생성
Iterator<String> iter = set.iterator();
// 2. 반복문을 사용해 하나씩 꺼내기
while(iter.hasNext()){
System.out.println(iter.next());
}
이 방법이 아니면 향상된 for문으로도 하나씩 출력해줄 수 있다.
for(String etem : set){
System.out.println("향상된 for : " + item);
}
✔ 정리
1. Set은 중복을 허용하지 않고, 순서도 존재하지 않는다.
2. Set은 검색할 수 없으며 하나씩 꺼내는 iterator를 제공한다.
3. 향상된 for문을 통해 더 쉽게 꺼내올 수도 있다.
Map
Map은 HashMap, HashTable, Properties, TreeMap가 있는데 이 글에서는 HashMap, HashTable만 다룰 것이다.
Properties은 파일을 다루기 때문에 다른 포스트에서 다룰 예정이다.
*TreeMap는 이제 잘 사용하지 않는다.
Hash Map 은 Map 인터페이스로 구현한 대표적인 Map 컬렉션이다.
key는 순서가 없으며 중복도 허용되지 않고, key는 일반적으로(90% 이상) String를 사용한다. (주로 암호화에 사용한다.)
Map<KeyType, valueType> map = new HashMap <KeyType, valueType>();
HashMap에 데이터를 넣는 방법은 .put()이다.
HashMap도 key의 경우 중복을 허용하지 않는데, Set과 다른 점은 아예 막아버리는 것이 아닌 덮어쓰는 형태로 진행된다.
(즉 중복되는 key에 해당하는 value가 바뀐다.)
map.put("kim", 23); // 이름, 나이
map.put("lee", 26);
map.put("park", 26);// value 중복은 상관 없다.
map.put("kim", 30); // key 중복 (덮어쓰기)
System.out.println(map.size()); // 3출력
단일값을 가져오는 방법은 List, Set과 동일하게 .get()을 사용해주면 되는데, 인덱스 번호가 아닌 key 값을 넣어주면 그에 해당하는 value를 반환해준다.
삭제는 .remove()로 하며 방법은 2가지가 있다.
1. key값으로 지우기 → 내가 지운 key의 value를 반환한다.
2. key와 value 값으로 지우기
HashMap에서 모든 값을 뽑아내는 방법은 총 3가지가 있다.
(방법 1)
Key Set으로 key만 잘라온다. (set은 덩어리 형태)
→ Iterator로 key를 하나씩 분리
→ Get으로 key에 해당하는 value 찾기
(Set은 중복이 없는 경우에만 가져올 수 있다.)
//1. key만 Set 형태로 뽑아오기
Set<String> keySet = map.keySet();
//2. Set을 하나씩 가져올 수 있도록 쪼개기 (iterator)
Iterator<String> iter = keySet.iterator();
//3. key를 하나씩 가져와서 해당되는 value 가져오기
while(iter.hasNext()) {
String key = iter.next();
int val = map.get(key);
System.out.println(key + " : " + val);
}
(방법 2)
Set<Entry> key : value 기준으로 자른다. (Entry = 키:값)
→ Iterator로 자른걸 하나씩 가져온다.
→ Get으로 키와 값을 한번에 뽑을 수 있다.
//1. Entry 를 Set 형태로 가져온다.
Set<java.util.Map.Entry<String, Integer>> entrySet = map.entrySet();
//2. 하나씩 쪼갠다. (iterator)
Iterator<java.util.Map.Entry<String, Integer>> entry = entrySet.iterator();
//3. 하나씩 Entry(key:value) 를 가져와 키와 값을 각각 추출한다.
while (entry.hasNext()) {
java.util.Map.Entry<String, Integer> en = entry.next();
String key = en.getKey();
int age = en.getValue();
System.out.println(key + " : " + age);
}
(방법 3)
.keySet 과 향상된 for문 을 활용해서 출력하기
for (String key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
Hash Table 은 Vector와 같이 동시 수행하는 것을 막고, Hash Map과 동일한 method를 사용한다.
즉, 꼭 한번에 한 명만 사용해야 할 경우 사용해준다.
추가로 특징은 순서는 없지만 검색은 가능하는 것이다.
검색은 정확한 값을 주는 것이 아닌 값으로 넣어 물어본 key 또는 value가 있는지 boolean 타입으로 반환해준다.
(여기서 설명하는 method는 Hash Map에서 동일하게 사용 가능하다.)
해당하는 key가 있는지
.containsKey(key 값) : 해당하는 key가 있으면 true 반환
해당하는 value가 있는지
.containsValue(value 값) : 해당하는 value가 있으면 true 반환
키와 값을 검색하기 위해 HashMap을 만들고, 메소드를 사용해보자
import java.util.HashMap;
import java.util.Map;
public class Main2 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("김철호", 99);
System.out.println(map.containsKey("김철호")); // true
System.our.println(map.containsValue(80)); // false
}
}
위에서 살펴봤던 List의 경우 숫자(index)로 접근을 하지만 Map은 switch문 처럼
list는 숫자로 접근을 하는데
map은 스위치처럼 바로 그곳으로 접근한다.
그래서 무언가를 찾아올 때는 list보다 map이 더 빠르다.
ex) 3번 인덱스를 가져와 (list) 보다 key가 A인 값을 가져와 (map) 이 더 빠르다.
✔ 정리
1. HashMap은 key와 value로 이루어진 자료 구조이다.
2. 순서가 없고, key의 중복을 허용하지 않는다. (덮어쓴다.)
3. 안의 내용을 하나씩 꺼내는 방법을 제공한다. (keySet, entrySet)
LIFO Collection ... Stack
Last In First Out의 약자로 마지막에 들어간 값이 먼저 나오는 것으로 가장 대표적인 것은 Stack 이다.
(Stack memory가 LIFO의 전형이다.)
즉 Stack 은 들어가는 곳과 나가는 곳다.
Stack을 쓰는 이유는 최신 것만 사용하기 때문에 과거 자료를 사용하지 않거나 한번 들어가면 이후에 사용할 일이 없는 경우 사용한다.
그래서 Stack은 무언가를 추척할 때 많이 사용한다.
(ex. 예외처리 시 사용했던 .printStackTrace() 도 예외를 추척하는 method이다.)
.push()로 밀어넣고, .pop()으로 빼낸다. (빼낸 후 즉 사용 후에는 사라진다.)
※ .peek(): 꺼냈다가 다시 넣는다.
Stack에서 push()로 값을 넣은 후 pop()으로 출력할 경우 뒤에서 부터 출력되며 꺼내진 후에는 사라지게 된다.
아래 코드를 예시로 보자
public class Towel {
private String color;
public Towel(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
import java.util.Stack
public class TowelBox{
public static void main(String[] args){
Stack<Towel> box = new Stack<Towel>();
box.push(new Towel("red");
box.push(new Towel("orange");
Towel t = box.pop();
System.out.println(t.getColor() + box.size()); // "orange" 1 출력
// 메소드 체이닝 활용
System.out.println(box.pop().getColor() + box.size()); // "red" 0 출력
}
}
그렇다면 Stack안에 있는 모든 값을 꺼내려면 어떤 방법이 있을까?
아래 코드를 살펴보자
// 방법 1 (size로 확인)
while(box.size() > 0) {
System.out.println(box.pop().getColor() + " 수건 꺼냄 " + box.size() + " 장 남음");
}
// 방법 2 (isEmpty()로 확인)
while(!box.isEmpty()) {
System.out.println(box.pop().getColor() + " 수건 꺼냄 " + box.size() + " 장 남음");
}
향상된 for문은 순차적으로 순회하기 때문에 Stack 에서는 사용하지 않는다.
만약 for문을 쓰면 .size() 가 점점 줄어들어 int i = 0; 으로 초기값을 지정할 경우 정확한 값을 얻기 어렵다.
때문에 언제까지 반복할 것인지에 대해 정확한 수를 지정해줘야 한다.
FIFO Collection ... Queue
First In First Out 약자로 먼저 넣은 값이 먼저 나오는 구조이다.
즉 들어가는 곳과 나오는 곳이 다르다.
Thread Job Queue 가 전형적인 구조이며 순차적으로 작업을 처리할 때 유용하다. (메세지 큐라고도 한다.)
큐는 ArrayList와 비슷한 구조이다. (새로운 값은 뒤에 인덱스로 계속 들어가고 0번 부터 빼낼 수 있다.)
Queue 는 인터페이스로 구성되어 있으며 내 앞 뒤가 무슨 일을 하는지 알고 있어야 하기 때문에 실질적인 구조는 linkdelist 형태로 구현되어 있다.
Queue는 인터페이스여서 실제 구현은 LinkdeList로 해야한다.
두가지의 인터페이스를 구현받은 경우 타입으로 선언한 인터페이스에 있는 메소드만 사용할 수 있기 때문에 Queue를 LinkdeList로 구현한 경우 리스트에 있는 메소드는 사용하지 못한다.
Queue에 데이터를 넣고 빼는 메소드는 다음과 같다.
.offer() : 데이터를 넣는다.
.poll() : 데이터를 뺀다. (다시 넣지 않고 뺀 후 삭제된다.)
→ poll 을 2번 사용할 경우 값이 2번 빠지기 때문에 주의해야 한다.
public class Job {
private String command;
private String to;
public Job(String command, String to) {
this.command = command;
this.to = to;
}
public String getCommand() {
}
public String getTo() {
}
}
import java.util.LinkedList;
import java.util.Queue;
public class JobQueue {
public static void main(String[] args) {
Queue<Job> queue = new LinkedList<Job>();
queue.offer(new Job("send SMS", "Alice"));
queue.offer(new Job("send Mail", "Bryan"));
// Queue에 들어있는 1개 값 출력하기
Job job = queue.poll();
System.out.println(job.getCommand() + " to " + job.getTo() + " size : " + queue.size()); // send SMS to Alice size : 1 출력
// Queue에 들어있는 모든 값 출력하기
while(!queue.isEmpty()) {
job = queue.poll();
System.out.println(job.getCommand() + " to " + job.getTo() + " size : " + queue.size());
}
}
}
※ Queue는 순차적으로 진행되기 때문에 Threads를 사용할 때 주로 사용된다.
즉 한명은 계속 넣고 다른 한명은 리스트를 보면서 처리를 하는 동시진행 구조이거나, 넣는 사람은 1명이고 처리하는 사람이 여럿일 때 주로 사용한다.
✔ 정리
LIFO : 나중에 들어온 데이터가 먼저 나가는 구조로 대표적으로 STACK이 있다.
FIFO : 먼저 들어온 데이터가 먼저 나가는 구조로 대표적으로 QUEUE가 있다.
. 으로 연결해서 계속 이어서 사용할 수 있도록
기존에는 변수에 담은 다음에 사용하는데, 변수에 넣지 않고 바로 사용할 수 있도록 하는걸 메소드 체이닝이라고 한다.