15.4 Map 컬렉션

키(key)와 값(value)으로 구성된 엔트리(Entry) 객체를 저장.

키는 중복 저장 X, 값은 중복 저장 O

기존의 저장된 키와 동일한 키로 값을 저장하면 기존 값은 없어지고 새로운 값으로 대치됨.

 

기능 메소드 설명
객체 추가 V put(K key, V value) 주어진 키와 값을 추가, 저장 되면 값을 리턴
객체 검색 boolean containsKey(Object key) 주어진 키가 있는지 확인
boolean containsValue(Object value) 주어진 값이 있는지 확인
Set<Map.Entry<K, V>> entrySet() 키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아 리턴
V get(Object key) 주어진 키의 값을 리턴
boolean isEmpty() 컬렉션이 비어있는지 여부
Set<K> keySet() 모든 키를 Set 객체에 담아 리턴
int size() 저장된 키의 총 수를 리턴
Collection<V> values() 저장된 모든 값을 Collection에 담아 리턴
객체 삭제 void clear() 모든 Map.Entry(키와 값)를 삭제
V remove(Object key) 주어진 키와 일치하는 Map.Entry 삭제.
삭제가 되면 값을 리턴

 

 

● HashMap

key로 사용할 객체를 hashCode() 메소드 리턴값이 같고 equals() 메소드가 true를 리턴할 경우, 동일 키로 보고 중복 저장을 허용하지 않음.

// HashMap 컬렉션 생성 방법
Map<K, V> map = new HashMap<K, V>();
Map<K, V> map = new HashMap<>();  // 앞의 파라미터 타입과 동일할 경우 생략 가능

- K는 Key타입, V는 Value타입

 

사용 예시)

import lombok.*;
@AllArgsConstructor
@Getter
@Setter
@EqualsAndHashCode  // 롬복은 클래스의 필드 값이 서로 같으면 같은 HashCode와 equals 값을 리턴
public class Member {	// Member 클래스
	public String name;
	public int age;
}

import java.util.*;
import java.util.Map.Entry;

public class HashMapExample {

	public static void main(String[] args) {
		
		// 컬렉션 생성
		Map<String, Integer> map = new HashMap<>();
		
		// 엔트리 저장
		map.put("홍길동", 85);  // 85는 Integer. int로 자동 언박싱이 일어남
		map.put("감자바", 95);
		map.put("홍자바", 74);
		map.put("추자바", 82);
		map.put(new String("홍길동"), 90);  // 키가 같기 때문에 제일 마지막에 저장한 값 저장
						// String은 hashCode()를 문자열에 의해 재정의되어있기 때문에 키값이 동등하게 나옴
		// 저장된 엔트리 수
		System.out.println("총 Entry 수: " + map.size()); // 4
		System.out.println();
		
		// key로 값을 얻기
		//System.out.println(map.get("홍길동"));
		String key = "홍길동";
		int value = map.get(key);  // Integer타입이 int로 자동 언박싱이 일어남
		System.out.println(key + ":" + value);
		System.out.println();
		
		// 엔트리를 하나씩 가져와 키와 값을 출력
		for(Entry<String, Integer> entry : map.entrySet()) {
			String k = entry.getKey();
			int v = entry.getValue();
			System.out.println(k + ":" + v);
		}
		System.out.println();
		
		for(String k : map.keySet()) {
			int v = map.get(k);
			System.out.println(k + ":" + v);
		}
		System.out.println();
		
		// 향상된 for문은 '삭제'작업시 문제가 될 수 있다.
		/*for(Entry<String, Integer> entry : map.entrySet()) {
			String k = entry.getKey();
			int v = entry.getValue();
			System.out.println(k + ":" + v);
			map.remove(k);
		}
		System.out.println();*/
		
		// Iterator로 사용하기
        // 키를 반복하기 위해 반복자를 얻는 것
		Iterator<Entry<String, Integer>> iterator = map.entrySet().iterator();
		while(iterator.hasNext()) {
			Entry<String, Integer> entry = iterator.next();
			String k = entry.getKey();
			int v = entry.getValue();
			if(k.equals("홍길동")) {
				iterator.remove();
			} else {
				System.out.println(k + ":" + v);				
			}
		}
	}
}

 

