Java Category/Java

[Java] 컬렉션 프레임워크(List, Set, Map)

ReBugs 2023. 8. 3.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


컬렉션 프레임워크(Collection Framework)

자바는 널리 알려져 있는 자료구조를 바탕으로 객체들을 효율적으로 추가, 삭제 검색할 수 있도록 관련된 인터페이스와 클래스들을 java.util 패키지에 포함시켜 놓았다.

이들을 총칭해서 컬렉션 프레임워크라고 부른다.

컬렉션 프레임워크는 몇 가지 인터페이스를 통해서 다양한 컬렉션 클래스를 이용할 수 있도록 설계되어 있다.

주요 인터페이스로는 List, Set, Map이 있다.

컬렉션 프레임워크
배열은 길이가 정해지면 바꿀 수 없었지만, 컬렉션 프레임워크는 길이가 가변적이다.

컬렉션 프레임워크는 인터페이스이기 때문에 인터페이스에 정의된 메소드들의 사용 방법만 익히면 어떤 구현 클래스가 오던지 사용 방법이 모두 똑같기 때문에 편리하다.

 

 

List와 Set은 객체를 추가, 삭제, 검색하는 방법에 있어서 공통점이 있기 때문에 공통된 메소드만 따로 모아 Collection 인터페이스로 정의해 두고 이것을 상속하고 있다.

 

Map은 키와 값을 하나의 쌍으로 묶어서 관리하는 구조로 되어 있어 List 및 Set과는 사용 방법이 다르다.

 

아래의 표는 각 인터페이스 별로 사용할 수 있는 컬렉션의 특징을 정리한 표이다.

인터페이스 분류 특징 구현 클래스
Collection List -순서를 유지하고 저장
-중복 저장 가능
ArrayList, Vector
LinkedList
Set -순서를 유지하지 않고 저장
-중복 저장 안됨
HashSet, TreeSet
Map -키와 값으로 구성된 엔트리 저장
-키는 중복 저장 안됨
HashMap, HashTable, TreeMap, Properties

 

 


 

List 컬렉션

List 컬렉션은 객체를 인덱스로 관리하기 때문에 객체를 저장하면 인덱스가 부여되고 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공한다.

 

List 컬렉션에서 공통적으로 사용 가능한 List 인터페이스 메소드는 아래와 같다.

기능 메소드 설명
객체 추가 boolean add(E e) 객체를 맨 끝에 추가
void add(int index, E element) 인덱스에 객체를 추가
set(int index, E element) 인덱스의 객체를 새로운 객체로 바꿈
객체 검색 boolean contains(Object o) 주어진 객체가 저장되어 있는지 여부
E get(int index) 주어진 인덱스에 저장된 객체를 리턴
isEmpty() 컬렉션이 비어 있는지 여부
int size() 저장되어 있는 전체 객체 수를 리턴
객체 삭제 void clear() 저장된 모든 객체를 삭제
E remove(int index) 주어진 인덱스에 저장된 객체를 삭제
boolean remove(Object o) 주어진 객체를 삭제

 


 

ArrayList

ArrayList는 List 컬렉션에서 가장 많이 사용하는 컬렉션이다.

ArrayList에 객체를 추가하면 내부 배열에 객체가 저장된다.

일반 배열과 차이점은 제한 없이 객체를 추가할 수 있다는 점이다.

tip
List 컬렉션은 객체 자체를 저장하는 것이 아니라 객체의 번지를 저장한다.
또한 동일한 객체를 중복 저장할 수 있는데, 이 경우 동일한 번지가 저장된다.
null 또한 저장이 가능하다.

 

ArrayList 컬렉션은 아래와 같이 생성할 수 있다.

List<E> list = new ArrayList<E>();
List<E> list = new ArrayList<>();
List list new ArrayList();

타입 파라미터 E에는 ArrayList에 저장하고 싶은 객체 타입을 지정하면 된다.

