JAVA - Collection
1. Enum
열거형(enum)은 서로 연관된 상수들의 집합을 의미합니다.
Collection 프레임워크 중 하나로 열거형을 사용하여 상수를 정의하면 상수명 중복과 타입 안정성 문제를 해결할 수 있으며, 코드를 단순하고 가독성이 좋게 만들 수 있습니다. 또한, switch문에서도 사용이 가능합니다.
리턴 타입 메소드(매개변수) 설명
String | name() | 열거 객체가 가지고 있는 문자열을 리턴하며, 리턴되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일합니다. |
int | ordinal() | 열거 객체의 순번(0부터 시작)을 리턴합니다. |
int | compareTo(비교값) | 주어진 매개값과 비교해서 순번 차이를 리턴합니다. |
열거 타입 | valueOf(String name) | 주어진 문자열의 열거 객체를 리턴합니다. |
열거 배열 | values() | 모든 열거 객체들을 배열로 리턴합니다. |
Generic - 똑같은 로직이지만 멤버 타입만 다를 때 사용
class Basket<T> { // Generic 변수 인스턴스화 할 때 적어주어야
private T item;
public Basket(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
Basket<String> basket1 = new Basket<>(item: "apple");
// 이런 식으로 어떤 타입인지 명시
Java의 Generic 문법
Java에서 제공하는 Generic은 클래스, 인터페이스, 메소드의 선언부에 타입 변수를 사용하여 다양한 타입의 객체를 다루는 것을 가능하게 합니다. Generic을 사용하면 타입 안정성(type safety)을 제공하며, 코드의 재사용성과 가독성을 높일 수 있습니다.
Generic 클래스
Generic 클래스는 클래스의 선언부에 타입 변수를 사용하여 클래스 내부에서 사용되는 타입을 지정합니다. 타입 변수는 클래스의 멤버 변수, 메소드 매개 변수, 반환 값 등 모든 부분에서 사용될 수 있습니다.
public class Box<T> {
private T contents;
public void setContents(T contents) {
this.contents = contents;
}
public T getContents() {
return contents;
}
}
Box 클래스에서 T는 타입 변수로서, Box 클래스를 사용하는 시점에 실제 타입으로 치환됩니다. 예를 들어, Box<String>은 Box 클래스의 T를 String으로 치환한 것입니다.
class Basket<T> {
private T item;
public Basket(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
Basket<String> basket1 = new Basket<>(item: "apple");
위와 같은 예제에서 Basket 클래스는 Generic 클래스로, 타입 변수 T를 사용하여 클래스 내부에서 사용되는 타입을 지정합니다. T는 클래스의 멤버 변수인 item의 타입으로 사용됩니다. Basket 클래스를 사용할 때는, Basket<String>과 같이 T를 실제 타입(String)으로 치환하여 사용합니다. 이렇게 하면, 컴파일러가 타입 안정성을 보장하며, 잘못된 타입의 객체가 들어올 가능성을 줄일 수 있습니다.
Generic 클래스의 주의점
제네릭 클래스에서 타입 매개변수를 임의의 타입으로 사용할 수 있다고 하였습니다. 이 때, 아래와 같이 클래스 변수에는 타입 매개변수를 사용할 수 없습니다.
class Basket<T> {
private T item1; // O
static T item2; // X
}
클래스 변수에 타입 매개변수를 사용할 수 없는 이유는 클래스 변수의 특성을 생각해보면 충분히 이해할 수 있습니다. 클래스 변수는 모든 인스턴스가 공유하는 변수입니다. 만약, 클래스 변수에 타입 매개변수를 사용할 수 있다면 클래스 변수의 타입이 인스턴스 별로 달라지게 됩니다.
Generic 메소드
Generic 메소드는 메소드의 선언부에 타입 변수를 사용하여 메소드 내부에서 사용되는 타입을 지정합니다. 메소드의 반환 값, 매개 변수 등에서도 타입 변수를 사용할 수 있으며, 메소드 호출 시에는 타입 변수를 실제 타입으로 치환해야 합니다.
public static <T> T getLast(List<T> list) {
return list.get(list.size() - 1);
}
위의 예제에서 T는 메소드의 타입 변수로서, List<T>에서 T를 치환한 실제 타입으로 사용됩니다. 이 경우, 메소드 호출 시에는 타입 변수 T를 실제 타입으로 지정해야 합니다.
Generic 제한자
Generic 클래스나 메소드에서 사용되는 타입 변수의 범위를 제한할 수 있습니다. 이를 위해서는 extends 키워드를 사용하여 타입 변수의 상한을 지정합니다.
public class FruitBox<T extends Fruit> {
private List<T> fruits = new ArrayList<>();
public void addFruit(T fruit) {
fruits.add(fruit);
}
public T getFruit(int index) {
return fruits.get(index);
}
}
위의 예제에서 T는 Fruit 클래스나 Fruit 클래스를 상속하는 클래스로 제한됩니다. 이렇게 제한된 타입 변수를 사용하면, 컴파일러가 타입 안정성을 보장하고, 잘못된 타입의 객체가 들어올 가능성을 줄일 수 있습니다.
이와 같이 특정 클래스를 상속받은 클래스만 타입으로 지정할 수 있도록 제한하는 것뿐만 아니라, 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한할 수도 있습니다. 이 경우에도 동일하게 extends 키워드를 사용합니다.
interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }
class Basket<T extends Plant> {
private T item;
...
}
class Main {
public static void main(String[] args) {
// 인스턴스화
Basket<Flower> flowerBasket = new Basket<>();
Basket<Rose> roseBasket = new Basket<>();
}
}
만약, 특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한하려면 아래와 같이 &를 사용하여 코드를 작성해주면 됩니다.
다만, 이러한 경우에는 클래스를 인터페이스보다 앞에 위치시켜야 합니다. 아래 예제의 (1)을 참고하세요.
interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }
class Basket<T extends Flower & Plant> { // (1)
private T item;
...
}
class Main {
public static void main(String[] args) {
// 인스턴스화
Basket<Flower> flowerBasket = new Basket<>();
Basket<Rose> roseBasket = new Basket<>();
}
}
ArrayList
ArrayList Method
기능 리턴 타입 메서드 설명
객체 추가 | void | add(int index, Object element) | 주어진 인덱스에 객체를 추가 |
boolean | addAll(int index, Collection c) | 주어진 인덱스에 컬렉션을 추가 | |
Object | set(int index, Object element) | 주어진 위치에 객체를 저장 | |
객체 검색 | Object | get(int index) | 주어진 인덱스에 저장된 객체를 반환 |
int | indexOf(Object o) / lastIndexOf(Object o) | 순방향 / 역방향으로 탐색하여 주어진 객체의 위치를 반환 | |
ListIterator | listIterator() / listIterator(int index) | List의 객체를 탐색할 수 있는ListIterator 반환 / 주어진 index부터 탐색할 수 있는 ListIterator 반환 | |
List | subList(int fromIndex, int toIndex) | fromIndex부터 toIndex에 있는 객체를 반환 | |
객체 삭제 | Object | remove(int index) | 주어진 인덱스에 저장된 객체를 삭제하고 삭제된 객체를 반환 |
boolean | remove(Object o) | 주어진 객체를 삭제 | |
객체 정렬 | void | sort(Comparator c) | 주어진 비교자(comparator)로 List를 정렬 |
Set (중복 허용 X, 저장순서 X)
Java의 Set
Java에서 Set은 중복된 값을 허용하지 않는 자료구조입니다. 이는 Set 내부에서 값의 유일성을 보장하기 위한 것입니다. Set은 내부적으로 해시 테이블을 사용하여 값의 위치를 저장합니다. 이 때, 값의 위치는 해시 함수를 통해 결정됩니다. 따라서, 값이 추가되는 순서와 값의 위치는 일치하지 않을 수 있습니다. 이는 Set이 값의 유일성과 탐색 속도를 보장하기 위한 특성으로 볼 수 있습니다.
Set 이 중복값을 허용하지 않고 순서를 보장하지 않는 이유
Java의 Set은 중복된 값을 허용하지 않고 순서를 보장하지 않는 이유는 값의 유일성을 보장하기 위함입니다. Set은 내부적으로 해시 테이블을 사용하여 값의 위치를 저장하고, 값의 위치는 해시 함수를 통해 결정됩니다. 따라서, 값이 추가되는 순서와 값의 위치는 일치하지 않을 수 있습니다. 이는 Set이 값의 유일성과 탐색 속도를 보장하기 위한 특성으로 볼 수 있습니다.
또한, Set은 중복값을 허용하지 않기 때문에 데이터의 중복 검사에서 많이 활용됩니다. 또한, 값의 탐색이 빈번한 경우에 유용하게 사용될 수 있는데, 이는 해시 함수를 사용하기 때문에 값을 검색하는 과정에서 빠른 속도를 보장합니다.
하지만, 저장 순서가 보장되지 않기 때문에 값을 순서대로 처리해야 하는 경우에는 TreeSet이나 LinkedHashSet 등의 자료구조를 사용하는 것이 더 적합합니다. TreeSet은 이진 검색 트리를 사용하여 값의 위치를 저장하고, 값의 순서를 보장합니다. LinkedHashSet은 해시 테이블을 사용하여 값의 위치를 저장하고, 값의 순서를 보장합니다.
Set은 값의 유일성과 탐색 속도를 보장하기 위한 자료구조로 활용됩니다. 하지만, Set은 값의 위치를 보장하지 않기 때문에 순서에 따라 처리해야 하는 경우에는 다른 자료구조를 사용하는 것이 더 적합합니다. 또한, Set은 내부적으로 해시 함수를 사용하기 때문에 값의 해시 충돌이 발생할 가능성이 있습니다. 이 경우, 충돌을 해결하기 위한 추가적인 연산이 필요하게 되어 Set의 성능이 저하될 수 있습니다.
따라서, Set을 사용할 때는 값의 유일성과 탐색 속도를 중점적으로 고려하여 사용해야 합니다.
Set 인터페이스
Java에서 Set은 java.util 패키지 내에 있는 Set 인터페이스를 구현한 클래스들로 사용됩니다. Set 인터페이스는 다음과 같은 메소드를 제공합니다.
- boolean add(E e) : Set에 값을 추가합니다.
- boolean remove(Object o) : Set에서 값을 제거합니다.
- boolean contains(Object o) : Set에 값을 포함하고 있는지 확인합니다.
- int size() : Set에 저장된 값의 개수를 반환합니다.
- boolean isEmpty() : Set이 비어있는지 확인합니다.
- void clear() : Set의 모든 값을 제거합니다.
그리고, Set 인터페이스를 구현한 클래스들은 다음과 같은 특징을 가지고 있습니다.
- HashSet : 해시 테이블을 사용하여 값의 위치를 저장합니다. 값의 순서를 보장하지 않습니다.
- TreeSet : 이진 검색 트리를 사용하여 값의 위치를 저장합니다. 값의 순서를 보장합니다.
- LinkedHashSet : 해시 테이블을 사용하여 값의 위치를 저장합니다. 값의 순서를 보장합니다.
Set 사용 방법
다음은 Set을 사용하는 예제 코드입니다.
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("cherry");
set.add("apple"); // 중복된 값은 추가되지 않음
System.out.println(set); // [banana, cherry, apple]
System.out.println(set.contains("banana")); // true
System.out.println(set.size()); // 3
set.remove("banana");
System.out.println(set); // [cherry, apple]
System.out.println(set.isEmpty()); // false
set.clear();
System.out.println(set); // []
System.out.println(set.isEmpty()); // true
}
}
위 예제에서는 HashSet을 사용하여 Set을 구현하였습니다. HashSet은 해시 테이블을 사용하기 때문에 값의 추가, 삭제, 탐색 속도가 빠릅니다. 이는 값의 위치를 검색하는 과정에서 해시 함수를 사용하기 때문입니다. 따라서, Set은 값의 탐색이 빈번한 경우에 유용하게 사용될 수 있습니다.
하지만, 저장 순서가 보장되지 않기 때문에 값을 순서대로 처리해야 하는 경우에는 TreeSet이나 LinkedHashSet 등의 자료구조를 사용하는 것이 더 적합합니다.
Set의 활용
Set은 중복된 값을 허용하지 않기 때문에 데이터의 중복 검사에서 많이 활용됩니다. 또한, Set은 값의 탐색이 빈번한 경우에 유용하게 사용될 수 있습니다. 예를 들어, 데이터베이스에서 유니크한 값을 조회할 때 Set을 사용하여 중복을 제거할 수 있습니다.
import java.sql.*;
import java.util.HashSet;
import java.util.Set;
public class UniqueValueExample {
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "password";
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT DISTINCT name FROM users");
Set<String> uniqueNames = new HashSet<>();
while (rs.next()) {
uniqueNames.add(rs.getString("name"));
}
System.out.println(uniqueNames);
rs.close();
stmt.close();
conn.close();
}
}
위 예제에서는 데이터베이스에서 유니크한 이름을 조회하여 Set에 저장하였습니다. 이렇게 하면, 중복된 이름이 제거되고 유니크한 이름만 Set에 저장됩니다. 따라서, Set은 데이터의 중복 검사에서 많이 활용됩니다.
Set의 한계
Set은 값의 유일성과 탐색 속도를 보장하기 위한 자료구조입니다. 하지만, Set은 값의 위치를 보장하지 않기 때문에 순서에 따라 처리해야 하는 경우에는 다른 자료구조를 사용하는 것이 더 적합합니다. 또한, Set은 내부적으로 해시 함수를 사용하기 때문에 값의 해시 충돌이 발생할 가능성이 있습니다. 이 경우, 충돌을 해결하기 위한 추가적인 연산이 필요하게 되어 Set의 성능이 저하될 수 있습니다.
따라서, Set을 사용할 때는 값의 유일성과 탐색 속도를 중점적으로 고려하여 사용해야 합니다.
결론
Java의 Set은 중복된 값을 허용하지 않는 자료구조입니다. Set은 내부적으로 해시 테이블을 사용하여 값의 위치를 저장합니다. 이 때, 값의 위치는 해시 함수를 통해 결정됩니다. 따라서, 값이 추가되는 순서와 값의 위치는 일치하지 않을 수 있습니다. Set은 값의 유일성과 탐색 속도를 보장하기 위한 자료구조로 활용됩니다. 하지만, Set은 값의 위치를 보장하지 않기 때문에 순서에 따라 처리해야 하는 경우에는 다른 자료구조를 사용하는 것이 더 적합합니다.
TreeSet
Java의 TreeSet은 이진 검색 트리를 사용하여 값의 위치를 저장하는 Set 자료구조입니다. 이진 검색 트리는 정렬된 상태를 유지하기 때문에 값의 위치를 보장합니다. 따라서, TreeSet은 값의 순서를 보장하면서 Set의 특성을 유지할 수 있습니다. 값의 추가, 삭제, 탐색 속도는 HashSet에 비해 느립니다. 하지만, 값의 검색이나 범위 내 값의 검색 등의 작업에서 빠른 속도를 보장합니다. 따라서, TreeSet은 값의 순서가 중요한 경우에 유용하게 사용될 수 있습니다.
자동으로 정렬이 되어있다. 이진 탐색 트리 작은 수부터 큰수로 오름차순
Map
Java의 Map
Java에서 Map은 Key-Value 쌍으로 이루어진 자료구조입니다. Map은 내부적으로 해시 테이블을 사용하여 Key-Value 쌍을 저장하며, 이는 값을 검색하는 데 있어서 O(1)의 시간 복잡도를 보장합니다. Map은 내부적으로 Entry 객체를 사용하여 Key-Value 쌍을 저장하며, 이 때 Key는 중복될 수 없으며, Value는 중복될 수 있습니다.
해싱을 사용하여 데이터를 저장하는 Map 구현 클래스
고정된 길이의 데이터로 바꾸는 것이 hashing
Map 인터페이스
Java에서 제공하는 Map 인터페이스는 Key-Value 쌍을 저장하는 자료구조를 추상화한 것입니다. Map 인터페이스는 다음과 같은 메소드를 제공합니다.
리턴 타입 메소드(매개변수) 설명
int | size() | Map에 저장된 Key-Value 쌍의 개수를 리턴합니다. |
boolean | isEmpty() | Map이 비어있는지 여부를 리턴합니다. |
boolean | containsKey(Object key) | 주어진 Key가 Map에 존재하는지 여부를 리턴합니다. |
boolean | containsValue(Object value) | 주어진 Value가 Map에 존재하는지 여부를 리턴합니다. |
V | get(Object key) | 주어진 Key에 해당하는 Value를 리턴합니다. |
V | put(K key, V value) | 주어진 Key-Value 쌍을 Map에 저장합니다. |
V | remove(Object key) | 주어진 Key에 해당하는 Key-Value 쌍을 Map에서 제거합니다. |
void | putAll(Map<? extends K, ? extends V> m) | 주어진 Map의 모든 Key-Value 쌍을 현재 Map에 추가합니다. |
void | clear() | Map의 모든 Key-Value 쌍을 제거합니다. |
Set<K> | keySet() | Map의 모든 Key를 Set으로 리턴합니다. |
Collection<V> | values() | Map의 모든 Value를 Collection으로 리턴합니다. |
Set<Map.Entry<K, V>> | entrySet() | Map의 모든 Key-Value 쌍을 Entry 객체로 묶어 Set으로 리턴합니다. |
HashMap 클래스
Java에서 가장 많이 사용되는 Map 구현체는 HashMap 클래스입니다. HashMap은 내부적으로 해시 테이블을 사용하여 Key-Value 쌍을 저장하며, 이는 값을 검색하는 데 있어서 O(1)의 시간 복잡도를 보장합니다. HashMap은 Key와 Value에 null 값을 허용하며, 동기화를 지원하지 않습니다.
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
System.out.println(map.get("apple")); // 1
System.out.println(map.containsKey("banana")); // true
System.out.println(map.containsValue(3)); // true
System.out.println(map.size()); // 3
map.remove("cherry");
System.out.println(map.size()); // 2
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
// apple : 1
// banana : 2
위의 예제에서는 HashMap 클래스를 사용하여 Key-Value 쌍을 저장하였습니다. put() 메소드를 사용하여 Key-Value 쌍을 추가하였으며, get() 메소드를 사용하여 Key에 해당하는 Value를 가져와 출력하였습니다. containsKey() 메소드와 containsValue() 메소드를 사용하여 Key나 Value가 Map에 존재하는지 여부를 확인하였으며, remove() 메소드를 사용하여 Key에 해당하는 Key-Value 쌍을 제거하였습니다. size() 메소드를 사용하여 Map에 저장된 Key-Value 쌍의 개수를 확인하였으며, entrySet() 메소드를 사용하여 모든 Key-Value 쌍을 출력하였습니다.
TreeMap 클래스
Java에서 TreeMap 클래스는 Key-Value 쌍을 저장하는 Map 구현체 중 하나입니다. TreeMap은 내부적으로 이진 검색 트리(binary search tree)를 사용하여 Key-Value 쌍을 저장하며, 이는 값을 검색하는 데 있어서 O(log N)의 시간 복잡도를 보장합니다. TreeMap은 Key를 기준으로 정렬되며, Key에 해당하는 Value를 검색하는 데 있어서 빠른 속도를 보장합니다.
Map<String, Integer> map = new TreeMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
System.out.println(map.get("apple")); // 1
System.out.println(map.containsKey("banana")); // true
System.out.println(map.containsValue(3)); // true
System.out.println(map.size()); // 3
map.remove("cherry");
System.out.println(map.size()); // 2
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
// apple : 1
// banana : 2
위의 예제에서는 TreeMap 클래스를 사용하여 Key-Value 쌍을 저장하였습니다. HashMap과 사용 방법은 동일하며, Key가 기준으로 정렬된다는 차이점이 있습니다. TreeMap은 Key의 자료형에 따라서 정렬 방식이 달라질 수 있으며, Comparator를 이용하여 Key의 정렬 방식을 변경할 수 있습니다.
ConcurrentHashMap 클래스
Java에서 ConcurrentHashMap 클래스는 동시성을 지원하는 Map 구현체입니다. ConcurrentHashMap은 내부적으로 세그먼트(segment)를 사용하여 Map을 분할하며, 이를 통해 동시성을 보장합니다. ConcurrentHashMap은 HashMap과 동일한 기능을 제공하며, 동시성을 보장하기 때문에 멀티 스레드 환경에서 사용하기 적합합니다.
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
System.out.println(map.get("apple")); // 1
System.out.println(map.containsKey("banana")); // true
System.out.println(map.containsValue(3)); // true
System.out.println(map.size()); // 3
map.remove("cherry");
System.out.println(map.size()); // 2
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
// apple : 1
// banana : 2
위의 예제에서는 ConcurrentHashMap 클래스를 사용하여 동시성을 보장하는 Map을 생성하였습니다. ConcurrentHashMap은 HashMap과 사용 방법이 동일하며, 멀티 스레드 환경에서 안전하게 사용할 수 있습니다.
결론
Java에서 제공하는 Map은 Key-Value 쌍을 저장하는 자료구조로, HashMap, TreeMap, ConcurrentHashMap 등 다양한 구현체가 제공됩니다. 이들 구현체는 각자의 특징을 가지고 있으며, 사용하는 상황에 따라 적절한 구현체를 선택하여 사용해야 합니다. Map은 많은 양의 데이터를 검색하는 데 있어서 높은 성능을 보장하며, Key-Value 쌍을 저장하는 데 있어서 유용합니다.
'개발일지' 카테고리의 다른 글
[TIL] 자료구조와 알고리즘 재귀에 대하여 feat.Java (0) | 2023.03.21 |
---|---|
[TIL] Java 문자열 비교 하는 방법 (0) | 2023.03.15 |
[KPT] Section 1을 마치며 하는 회고 feat. 코드스테이츠 백엔드 (0) | 2023.03.13 |
[TIL] Java 객체지향프로그래밍 - 생성자와 생성자 오버로딩 (0) | 2023.03.01 |
[TIL] JAVA 기초 - 반복문 (0) | 2023.02.22 |