● Hashtable

HashMap과 동일한 내부 구조를 가짐.

차이점은 동기화된(synchronized) 메소드로 구성되어 멀티 스레드가 동시에 Hashtable의 메소드들을 실행할 수 없다는 것.

Map<K, V> map = new Hashtable<K, V>();
Map<K, V> map = new Hashtable<>();

 

→ 앞서 배운 Vector부분과 비슷한 예시

 

 

● Properties

Properties는 Hashtable의 자식 클래스.

때문에 Hashtable의 특징을 그대로 가지고 있음.

→ 키와 값을 String 타입으로 제한한 컬렉션이다.

주로 확장자가 .properties인 프로퍼티 파일을 읽을 때 사용한다.

 

프로퍼티 파일은 키와 값이 '=' 기호로 연결되어 있는 텍스트 파일

일반 텍스트 파일과는 다르게 ISO 8859-1문자셋으로 저장, 한글일 경우 \u+유니코드로 표현되어 저장.

// database.properties 파일 텍스트 내용
driver=oracle.jdbc.OracleDriver
username=\uD64D\uAE38\uB3D9
password=12345

// 프로퍼티 사용 예제
import java.io.IOException;
import java.util.Properties;

public class PropertiesExample {
	public static void main(String[] args) {
		// Properties 컬렉션 생성
		Properties prop = new Properties();

		// 이 파일과 동일한 ClassPath에 있는 database.properties 파일 로드
		try {
			prop.load(PropertiesExample.class.getResourceAsStream("database.properties"));
		} catch (IOException e) {
		}
		
		String userName = prop.getProperty("username");
		String password = prop.getProperty("password");
		System.out.println(userName + ":" + password);
	}
}

출력 → 홍길동:123245

 

 

15.5 검색 기능을 강화시킨 컬렉션

컬렉션 프레임워크는 검색 기능을 강화시킨 TreeSet과 TreeMap을 제공함.

TreeSet → Set 컬렉션

TreeMap → Map 컬렉션

 

● TreeSet

이진 트리(binary tree)를 기반으로 한 Set 컬렉션.

여러 개의 노드(node)가 트리 형태로 연결된 구조.

루트 노드(root node)라고 불리는 하나의 노드에서 시작하여 각 노드에 최대 2개의 노드를 연결할 수 있는 구조를 가지고 있다.

 

 

 

부모 노드보다 작은 값을 가지는 노드는 왼쪽 자식으로, 큰 값을 가지는 노드는 오른쪽 자식으로 배치하여 데이터의 추가나 삭제 시 트리가 한쪽으로 치우쳐지지 않도록 균형을 맞춘다.

 

 

 

 

 

TreeSet<E> treeSet = new TreeSet<E>();
TreeSet<E> treeSet = new TreeSet<>();

★ 여기서 E는 비교 가능한(comparable) 객체만 올 수 있다!

Set 타입 변수에 대입해도 되지만 TreeSet 타입으로 대입한 이유는?

→ 검색 관련 메소드가 TreeSet에만 정의되어 있기 때문.

 

리턴 타입 메소드 설명
E first() 제일 낮은 객체를 리턴
E last() 제일 높은 객체를 리턴
E lower(E e) 주어진 객체보다 바로 아래 객체 리턴
E higher(E e) 주어진 객체보다 바로 위 객체 리턴
E floor(E e) 주어진 객체와 동등한 객체가 있으면 리턴,
만약 없다면 주어진 객체의 바로 아래의 객체를 리턴.
E ceiling(E e) 주어진 객체와 동등한 객체가 있으면 리턴,
만약 없다면 주어진 객체의 바로 위의 객체를 리턴.
E pollFirst() 제일 낮은 객체를 꺼내오고 컬렉션에서 제거함
E pollLast() 제일 높은 객체를 꺼내오고 컬렉션에서 제거함
Iterator<E> descendingIterator() 내림차순으로 정렬된 Iterator 리턴
NavigableSet<E> descendingSet() 내림차순으로 정렬된 NavigableSet 리턴
NavigableSet<E> headSet(
E toElement,
boolean inclusive
)
주어진 객체보다 낮은 객체들을 NavigableSet으로 리턴,
주어진 객체 포함 여부는 두 번째 매개값에 따라 달라짐
NavigableSet<E> tailSet(
E fromElement,
boolean inclusive
)
주어진 객체보다 높은 객체들을 NavigableSet으로 리턴,
주어진 객체 포함 여부는 두 번째 매개값에 따라 달라짐
NavigableSet<E> subSet(
E fromElement,
boolean tolnclusive
)
시작과 끝으로 주어진 객체 사이의 객체들을 NavigableSet으로 리턴.
시작과 끝 객체의 포함 여부는 두 번째, 네 번째 매개값에 따라 달라짐.

 