객체 타입을 모두 생략하면 Object 타입 객체를 저장할 수 있다.

효율성
빈번한 객체 삭제와 삽입이 일어나면 해당 인덱스부터 끝까지 인덱스가 당겨지거나 밀려나게 된다.
따라서 이런 경우에는 LinkedList를 사용하는 것이 좋다.

 

아래는 ArrayList에 객체를 추가, 검색, 삭제하는 예제이다.

더보기

Board.java

public class Board {
	private String subject;
	private String content;
	private String writer;

	public Board(String subject, String content, String writer) {
		this.subject = subject;
		this.content = content;
		this.writer = writer;
	}

	public String getSubject() { return subject; }
	public void setSubject(String subject) { this.subject = subject; }
	public String getContent() { return content; }
	public void setContent(String content) { this.content = content; }
	public String getWriter() { return writer; }
	public void setWriter(String writer) { this.writer = writer; }
}

 

ArrayListExample.java

import java.util.ArrayList;
import java.util.List;
	
public class ArrayListExample {
	public static void main(String[] args) {
		//ArrayList 컬렉션 생성
		List<Board> list = new ArrayList< >();
		
		//객체 추가
		list.add(new Board("제목1", "내용1", "글쓴이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"));

		//저장된 총 객체 수 얻기
		int size = list.size();
		System.out.println("총 객체 수: " + size);
		System.out.println();
				
		//특정 인덱스의 객체 가져오기
		Board board = list.get(2);
		System.out.println(board.getSubject() + "\t" + board.getContent() +
								"\t" + board.getWriter());
		System.out.println();
			
		//모든 객체를 하나씩 가져오기
		for(int i=0; i<list.size(); i++) {
			Board b = list.get(i);
			System.out.println(b.getSubject() + "\t" + b.getContent() +
								"\t" + b.getWriter());
		}
		System.out.println();
			
		//객체 삭제
		list.remove(2);
		list.remove(2);
			
		//향상된 for문으로 모든 객체를 하나씩 가져오기
		for(Board b : list) {
			System.out.println(b.getSubject() + "\t" + b.getContent() +
								"\t" + b.getWriter());
		}
	}
}
/*
총 객체 수: 5

제목3	내용3	글쓴이3

제목1	내용1	글쓴이1
제목2	내용2	글쓴이2
제목3	내용3	글쓴이3
제목4	내용4	글쓴이4
제목5	내용5	글쓴이5

제목1	내용1	글쓴이1
제목2	내용2	글쓴이2
제목5	내용5	글쓴이5
*/

 


 

Vector

Vector는 ArrayList와 동일한 내부 구조를 가지고 있다.

차이점은 동기화된 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 Vector() 메소드를 호출할 수 없다는 것이다.

그렇기 때문에 멀티 스레드 환경에서 안전하게 객체를 추가 또는 삭제할 수 있다.

 

Vector 컬렉션은 다음과 같이 생성할 수 있다.

List<E> list = new Vector<E>();
List<E> list = new Vector<>();
List list new Vector();

타입 파라미터 E에는 Vector에 저장하고 싶은 객체 타입을 지정하면 된다.

객체 타입을 모두 생략하면 모든 종류의 객체를 저장할 수 있다.

 

아래의 예제는 쓰레드 A, B에서 동시에 객체를 벡터에 1000개씩 추가한 후 전체 저장된 수를 출력하는 예제이다.

벡터가 아니라 ArrayList로 구현하면 실행 결과가 달라진다. 

더보기

Board.java

public class Board {
	private String subject;
	private String content;
	private String writer;
	
	public Board(String subject, String content, String writer) {
		this.subject = subject;
		this.content = content;
		this.writer = writer;
	}

	public String getSubject() { return subject; }
	public void setSubject(String subject) { this.subject = subject; }
	public String getContent() { return content; }
	public void setContent(String content) { this.content = content; }
	public String getWriter() { return writer; }
	public void setWriter(String writer) { this.writer = writer; }
}

 

