컬렉션 프레임워크 소개
애플리케이션을 개발하다 보면 다수의 객체를 저장해 두고 필요할 때마다 꺼내서 사용하는 경우가 많다. 만약 10개의 Product 객체를 저장해 두고, 필요할 때마다 하나씩 꺼내서 이용한다고 가정해봅시다. 어떻게 Product객체를 효율적으로 추가, 검색, 삭제할지 고민하게 되는데, 가장 간단한 방법은 배열을 이용하는 것입니다.
배열은 쉽게 생성하고 사용할 수 있지만, 저장할 수 있는 객체 수가 배열을 생성할 때 결정되기 때문에 불특정 다수의 객체를 저장하기에는 문제가 있습니다. 물론 배열의 크기를 크게 생성하면 되겠지만, 이것은 좋은 방법이 될 수 없습니다. 배열의 또다른 문제점은 객체를 삭제했을 때 해당 인덱스가 비게 되어 낱알이 듬성듬성 빠진 옥수수가 될 수 있습니다. 그렇게 때문에 새로운 객체를 저장하려면 어디가 비어 있는지 확인하는 코드가 필요합니다.
자바는 배열의 이러한 문제점을 해결하고, 널리 알려져 있는 자료구조(Data Sturucture)를 바탕으로 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 java.util패키지에 컬렉션과 관련된 인터페이스와 클래스들을 포함시켜 놓았습니다. 이들을 총칭해서 컬렉션 프레임워크라고 부릅니다,
자바 컬렉션은 객체를 수집해서 자장하는 역할을합니다. 이는 몇 가지 인터페이스를 통해서 다양한 컬렉션 클래스를 이용할 수 있도록 하고 있습니다. 컬렉션 프레임워크의 주요 인터페이스로는 List, Set, Map이 있습니다. 이 인터페이스들은 컬렉션을 사용하는 방법을 정의한 것입니다.
ArrayList, Vector, LinkedList는 List인터페이스를 구현한 클래스로, List인터페이스로 사용 가능한 컬렉션입니다. 마찬가지로 HashSet, TreeSet은 Set 인터페이스를 구현한 클래스로 Set인터페이스로 사용 가능한 컬렉션입니다. HashMap, HashTable, TreeMap, Properties는 Map인터페이스를 구현한 클래스로 Map인터페이스로 사용 가능한 컬렉션입니다.
List와 Set은 객체를 추가, 삭제, 검색하는 방법에 많은 공통점이 있기 때문에 이 인터페이스들의 공통된 메소드들만 모아 Collection 인터페이스로 정의해 두고 있습니다.
List컬렉션
List컬렉션은 객체를 일렬로 늘어놓은 구조를 가지고 있습니다. 객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동 인덱스가 부여되고 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공합니다. List컬렉션에는 ArrayList, Vector, LinkedList등이 있는데, 다음은 List 컬렉션에서 공통적으로 사용 가능한 List인터페이스의 메소드들입니다. 인덱스로 객체를 관리하기 때문에 인덱스를 매개값으로 갖는 메소드가 많습니다.
메소드의 매개변수 타입과 리턴 타입에 E라는 타입 파라미터가 있는데, 이것은 List인터페이스가 제네릭 타입이기 때문입니다.
< List - ArrayList >
ArrayList는 List 인터페이스의 구현 클래스로, ArrayList에 객체를 추가하면 객체가 인덱스로 관리됩니다. 일반 배열과 ArrayList는 인덱스로 객체를 관리한다는 점에서는 유사하지만, 큰 차이점을 가지고 있습니다. 배열은 생성할 떄 크기가 고정되고 사용 중에 크기를 변경할 수 없지만, ArrayList는 저장 용량을 초과한 객체들이 들어오면 자동적으로 저장 용량이 늘어납니다
또한 ArrayList에 객체를 추가하면 인덱스 0부터 차례대로 저장됩니다. ArrayList에서 특정 인덱스의 객체를 제거하면 바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨집니다. 마찬가지로 특정 인덱스에 객체를 삽입하면 해당 인덱스부터 마지막 인덱스까지 모두 1씩 밀려납니다.
따라서 빈번한 객체 삽입과 삭제가 일어나는 곳에서는 ArrayList를 사용하는 것이 바람직하지 않습니다. 이런 경우라면 LinkedList를 사용하는 것이 좋습니다. 그러나 인덱스 검색이나, 맨 마지막에 객체를 추가하는 경우에는 ArrayList가 더 좋은 성능을 발휘합니다.
package com.company.Collection;
import java.util.*;
public class ArrayListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("JDBC");
list.add("Servlet/JSP");
list.add(2, "DataBase");
list.add("iBATIS");
int size = list.size();
System.out.println("총 객체수: " + size);
System.out.println();
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("iBATIS");
for(int i = 0;i < list.size(); i++) {
String str = list.get(i);
System.out.println(i + ":" + str);
}
System.out.println();
}
}
//총 객체수: 5
//
//2: DataBase
//
//0:Java
//1:JDBC
//2:DataBase
//3:Servlet/JSP
//4:iBATIS
//
//0:Java
//1:JDBC
다음은 ArrayList에 String객체를 추가, 검색, 삭제하는 예제를 만들어 본 것입니다.
ArrayList를 생성하고 런타임 시 필요에 의해 객체들을 추가하는 것이 일반적이지만, 고정된 객체들로 구성된 List를 생성할 때도 있습니다. 이런 경우에는 Arrays.asList(T...a)메소드를 사용하는 것이 간편합니다.
package com.company.Collection;
import java.util.Arrays;
import java.util.List;
public class ArraysAsListExample {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("홍길동", "신용권", "감자바");
for(String name : list1) {
System.out.println(name);
}
Integer[] intList = new Integer[] {1, 2, 3};
List<Integer> list2 = Arrays.asList(intList);
for(int value : list2) {
System.out.println(value);
}
}
}
//홍길동
//신용권
//감자바
//1
//2
//3
< List - Vector >
Vector는 ArrayList와 동일한 내부 구조를 가지고 있습니다. Vector를 생성하기 위해서는 저장할 객체 타입을 타입 파라미터로 표기하고 기본 생성자를 호출하면 됩니다.
ArrayList와 다른 점은 Vector는 동기화된(synchronized)메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 이 메소드들을 실행할 수 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있습니다. 그래서 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다. 이것을 스레드가 안전(Thread Safe)하다라고 말합니다.
다음은 Vector를 이용해서 Board객체를 추가, 삭제, 검색하는 예제입니다.
package com.company.Collection;
import java.util.*;
public class VectorExample {
public static void main(String[] args) {
List<Board> list = new Vector<>();
list.add(new Board("제목1", "제목2", "글쓴이1"));
list.add(new Board("제목2", "제목2", "글쓴이2"));
list.add(new Board("제목3", "제목3", "글쓴이3"));
list.add(new Board("제목4", "제목4", "글쓴이4"));
list.add(new Board("제목5", "제목5", "글쓴이5"));
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);
}
}
}
//제목1 제목2 글쓴이1
//제목2 제목2 글쓴이2
//제목4 제목4 글쓴이4
< List - LinkedList >
LinkedList는 List구현 클래스이므로 ArrayList와 사용 방법은 똑같지만 내부 구조는 완전히 다릅니다. ArrayList는 내부 배열에 객체를 저장해서 인덱스로 관리하지만, LinkedList는 인접 참조를 링크해서 체인처럼 관리합니다.
LinkedList에서 특정 인덱스의 객체를 제거하면 앞뒤 링크만 변경되고 나머지 링크는 변경되지 않습니다. 특정 인덱스에 객체를 삽입할 때에도 마찬가지입니다. ArrayList는 중간 인덱스의 객체를 제거하면 뒤의 객체는 인덱스가 1씩 앞으로 당겨진다고 했습니다. 그렇기 때문에 빈번한 객체 삭제와 삽입이 일어나는 곳에서는 ArrayList보다 LinkeList가 좋은 성능을 발휘합니다.
다음 예제는 ArrayList와 LinkedList에 10000개의 객체를 삽입하는데 걸린 시간을 측정한 것입니다. 0번 인덱스에 String객체를 10000번 추가하기 위해 List인터페이스의 add(int index, E element)메소드를 이용했습니다.
package com.company.Collection;
import java.util.*;
public class LinkedListExample {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
List<String> list2 = new LinkedList<>();
long startTime;
long endTime;
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");
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 걸린시간: 10401879 ns
//LinkedList 걸린시간: 3879617 ns
끝에서부터(순차적으로) 추가/삭제하는 경우는 ArrayList가 빠르지만, 중간에 추가 또는 삭제할 경우는 앞뒤 링크 정보만 변경하면 되는 LinkedList가 더 빠릅니다.
Set컬렉션
List컬렉션은 저장 순서를 유지하지만, Set컬렉션은 저장 순서가 유지되지 않습니다. 또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있습니다.
Set 컬렉션에는 HashSet, LinkedHashSet, TreeSet등이 있습니다. 다음은 Set컬렉션에서 공통적으로 사용 가능한 Set인터페이스들의 메소드입니다. 인덱스로 관리하지 않기 때문에 인덱스를 매개값으로 갖는 메소드가 없습니다.
Set컬렉션은 인덱스로 객체를 검사해서 가져오는 메소드가 없습니다. 대신, 전체 객체를 대상으로 한번씩 반복해서 가져오는 반복자(Iterator)를 제공합니다. 반복자는 Iterator인터페이스를 구현한 객체를 말합니다. iterator()메소드를 호출하면 얻을 수 있습니다.
Set<String> set = ...;
Iterator<String> iterator = set.iterator();
다음은 Iterator인터페이스에 선언된 메소드의 예시입니다.
Iterator에서 하나의 객체를 가져올 때는 next()메소드를 사용합니다. next()메소드를 사용하기 전에 먼저 가져올 객체가 있는지 확인하는 것이 좋습니다. hasNext()메소드는 가져올 객체가 있으면 true를 리턴하고 더 이상 가져올 객체가 없으면 false를 리턴합니다. 따라서 true가 리턴될 때 next()메소드를 사용해야 합니다. 다음은 Set컬렉션에서 String 객체들을 반복해서 하나씩 가져오는 코드를 보여줍니다.
Set<String> set = ...;
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
// String객체를 하나 가져옴
String str = iterator.next();
}
또한 Iterator를 사용하지 않더라도 향상된 for문을 이용해서 전체 객체를 대상으로 반복할 수 있습니다.
Set<String> set = ...;
for(String str : set) {
}
또한 Set컬렉션에서 Iterator의 next()메소드로 가져온 객체를 제거하고 싶다면 remove()메소드를 호출하면 됩니다. Iterator의 메소드이지만, 실제 Set 컬렉션에서 객체가 제거됨을 알아야 합니다. 다음은 Set컬렉션에서 "홍길동"을 제거합니다.
while(iterator.hasNext()) {
String str = iterator.next();
if(str.equals("홍길동")) {
iterator.remove();
}
}
< Set - HashSet >
HashSet은 Set인터페이스의 구현 클래스입니다. HashSet은 객체를 저장하기 전에 먼저 객체의 HashCode()메소드를 호출해서 해시코드를 얻어냅니다. 그리고 이미 저장되어 있는 객체들의 해시코드와 비교합니다. 만약 동일한 해시코드가 있다면 다시 equals()메소드로 두 객체를 비교해서 true가 나오면 동일한 객체로 판단하고 중복 저장을 하지 않습니다.
문자열을 HashSet에 저장할 경우, 같은 문자열을 갖는 String객체는 동등한 객체로 간주되고 다른 문자열을 갖는 String객체는 다른 객체로 간주됩니다. 그 이유는 String클래스가 hashCode()와 equals()메소드를 재정의해서 같은 문자열일 경우 hashCode()의 리턴값을 같게, equals()의 리턴값은 true가 나오도록 했기 때문입니다. 다음 예제는 HashSet에 String 객체를 추가, 검색, 제거하는 방법을 보여줍니다.
package com.company.Collection;
import java.util.*;
public class HashSetExample1 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Java");
set.add("JDBC");
set.add("Servlet/JSP");
set.add("Java");
set.add("iBATIS");
int size = set.size();
System.out.println("총 객체수: " + size);
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
System.out.println("\t" + element);
}
set.remove("JDBC");
set.remove("iBATIS");
System.out.println("총 객체수: " + set.size());
iterator = set.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
System.out.println("\t" + element);
}
}
}
//총 객체수: 4
// Java
// JDBC
// Servlet/JSP
// iBATIS
//총 객체수: 2
// Java
// Servlet/JSP
다음은 사용자 정의 클래스 Member를 만들고 hashCode()와 equals()메소드를 오버라이딩하였습니다. 인스턴스가 달라도 이름과 나이가 동일하다면 동일한 객체로 간주하여 중복 저장되지 않도록 하기 위함입니다.
package com.company.Collection;
import java.util.*;
public class HashSetExample2 {
public static void main(String[] args) {
Set<Member> set = new HashSet<>();
set.add(new Member("홍길동", 30));
set.add(new Member("홍길동", 30));
System.out.println("총 객체수 : " + set.size());
}
}
//총 객체수 : 1
package com.company.Collection;
import java.util.Objects;
public class Member {
public String name;
public int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Member) {
Member member = (Member) obj;
return Objects.equals(member.name, name) && member.age == age;
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Map 컬렉션
Map컬렉션은 키(key)와 값(value)으로 구성된 Entry객체를 저장하는 구조를 가지고 있습니다. 여기서 키와 값은 모두 객체입니다. 키는 중복 저장될 수 없지만 값은 중복 저장될 수 있습니다. 만약 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 대치됩니다.
Map 컬렉션에는 HashMap, Hashtable, LinkedHashMap, Properties, TreeMap등이 있습니다. 다음은 Map컬렉션에서 공통적으로 사용 가능한 Map인터페이스의 메소드들입니다. 키로 객체들을 관리하기 때문에 키를 매개값으로 갖는 메소드가 많습니다.
객체 추가는 put()메소드를 사용하고, 키로 객체를 찾아올 때에는 get()메소드를 사용합니다. 그리고 객체 삭제는 remove()메소드를 사용합니다.
Map<String, Integer> map = ~;
map.put("홍길동", 30);
int score = map.get("홍길동");
map.remove("홍길동");
또한 저장된 전체 객체를 대상으로 하나씩 얻고 싶은 경우에는 두 가지 방법을 사용할 수 있습니다. 첫 번째는 keySet()메소드로 모든 키를 Set컬렉션으로 얻은 다음, 반복자를 통해 키를 하나씩 얻고 get()메소드를 통해 값을 얻으면 됩니다.
Map<K, V> = ~;
Set<K> keySet = map.keySet();
Iterator<K> keyIterator = keySet.iterator();
while(keyIterator.hasNext()) {
K key = keyIterator.next();
V value = map.get(key);
}
두 번째 방법은 entrySet()메소드로 모든 Map.Entry를 Set컬렉션으로 얻은 다음, 반복자를 통해 Map.Entry를 하나씩 얻고 getKey()와 getValue()메소드를 통해 키와 값은 얻으면 됩니다.
Set<Map.Entry<K, V>> entrySet = map.entrySet();
Iterator<Map.Entry<K, V>> entryIterator = entrySet.iterator();
while(entryIterator.hasNext()) {
Map.Entry<K, V> entry = entryIterator.next();
K key = entry.getkey();
V value = entry.getValue();
}
< Map - HashMap >
HashMap은 Map인터페이스를 구현한 대표적인 Map컬렉션입니다. HashMap의 키로 사용할 객체는 hashCode()와 equals()메소드를 재정의해서 동등 객체가 될 조건을 정해야 합니다. 동등 객체, 즉 동일한 키가 될 조건은 hashCode()의 리턴값이 같아야 하고, equals()메소드가 true를 리턴해야 합니다.
주로 키 타입은 String을 많이 쓰는데, String은 문자열이 같을 경우 동등 객체가 될 수 있도록 hashCode()와 equals()메소드가 재정의되어 있습니다. HashMap을 생성하기 위해서는 키 타입과 값 타입을 파라미터로 주고 기본 생성자를 호출하면 됩니다.
또한 중요한 속성중 하나가 키와 값의 타입은 기본타입은 안되고 클래스 및 인터페이스 타입만 가능하다는 것입니다.
package com.company.Collection;
import java.util.*;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("신용권", 85);
map.put("홍길동", 90);
map.put("동장군", 80);
map.put("홍길동", 95);
System.out.println("총 Entry 수:" + map.size());
System.out.println("\t홍길동 : " + map.get("홍길동"));
System.out.println();
Set<String> keySet = map.keySet();
Iterator<String> keyIterator = keySet.iterator();
while(keyIterator.hasNext()) {
String key = keyIterator.next();
Integer value = map.get(key);
System.out.println("\t" + key + " : " + value);
}
System.out.println();
map.remove("홍길동");
System.out.println("총 Entry 수: " + map.size());
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> entryIterator = entrySet.iterator();
while(entryIterator.hasNext()) {
Map.Entry<String, Integer> entry = entryIterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("\t" + key + " : " + value);
}
System.out.println();
map.clear();
System.out.println("총 Entry 수: " + map.size());
}
}
//총 Entry 수:3
// 홍길동 : 95
//
// 홍길동 : 95
// 신용권 : 85
// 동장군 : 80
//
//총 Entry 수: 2
// 신용권 : 85
// 동장군 : 80
//
//총 Entry 수: 0
또한 아래와 같이 사용자 정의 객체인 Student를 키로하고 점수를 저장하는 HashMap사용방법을 보여줍니다.
package com.company.Collection;
import java.util.HashMap;
import java.util.Map;
public class HashMapExample2 {
public static void main(String[] args) {
Map<Student, Integer> map = new HashMap<>();
map.put(new Student(1, "홍길동"), 95);
map.put(new Student(1, "홍길동"), 95);
System.out.println("총 Entry 수: " + map.size());
}
}
//총 Entry 수: 1
'School > Java Programming' 카테고리의 다른 글
Java Programming - Thread notify-wait() (0) | 2022.05.19 |
---|---|
Java Programming - Thread basic (0) | 2022.05.19 |
Java Programming - Basic API Class (0) | 2022.03.24 |
Java Programming - Exception (0) | 2022.03.17 |
Java Programming - 중첩 클래스 & 중첩 인터페이스 (0) | 2022.03.17 |