예시)

import java.util.TreeSet;

public class TreeSetExample {
	public static void main(String[] args) {
		// 컬렉션 생성
		TreeSet<Integer> scores = new TreeSet<>();
		
		// 객체 저장
		scores.add(87);
		scores.add(56);
		scores.add(86);
		scores.add(23);
		scores.add(66);
		scores.add(81);
		scores.add(96);
		
		// 하나씩 객체를 오름차순으로 가져오기
		for(int score : scores) {
			System.out.print(score + " ");
		}
		System.out.println();
		
		// TreeSet이 가지고 있는 메소드
		System.out.println("가장 낮은 점수: " + scores.first());
		System.out.println("가장 낮은 점수: " + scores.last());
		System.out.println("90점 바로 위의 점수: " + scores.lower(86));
		System.out.println("90점 바로 아래 점수: " + scores.higher(86));
		System.out.println("90이거나 바로 아래 점수: " + scores.floor(86));
		System.out.println("90이거나 바로 아래 점수: " + scores.ceiling(86));
		System.out.println();
		
		// 하나씩 객체를 오름차순으로 가져오기
		for(int score : scores) {
			System.out.print(score + " ");
		}
		System.out.println();
		
		// 하나씩 객체를 내림차순으로 가져오기
		for(int score : scores.descendingSet()) {
			System.out.print(score + " ");
		}
		System.out.println();
		
		// 범위 검색(80 <= )
		for(int score : scores.tailSet(80, true)) {
			System.out.print(score + " ");
		}
		System.out.println();
		
		// 범위 검색(80 <= score < 90)
		for(int score : scores.subSet(80, true, 90, false)) {
			System.out.print(score + " ");
		}
	}
}

 

 

● TreeMap

이진 트리를 기반으로 한 Map 컬렉션.

TreeSet과의 차이점 → 키와 값이 저장된 Entry를 저장한다.

키를 기준으로 자동 정렬된다.

부모 키 값과 비교해서 낮은 것은 왼쪽, 높은 것은 오른쪽 자식 노드에 Entry 객체를 저장.

TreeMap<K, V> treeMap = new TreeMap<K, V>();
TreeMap<K, V> treeMap = new TreeMap<>();

★ 여기서 K는 비교 가능한(comparable) 객체만 올 수 있다!

 

Map 타입 변수에 대입해도 되지만 TreeMap 타입으로 대입한 이유는 검색 관련 메소드가 TreeMap에만 정의되어 있기 때문이다.