VectorExample.java

import java.util.List;
import java.util.Vector;

public class VectorExample {
	public static void main(String[] args) {
		//Vector 컬렉션 생성
		List<Board> list = new Vector<>();
	
		//작업 스레드 객체 생성
		Thread threadA = new Thread() {
			@Override
			public void run() {
				//객체 1000개 추가
				for(int i=1; i<=1000; i++) {
					list.add(new Board("제목"+i, "내용"+i, "글쓴이"+i));
				}
			}
		};
		
		//작업 스레드 객체 생성
		Thread threadB = new Thread() {
			@Override
			public void run() {
				//객체 1000개 추가
				for(int i=1001; i<=2000; i++) {
					list.add(new Board("제목"+i, "내용"+i, "글쓴이"+i));
				}
			}
		};
		
		//작업 스레드 실행
		threadA.start();
		threadB.start();
		
		//작업 스레드들이 모두 종료될때까지 메인 스레드를 기다리게함
		try {
			threadA.join();
			threadB.join();
		} catch(Exception e) {
		}
		
		//저장된 총 객체 수 얻기
		int size = list.size();
		System.out.println("총 객체 수: " + size);		
		System.out.println();		
	}
}
/*
총 객체 수: 2000
*/

 


 

LinkedList

LinkedList는 ArrayList와 사용 방법은 동일하지만 내부 구조는 완전히 다르다.

LinkedList는 인접 객체를 체인처럼 연결해서 관리한다.

연결 리스트는 특정 위치에서 객체를 삽입하거나 삭제하면 바로 앞뒤 링크만 변경하면 되므로 빈번한 객체 삭제와 삽입이 일어나는 곳에서 ArrayList보다 좋은 성능을 발휘한다.

LinkedList 컬렉션은 DLL(Double Linked List) 구조이다.

LinkedList 컬렉션은 아래와 같이 생성할 수 있다.

List<E> list = new LinkedList<E>();
List<E> list = new LinkedList<>();
List list new LinkedList();

 

아래의 예제는 ArrayList와 LinkedList에 만 개의 객체를 삽입하는 데 걸린 시간을 측정한 것이다.

더보기

LinkedListExample.java

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
	public static void main(String[] args) {
		//ArrayList 컬렉션 객체 생성
		List<String> list1 = new ArrayList<String>();

		//LinkedList 컬렉션 객체 생성
		List<String> list2 = new LinkedList<String>();

		//시작 시간과 끝 시간을 저장할 변수 선언
		long startTime;
		long endTime;

		//ArrayList 컬렉션에 저장하는 시간 측정
		startTime = System.nanoTime();
		for(int i=0; i<10000; i++) {
			list1.add(0, String.valueOf(i));
		}
		endTime = System.nanoTime();
		System.out.printf("%-17s %8d ns \n", "ArrayList 걸린 시간: ", (endTime-startTime) );

		//LinkedList 컬렉션에 저장하는 시간 측정
		startTime = System.nanoTime();
		for(int i=0; i<10000; i++) {
			list2.add(0, String.valueOf(i));
		}
		endTime = System.nanoTime();
		System.out.printf("%-17s %8d ns \n", "LinkedList 걸린 시간: ", (endTime-startTime) );
	}
}
/*
ArrayList 걸린 시간:   5813200 ns 
LinkedList 걸린 시간:    880900 ns 
*/

 

 


 

Set 컬렉션

Set 컬렉션은 저장 순서가 유지되지 않는다.

또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다. 

Set 컬렉션은 수학의 집합에 비유될 수 있다.

집합은 순서와 상관없고 중복이 허용되지 않기 때문이다.

 

Set 컬렉션에는 HashSet, LinkedHashSet, TreeSet 등이 있는데, Set 컬렉션에서 공통적으로 사용 가능한 Set 인터페이스의 메소드는 아래 표와 같다.

기능 메소드 설명
객체 추가 boolean add(E e) 주어진 객체를 성공적으로 저장하면 true를 리턴하고 중복 객체면 false를 리턴
객체 검색 boolean contains(Object o) 주어진 객체가 저장되어 있는지 여부
isEmpty() 컬렉션이 비어 있는지 조사
Iterator<E> iterator() 저장된 객체를 한 번씩 가져오는 반복자 리턴
int size() 저장되어 있는 전체 객체 수 리턴
객체 삭제 void clear() 저장된 모든 객체를 삭제
boolean remove(Object o) 주어진 객체를 삭제
Iterator<E> iterator()
이 메소드는 Interface Iterable<E>의 추상 메소드이다.
Iterable을 한국어로 번역하면 '반복할 수 있는'이다.
향상된 for문에서 선언할 수 있는 객체는 이 인터페이스를 구현한 객체만 올 수 있다.
iterator() 메소드를 호출하면 해당 객체의 참조를 Iterator 타입으로 가져온다.

 


 

HashSet

Set 컬렉션 중에서 가장 많이 사용되는 것이 HashSet이다.

아래는 HashSet 컬렉션을 생성하는 방법이다.

Set<E> set = new HashSet<E>();
Set<E> set = new HashSet<>();
Set set new Hashset();

HashSet은 동일한 객체는 중복 저장하지 않는다.

여기서 동일한 객체란 동등 객체를 말한다.
HashSet은 다른 객체라도 HashCode() 메소드의 리턴값이 같고, equals() 메소드가 true를 리턴하면 동일한 객체라고 판단하고 중복 저장하지 않는다.

 

HashSet 컬렉션 생성 예제

더보기

HashSetExample.java

import java.util.*;

public class HashSetExample {
	public static void main(String[] args) {
		//HashSet 컬렉션 생성
		Set<String> set = new HashSet<String>();
		
		//객체 저장
		set.add("Java");
		set.add("JDBC");
		set.add("Servlet/JSP");
		set.add("Java");			//<-- 중복 객체이므로 저장하지 않음
		set.add("iBATIS");
		
		//저장된 객체 수 출력
		int size = set.size();
		System.out.println("총 객체 수: " + size);
	}
}
/*
총 객체 수: 4
*/

 

이름과 나이가 동일할 경우 Member 객체를 HashSet에 중복 저장하지 않는 예제

더보기

Member.java

public class Member {
	public String name;
	public int age;

	public Member(String name, int age) {
		this.name = name;
		this.age = age;
	}
				
	//hashCode 재정의
	@Override
	public int hashCode() {
		return name.hashCode() + age;
	}

	//equals 재정의
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Member target) {
			return target.name.equals(name) && (target.age==age) ;
		} else {
			return false;
		}
	}
}

 

HashSetExample.java

import java.util.*;
	
public class HashSetExample {
	public static void main(String[] args) {
		//HashSet 컬렉션 생성
		Set<Member> set = new HashSet<Member>();

		//Member 객체 저장
		set.add(new Member("홍길동", 30));
		set.add(new Member("홍길동", 30));

		//저장된 객체 수 출력
		System.out.println("총 객체 수 : " + set.size());
	}
}
/*
총 객체 수 : 1
*/

 

Set 컬렉션은 인덱스로 객체를 검색해서 가져오는 메소드가 없다.

대신 객체를 한 개씩 반복해서 가져와야 하는데, 두 가지 방법이 있다.

1. for 문을 이용하는 방법