리턴 타입 메소드 설명
Map.Entry<K,V> firstEntry() 제일 낮은 Map.Entry를 리턴
Map.Entry<K,V> lastEntry() 제일 높은 Map.Entry를 리턴
Map.Entry<K,V> lowerEntry(K key) 주어진 키보다 바로 아래 Map.Entry를 리턴
Map.Entry<K,V> higherEntry(K key) 주어진 키보다 바로 위 Map.Entry를 리턴
Map.Entry<K,V> floorEntry(K key) 주어진 키와 동등한 키가 있으면 해당 Map.Entry를 리턴,
없으면 바로 아래의 Map.Entry를 리턴.
Map.Entry<K,V> ceilingEntry(K key) 주어진 키와 동등한 키가 있으면 해당 Map.Entry를 리턴,
없으면 바로 위의 Map.Entry를 리턴.
Map.Entry<K,V> pollFirstEntry() 제일 낮은 Map.Entry를 꺼내오고 컬렉션에서 제거
Map.Entry<K,V> pollLastEntry() 제일 높은 Map.Entry를 꺼내오고 컬렉션에서 제거
NavigableSet<K> desendingKeySet() 내림차순으로 정렬된 키의 NavigableMap을 리턴.
NavigableMap<K,V> descendingMap() 내림차순으로 정렬된 Map.Entry의 NavigableMap을 리턴.
NavigableMap<K,V> headMap(
K toKey,
boolean inclusive
)
주어진 키보다 낮은 Map.Entry들을 NavigableMap으로 리턴.
주어진 객체 포함 여부는 두 번째 매개값에 따라 달라짐.
NavigableMap<K,V> headMap(
K fromKey,
boolean inclusive
)
주어진 키보다 높은 Map.Entry들을 NavigableMap으로 리턴.
주어진 객체 포함 여부는 두 번째 매개값에 따라 달라짐.
NavigableMap<K,V> subMap(
K fromKey,
boolean fromInclusive,
K toKey,
boolean toInclusive
)
시작과 끝으로 주어진 키 사이의 Map.Entry들을 NavigableMap 컬렉션으로 반환.
시작과 끝 키의 Map.Entry 포함 여부는 두 번째, 네 번째 매개값에 따라 달라짐.

 

예시)

import java.util.*;
import java.util.Map.Entry;

public class TreeMapExample {
	public static void main(String[] args) {
		// TreeMap 컬렉션 생성
		TreeMap<String, Integer> treeMap = new TreeMap<>();
		
		treeMap.put("apple", 10);
		treeMap.put("forever", 60);
		treeMap.put("description", 40);
		treeMap.put("ever", 50);
		treeMap.put("zoo", 80);
		treeMap.put("base", 20);
		treeMap.put("guess", 70);
		treeMap.put("monkey", 60);
		
		// 정렬된 엔트리를 하나씩 가져오기
		Set<Entry<String, Integer>> entrySet = treeMap.entrySet();
		for(Entry<String, Integer> entry : entrySet) {
			System.out.println(entry.getKey() + " - " + entry.getValue());
		}
		System.out.println();
		
		// 특정 키에 대한 값 가져오기
		Entry<String, Integer> entry = null;
		entry = treeMap.firstEntry();
		System.out.println("제일 앞 단어: " + entry.getKey() + " - " + entry.getValue());
		entry = treeMap.lastEntry();
		System.out.println("제일 뒤 단어: " + entry.getKey() + " - " + entry.getValue());
		entry = treeMap.lowerEntry("z");  // 생각해보기
		System.out.println("ever 앞 단어: " + entry.getKey() + " - " + entry.getValue());
		System.out.println();
		
		// 내림차순으로 정렬
		NavigableMap<String, Integer> descendingMap = treeMap.descendingMap();
		Set<Entry<String, Integer>> desendingEntrySet = descendingMap.entrySet();
		for(Entry<String, Integer> e : desendingEntrySet) {
			System.out.println(e.getKey() + " - " + e.getValue());
		}
		System.out.println();
		
		// 범위 검색
		System.out.println("[c~h 사이의 단어 검색]");
		NavigableMap<String, Integer> rangeMap = treeMap.subMap("c", true, "h", false);
		for(Entry<String, Integer> e : rangeMap.entrySet()) {
			System.out.println(e.getKey() + " - " + e.getValue());
		}
	}
}

 

 

● Comparable과 Comparator

TreeSet에 저장되는 객체, TreeMap에 저장되는 키 객체는 저장과 동시에 오름차순으로 정렬되는데

객체가 Comparable(비교가능한) 인터페이스를 구현하고 있어야 가능.

(Integer, Double, String 타입 등은 모두 Comparable을 구현하고 있음)

 

Comparable 인터페이스의 compareTo() 메소드가 정의되어 있다.

사용자 정의 객체를 저장할 때 반드시 이 메소드를 재정의 해야한다.

리턴 타입 메소드 설명
int compareTo(T o) 주어진 객체와 같으면 0 리턴,
주어진 객체보다 적으면 음수 리턴,
주어진 객체보다 크면 양수 리턴.

 