Set<E> set = new HashSet<E>();
for(E e : set) {

}
for문에서 HashSet 객체를 직접적으로 추가 및 삭제를 하면 안 된다.
for(String element : set) {
    if(element.equals("JSP")) {
        set.remove(element);
    }
}​
위 코드에서 set의 객체의 개수가 4개라고 하면, 처음에 for문은 반복하는 횟수가 4번으로 정해져 있다.
하지만 중간에 remove나 add 메소드로 인해서 set 객체의 개수가 감소 또는 증가하면 4번 반복을 돌아야하는데 객체의 수가 4개 미만 또는 초과이므로 for문은 오류를 내뿜는다.
따라서 직접적인 add나 remove 메소드 호출은 지양해야 한다.

추가 또는 삭제를 하려면 iterator() 메소드로 반복자를 얻어서 작업을 하는 것이 안전하다.

 

2. iterator() 메소드로 반복자를 얻어 객체를 하나씩 가져오기

Set<E> set = new HashSet<E>();
Iterator<E> iterator = set.iterator();

iterator는 Set 컬렉션의 객체를 가져오거나 제거하기 위해 아래의 메소드를 제공한다.

리턴 타입 메소드명 설명
boolean hasNext() 가져올 객체가 있으면 true를 리턴하고 없으면 false를 리턴
E next() 컬렉션에서 하나의 객체를 가져온다.
void remove() next()로 가져온 객체를 Set 컬렉션에서 제거한다.

사용 방법은 아래와 같다.

while(iterator.hasNext()) {
    E e = iterator.next();
}

hasNext() 메소드로 가져올 객체가 있는지 먼저 확인하고, true를 리턴할 때만 next() 메소드로 객체를 가져온다.

만약, next()로 가져온 객체를 컬렉션에서 제거하고 싶다면 remove() 메소드를 사용한다.

 

HashSet 추가, 삭제, 제거 예제

import java.util.*;

public class HashSetExample {
	public static void main(String[] args) {
		//HashSet 컬렉션 생성
		Set<String> set = new HashSet<String>();
		
		//객체 추가
		set.add("Java");
		set.add("JDBC");
		set.add("JSP");
		set.add("Spring");
		
		//객체를 하나씩 가져와서 처리
		Iterator<String> iterator = set.iterator();
		while(iterator.hasNext()) {
			//객체를 하나 가져오기
			String element = iterator.next();
			System.out.println( element);
			if(element.equals("JSP")) {
				//가져온 객체를 컬렉션에서 제거
				iterator.remove();
			}
		}
		System.out.println();
		
		//객체 제거
		set.remove("JDBC");
		
		//객체를 하나씩 가져와서 처리
		for(String element : set) {
			System.out.println(element);
		}
	}
}
/*
Java
JSP
JDBC
Spring

Java
Spring
*/

 


 

Map 컬렉션

Map 컬렉션은 키(key)와 값(value)으로 구성된 엔트리(entry) 객체를 저장한다.

여기서 키와 값은 모두 객체이다.

키는 중복저장할 수 없지만, 값은 중복 저장할 수 있다.

기존에 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 바뀌게 된다.

Map 컬렉션에는 HashMap, HashTable, LinkedHash, Properties, TreeMap 등이 있다.

Map 컬렉션에서 공통적으로 사용 가능한 Map 인터페이스 메소드는 아래와 같다.

기능 메소드 설명
객체 추가 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 삭제, 삭제가 되면 값을 리턴

위 표에서 K와 V는 타입 파라미터이고, K는 키 타입, V는 값 타입을 말한다.

 


 

HashMap

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

 

아래는 HashMap 컬렉션을 생성하는 방법이다. K와 V는 각각 키와 값의 타입을 지정할 수 있는 타입 파라미터이다.

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

 

아래의 예제는 이름을 키로, 점수를 값으로 저장하는 해시맵 사용 방법을 보여준다.

더보기

HashMapExample.java

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class HashMapExample {
	public static void main(String[] args) {
		//Map 컬렉션 생성
		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();

		//키로 값 얻기
		String key = "홍길동";
		int value = map.get(key); //키를 매개값으로 주면 값을 리턴
		System.out.println(key + ": " + value);
		System.out.println();

		//키 Set 컬렉션을 얻고, 반복해서 키와 값을 얻기
		Set<String> keySet = map.keySet();
		Iterator<String> keyIterator = keySet.iterator();
		while (keyIterator.hasNext()) {
			String k = keyIterator.next();
			Integer v = map.get(k);
			System.out.println(k + " : " + v);
		}
		System.out.println();

		//엔트리 Set 컬렉션을 얻고, 반복해서 키와 값을 얻기
		Set<Entry<String, Integer>> entrySet = map.entrySet();
		Iterator<Entry<String, Integer>> entryIterator = entrySet.iterator();
		while (entryIterator.hasNext()) {
			Entry<String, Integer> entry = entryIterator.next();
			String k = entry.getKey();
			Integer v = entry.getValue();
			System.out.println(k + " : " + v);
		}
		System.out.println();
		
		//키로 엔트리 삭제
		map.remove("홍길동");
		System.out.println("총 Entry 수: " + map.size());
		System.out.println();
	}
}
/*
총 Entry 수: 3

홍길동: 95

홍길동 : 95
신용권 : 85
동장군 : 80

홍길동 : 95
신용권 : 85
동장군 : 80

총 Entry 수: 2
*/

 


 

HashTable

HashTable은 HashMap과 동일한 내부 구조를 가지고 있다. 차이점은 동기화된 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 HashTable의 메소드들을 실행할 수 없다는 것이다.

따라서 멀티 스레드 환경에서도 안전하게 객체를 추가, 삭제할 수 있다.

 

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

 


 

Properties

Properties는 HashTable의 자식 클래스이기 때문에 해시 테이블의 특징을 그대로 가지고 있다.

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

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

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

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

 

database.properties

driver=oracle.jdbc.OracleDirver
url=jdbc:oracle:thin:@localhost:1521:orcl
username=scott
password=tiger
admin=\uD64D\uAE38\uB3D9

 

Properties을 사용하면 위와 같은 프로퍼티 파일의 내용을 코드에서 쉽게 읽을 수 있다.

먼저 프로퍼티 객체를 생성하고, load() 메소드로 프로퍼티 파일의 내용을 메모리로 로드한다.

Properties properties = new Properties();
properties.load(Xxx.class.getResourceAsStream("database.properties"));

일반적으로 프로퍼티 파일은 클래스 파일들과 함께 저장된다.

따라서 클래스 파일을 기준으로 상대 경로를 이용해서 읽는 것이 편리하다.

Class 객체의 getResourceAsStream() 메소드는 주어진 상대 경로의 리소스 파일을 읽는 InputStream을 리턴한다.

 

PropertiesExample.java

import java.util.Properties;
	
public class PropertiesExample {
	public static void main(String[] args) throws Exception {
		//Properties 컬렉션 생성
		Properties properties = new Properties();

		//PropertiesExample.class와 동일한 ClassPath에 있는 database.properties 파일 로드
		properties.load(PropertiesExample.class.getResourceAsStream("database.properties"));

		//주어진 키에 대한 값 읽기
		String driver = properties.getProperty("driver");
		String url = properties.getProperty("url");
		String username = properties.getProperty("username");
		String password = properties.getProperty("password");
		String admin = properties.getProperty("admin");

		//값 출력
		System.out.println("driver : " + driver);
		System.out.println("url : " + url);
		System.out.println("username : " + username);
		System.out.println("password : " + password);
		System.out.println("admin : " + admin);
	}
}
/*
driver : oracle.jdbc.OracleDirver
url : jdbc:oracle:thin:@localhost:1521:orcl
username : scott
password : tiger
admin : 홍길동
*/

 


 

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

TreeSet

TreeSet은 이진 트리를 기반으로 한 Set 컬렉션이다.

이진 트리는 여러 개의 노드가 트리 형태로 연결된 구조로, 루트 노드라고 불리는 하나의 노드에서 시작해 각 노드에 최대 2개의 노드를 연결할 수 있는 구조를 가지고 있다.