예시) 사용자 정의 객체

public class Person implements Comparable<Person> {
	public String name;
	public int age;
	
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	@Override
	public int compareTo(Person o) {
		int result = name.compareTo(o.name);
//		return -result; // 역순으로 출력 활용 가능
		return result;
	}
}

 

※ 비교 기능이 없는 Comparable 비구현 객체일 경우, 비교자(Comparator)를 제공해야 함.

리턴 타입 메소드 설명
int compare(T o1, T o2) o1과 o2가 동등하면 0 리턴
o1이 o2보다 앞에 오게 하려면 음수 리턴
o1이 o2보다 뒤에 오게 하려면 양수 리턴

 

예시)

import lombok.AllArgsConstructor;
import lombok.Data;
// 롬복 사용
@Data
@AllArgsConstructor
public class Fruit {
	public String name;
	public int price;
}

// 비교자 클래스
import java.util.Comparator;
public class FruitComparator implements Comparator<Fruit> {
	@Override
	public int compare(Fruit o1, Fruit o2) {
		if(o1.price > o2.price)	return +1;	// price로 비교
		else if(o1.price == o2.price) return 0;
		else return -1;
	}
}

// 사용
import java.util.TreeSet;
public class ComparatorExample {
	public static void main(String[] args) {
    	// 비교자 객체를 생성하여 선언
		TreeSet<Fruit> treeSet = new TreeSet<>(new FruitComparator());
		
		treeSet.add(new Fruit("포도", 3000));
		treeSet.add(new Fruit("수박", 10000));
		treeSet.add(new Fruit("딸기", 6000));
		treeSet.add(new Fruit("바나나", 5000));
		// 비교가능한 객체가 되어 값은 반환
		for(Fruit t : treeSet) {
			System.out.println(t);
		}
	}
}

 

 

15.6 LIFO와 FIFO 컬렉션

▶ LIFO(Last In First Out) : 후입선출. 나중에 넣은 객체가 먼저 빠져나간다.

▶ FIFO(First In First Out) : 선입선출. 먼저 넣은 객체가 먼저 빠져나가는 구조.

 

● Stack

LIFO 자료구조를 구현한 클래스.

Stack<E> stack = new Stack<E>();
Stack<E> stack = new Stack<>();

 

리턴 타입 메소드 설명
E push(E item) 주어진 객체를 스택에 넣음
E pop() 스택의 맨 위 객체를 빼냄

→ 동전 넣고 빼기의 예시를 보며 이해

 

● Queue

FIFO 자료구조에서 사용되는 메소드를 정의하고 있는 인터페이스.

Queue 인터페이스를 구현한 대표적 클래스는 LinkedList이다.

따라서

Queue<E> queue = new LinkedList<E>();
Queue<E> queue = new LinkedList<>();
리턴 타입 메소드 설명
boolean offer(E e) 주어진 객체를 큐에 넣음
E poll() 큐에서 객체를 빼냄

→ 메시지 예시

 

 

15.7 동기화된 컬렉션

컬렉션 프레임워크의 대부분 클래스들은 싱글 스레드 환경에서 사용할 수 있도록 설계.

ArrayLiest, HashSet, HashMap은 멀티 스레드 환경에서 안전하지 않다.

이렇게 비동기화 된 메소드를 동기화된 메소드로 래핑하는 메소드를 제공한다.(synchronizedList 등등)

→ 대략적으로 이해하고 넘어가자.

 

 

15.8 수정할 수 없는 컬렉션

컬렉션 생성 시 저장된 요소를 변경하고 싶지 않을 때 유용.

→ 쓸일이 많지 않다. 따라서 대략적으로 이해하고 넘어가자.

'JAVA' 카테고리의 다른 글

19일차 2024 - 3 - 22  (1) 2024.03.24
18일차 2024 - 3 - 21  (0) 2024.03.21
16일차 2024 - 3 - 19  (0) 2024.03.19
5일차 2024 - 3 - 4  (0) 2024.03.18
4일차 2024 - 2 - 29  (0) 2024.03.18

+ Recent posts