TreeSet에 객체를 저장하면 아래와 같이 자동으로 정렬된다.

부모 노드의 객체와 비교해서 낮은 것은 왼쪽 자식 노드에, 높은 것은 오른쪽 자식 노드에 저장한다.

 

아래는 트리셋 컬렉션을 생성하는 방법이다.

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

Set 타입 변수에 대입을 하면 자식인 TreeSet 메소드를 사용할 수 없다.

 

아래는 TreeSet의 검색 관련 메소드들이다.

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

 

아래의 예제는 무작위로 저장한 점수를 검색하는 방법을 보여준다.

더보기

TreeSetExample.java

import java.util.NavigableSet;
import java.util.TreeSet;

public class TreeSetExample {
	public static void main(String[] args) {
		//TreeSet 컬렉션 생성
		TreeSet<Integer> scores = new TreeSet< >();

		//Integer 객체 저장
		scores.add(87);
		scores.add(98);
		scores.add(75);
		scores.add(95);
		scores.add(80);

		//정렬된 Integer 객체를 하나씩 가져오기
		for(Integer s : scores) {
			System.out.print(s + " ");
		}
		System.out.println("\n");

		//특정 Integer 객체를 가져오기
		System.out.println("가장 낮은 점수: " + scores.first());
		System.out.println("가장 높은 점수: " + scores.last());
		System.out.println("95점 아래 점수: " + scores.lower(95));
		System.out.println("95점 위의 점수: " + scores.higher(95));
		System.out.println("95점이거나 바로 아래 점수: " + scores.floor(95));
		System.out.println("85점이거나 바로 위의 점수: " + scores.ceiling(85) + "\n");

		//내림차순으로 정렬하기
		NavigableSet<Integer> descendingScores = scores.descendingSet();
		for(Integer s : descendingScores) {
			System.out.print(s + " ");
		}
		System.out.println("\n");
		
		//범위 검색( 80 <= )
		NavigableSet<Integer> rangeSet = scores.tailSet(80, true);
		for(Integer s : rangeSet) {
			System.out.print(s + " ");
		}
		System.out.println("\n");
		
		//범위 검색( 80 <= score < 90 )
		rangeSet = scores.subSet(80, true, 90, false);
		for(Integer s : rangeSet) {
			System.out.print(s + " ");
		}
	}
}
/*
75 80 87 95 98 

가장 낮은 점수: 75
가장 높은 점수: 98
95점 아래 점수: 87
95점 위의 점수: 98
95점이거나 바로 아래 점수: 95
85점이거나 바로 위의 점수: 87

98 95 87 80 75 

80 87 95 98 

80 87 
*/

 


 

TreeMap

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

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

TreeMap에 엔트리를 저장하면 키를 기준으로 자동 정렬되는데, 부모 키 값과 비교해서 낮은 것은 왼쪽, 높은 것은 오른쪽 자식 노드에 Entry 객체를 저장한다.

 

아래는 TreeMap 컬렉션을 생성하는 방법이다.

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

Map 타입 변수에 대입해도 되지만 자식인 TreeMap 타입으로 대입해야 관련 메소드들을 사용할 수 있다.

 

아래는 TreeMap이 가지고 있는 검색 관련 메소드들이다.

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

 

아래의 예제는 영어 단어와 페이지 번호를 무작위로 저장하고 검색하는 방법을 보여준다.

더보기

TreeMapExample.java

import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;

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("cherry", 30);

		//정렬된 엔트리를 하나씩 가져오기
		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("ever");
		System.out.println("ever 앞 단어: " + entry.getKey() + "-" + entry.getValue() + "\n");
		
		//내림차순으로 정렬하기
		NavigableMap<String,Integer> descendingMap = treeMap.descendingMap();
		Set<Entry<String,Integer>> descendingEntrySet = descendingMap.entrySet();
		for(Entry<String,Integer> e : descendingEntrySet) {
			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());
		}
	}
}
/*
apple-10
base-20
cherry-30
description-40
ever-50
forever-60
guess-70
zoo-80

제일 앞 단어: apple-10
제일 뒤 단어: zoo-80
ever 앞 단어: description-40

zoo-80
guess-70
forever-60
ever-50
description-40
cherry-30
base-20
apple-10

[c~h 사이의 단어 검색]
cherry-30
description-40
ever-50
forever-60
guess-70
*/

 


 

Comparable과 Comparator

Comparable

TreeSet과 TreeMap에 저장되는 키 객체는 저장과 동시에 오름차순으로 정렬되지만, 어떤 객체든 정렬될 수 있는 것은 아니다.

객체가 Comparable 인터페이스를 구현하고 있어야 정렬을 할 수 있다.

사용자 정의 객체를 저장할 때에는 반드시 Comparable을 구현하고 있어야 한다.

 

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

따라서 사용자 정의 클래스에서 이 메소드를 재정의해서 비교 결과를 정수 값으로 리턴해야 한다.

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

 

아래의 예제는 사용자 정의 객체를 정렬하기 위해 Comparable 인터페이스를 구현한 예제이다.

더보기

Person.java

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) {
		if(age<o.age) return -1;
		else if(age == o.age) return 0;
		else return 1;
	}
}

 

ComparableExample.java

import java.util.TreeSet;

public class ComparableExample {
	public static void main(String[] args) {
		//TreeSet 컬렉션 생성
		TreeSet<Person> treeSet = new TreeSet<Person>();

		//객체 저장
		treeSet.add(new Person("홍길동", 45));
		treeSet.add(new Person("감자바", 25));
		treeSet.add(new Person("박지원", 31));
		
		//객체를 하나씩 가져오기
		for(Person person : treeSet) {
			System.out.println(person.name + ":" + person.age);
		}
	}
}
/*
감자바:25
박지원:31
홍길동:45
*/

 


 

Comparator

비교 기능이 있는 Comparable 구현 객체를 TreeSet에 저장하거나 TreeMap의 키로 저장하는 것이 원칙이다

하지만 비교 기능이 없는 Comparable 비구현 객체를 저장하고 싶다면 TreeSet과 TreeMap을 생성할 때 비교자(Comparator)를 아래와 같이 제공하면 된다.

TreeSet<K, V> treeSet = new TreeMap<E>(new ComparatorImpl());

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

 

비교자는 Comparator 인터페이스를 구현한 객체를 말하는데, Comparator 인터페이스에는 compare() 메소드가 정의되어 있다.

비교자는 이 메소드를 재정의해서 비교 결과를 정수 값으로 리턴하면 된다.

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

 

아래는 Comparable을 구현하고 있지 않은 사용자 정의 객체를 TreeSet에 저장하는 예제이다.

더보기

Fruit.java

public class Fruit {
	public String name;
	public int price;

	public Fruit(String name, int price) {
		this.name = name;
		this.price = price;
	}
}

 

FruitComparator.java

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;
		else if(o1.price == o2.price) return 0;
		else return 1;
	}
}

 

ComparatorExample.java

import java.util.TreeSet;

public class ComparatorExample {
	public static void main(String[] args) {
		//비교자를 제공한 TreeSet 컬렉션 생성
		TreeSet<Fruit> treeSet = new TreeSet<Fruit>(new FruitComparator());
			
		//객체 저장
		treeSet.add(new Fruit("포도", 3000));
		treeSet.add(new Fruit("수박", 10000));
		treeSet.add(new Fruit("딸기", 6000));
		
		//객체를 하나씩 가져오기
		for(Fruit fruit : treeSet) {
			System.out.println(fruit.name + ":" + fruit.price);
		}
	}
}
/*
포도:3000
딸기:6000
수박:10000
*/

댓글