다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스의 집합을 의미한다.
즉, 데이터를 저장하는 자료 구조와 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현해 놓은 것
컬렉션 프레임워크는 자바의 인터페이스(interface)를 사용하여 구현된다.
컬렉션
-> 다수의 데이터 /프레임워크
-> 표준화 설계
모든 컬렉션 클래스들은 List, Set, Map중 하나를 구현하고 있다.
Set
Collection
Collection은 List와 Set의 상위 인터페이스이며, 컬렉션 클래스에 저장된 데이터를 읽고, 추가하고, 삭제하는 등 컬렉션을 다루는데 가장 기본이적인 메서드들을 정의하고 있다.
메서드 | 설명 |
---|---|
boolean add(Object o) boolean addAll(Collection c) | 지정된 객체(o) 또는 Collection(c)의 객체들을 Collection에 추가한다. |
void clear() | Collection의 모든 객체를 삭제한다. |
boolean contains(Object o) boolean containsAll(Collection c) | 지정된 객체(o) 또는 Collection의 객체들이 Collection에 포함되어 있는지 확인한다. |
boolean equals(Object o) | 동일한 Collection인지 비교한다. |
int hashCode() | Collection의 hash code를 반환한다. |
boolean isEmpty() | Collection이 비어 있는지 확인한다. |
Iterator iterator() | Collection의 iterator를 얻어서 반환한다. |
boolean remove(Object o) | 지정된 객체를 삭제한다. |
boolean removeAll(Collection c) | 지정된 Collection에 포함된 객체들을 삭제한다. |
boolean retainAll(Collection c) | 지정된 Collection에 포함된 객체만 남기고 다른 객체들은 Collection에서 삭제한다. 이 작업으로 인해 Collection에 변화가 있으면 true를 그렇지 않다면 false를 반환한다. |
int size() | Collection에 저장된 객체의 개수를 반환한다. |
Object[] toArray() | Collection에 저장된 객체를 객체배열(Object[])로 반환한다. |
Object[] toArray(Object[] a) | 지정된 배열에 Collection의 객체를 지정해서 반환한다. |
ArrayList
, LinkedList
, Stack
, Vector
public class ArraList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable {
...
transient Object[] elementData;
...
}
package List;
import java.util.*;
public class ArrayListEx1 {
public static void main(String[] args) {
ArrayList arrayList1 = new ArrayList();
arrayList1.add(Integer.valueOf(5));
arrayList1.add(Integer.valueOf(4));
arrayList1.add(Integer.valueOf(1));
arrayList1.add(Integer.valueOf(0));
arrayList1.add(Integer.valueOf(-1));
// 인덱스 순서로 잘라서 리스트로 저장함
ArrayList arrayList2 = new ArrayList(arrayList1.subList(1, 4));
print(arrayList1, arrayList2);
// list1 : [5, 4, 1, 0, -1]
// list2 : [4, 1, 0]
Collections.sort(arrayList1); // 정렬
Collections.sort(arrayList2); // 정렬
print(arrayList1, arrayList2);
// list1 : [-1, 0, 1, 4, 5]
// list2 : [0, 1, 4]
// 포함여부 true
System.out.println(arrayList1.containsAll(arrayList2));
// 추가
arrayList2.add("B");
arrayList2.add("V");
// 인덱스 3번째 순서에 추가
arrayList2.add(3, "세번째");
print(arrayList1, arrayList2);
// list1 : [-1, 0, 1, 4, 5]
// list2 : [0, 1, 4, 세번째, B, V]
// 인덱스 3번째 순서 컨텐츠 변경
arrayList2.set(3, "Third");
print(arrayList1, arrayList2);
// list1 : [-1, 0, 1, 4, 5]
// list2 : [0, 1, 4, Third, B, V]
// 겹치는 부분만 남기고 삭제
// true
System.out.println(arrayList1.retainAll(arrayList2));
print(arrayList1, arrayList2);
// list1 : [0, 1, 4]
// list2 : [0, 1, 4, Third, B, V]
// arrayList2에서 arrayList1과 동일한 멤버가 있다면 삭제한다.
// list2.size() - 1 부터 감소시키면서 거꾸로 반복 시켰다.
// 변수 i를 증가시켜가면서 삭제하면, 한 요소가 삭제될 때마다 빈 공간을 채우기 위해
// 나머지 요소들이 자리이동을 하기 때문에 올바른 결과를 얻을 수 없다.
for(int i = arrayList2.size()-1; i>=0; i--){
if(arrayList1.contains(arrayList2.get(i))){
arrayList2.remove(i);
}
}
print(arrayList1, arrayList2);
// list1 : [0, 1, 4]
// list2 : [Third, B, V]
}
private static void print(ArrayList arrayList1, ArrayList arrayList2) {
System.out.println("list1 : " + arrayList1);
System.out.println("list2 : " + arrayList2);
}
}
위치를 이동 시킨 후 새로운 배열을 생성하기 때문에 뒤에서부터 삭제하는 것이 좋다.
package List;
import java.util.ArrayList;
import java.util.List;
public class ArrayList2 {
public static void main(String[] args) {
final int LIMIT = 10;
String source = "0123456789abcdefgDFEFDAFDJ!L@#%$%$ZZSDF";
int length = source.length(); // 문자열의 길이
// 크기를 여유롭게 설정함
// 생설할 때 지정한 크기보다 더 많은 객체를 저장하면 자동적으로
// 크기가 늘어나기는 하지만 이 과정에서 처리시간이 많이 소요되기 때문이다.
List list = new ArrayList(length / LIMIT + 10);
// LIMIT 크기만큼 잘라서 리스트에 저장
for (int i=0; i<length; i+=LIMIT){
if(i+LIMIT<length){
list.add(source.substring(i, i+LIMIT));
}else{
list.add(source.substring(i));
}
}
for (int i=0; i<list.size(); i++){
System.out.println(list.get(i));
}
//
}
}
ArrayList
나Vector
같이 배열을 이용한 자료구조는 데이터를 저장하는데는 효율적이지만, 용량을 변경해야 할 때는 새로운 배열을 생성한 후 기존 배열에서 새로 생성된 배열로 복사해야되기 때문에 상당히 효율이 떨어진다.
따라서 충분한 용량의 인스턴스를 생성하는 것이 좋다.
ArrayList
의 단점크기를 변경하기 힘들다, 비순차적인 데이터의 추가 또는 삭제에 시간이 많이 걸린다.(why? 배열의 중간에 데이터를 추가하려면 빈자리를 만들기 위해 데이터를 복사해서 이동해야한다.)
단 하나의 참조만 변경하면 삭제가 이루어지므로 배열처럼 데이터를 이동하기 위해 복사하는 과정이 없기 때문에 처리속도가 매우 빠르다.
버려진 요소는 가비지컬렉터가 수거해간다.
새로운 데이터를 추가할 때는 새로운 요소를 참조변경하기만 하면 되므로 처리 속도가 매우 빠르다.
더블 링크드 리스트의 접근성을 보다 향상시킨 것이 '더블 써큘러 링크드 리스트(이중 원형 연결리스트, doubly circular linked list)'이다. 이는 단순히 더블 링크드 리스트의 첫 번째 요소와 마지막 요소를 서로 연결시킨 것이다.
이렇게 하면 마지막요소의 다음요소가 첫번째 요소가 되고, 첫 번째 요소의 이전 요소가 마지막 요소가 된다.
실제로 구현된 LinkedList 클래스는 이름과 달리 '링크드 리스트'가 아닌 더블 링크드 리스트로 구현되어 있다.
package List;
import java.util.*;
public class ArrayListLinkedList1 {
public static void main(String[] args) {
ArrayList al = new ArrayList(200000);
LinkedList li = new LinkedList();
System.out.println("==순차적으로 추가하기==");
System.out.println("ArrayList : " + add1(al));
System.out.println("LinkedList : " + add1(li));
System.out.println();
System.out.println("==중간에 추가하기==");
System.out.println("ArrayList : " + add2(al));
System.out.println("LinkedList : " + add2(li));
System.out.println();
System.out.println("==중간에서 삭제하기==");
System.out.println("ArrayList : " + remove1(al));
System.out.println("LinkedList : " + remove1(li));
System.out.println();
//
// System.out.println("==순차적으로 삭제하기==");
// System.out.println("ArrayList : " + remove2(al));
// System.out.println("LinkedList : " + remove2(li));
// System.out.println();
}
private static long remove1(List list) {
long start = System.currentTimeMillis();
for(int i = list.size()-1; i>=0; i--){
list.remove(i);
}
long end = System.currentTimeMillis();
return end - start;
}
private static long add2(List list) {
long start = System.currentTimeMillis();
for(int i = 0; i<10000; i++){
list.add(500, "X");
}
long end = System.currentTimeMillis();
return end - start;
}
private static long add1(List list) {
long start = System.currentTimeMillis();
for(int i = 0; i<1000000; i++){
list.add(i + "");
}
long end = System.currentTimeMillis();
return end - start;
}
private static long remove2(List list) {
long start = System.currentTimeMillis();
for(int i=0; i<10000; i++){
list.remove(i);
}
long end = System.currentTimeMillis();
return end - start;
}
}
==순차적으로 추가하기==
ArrayList : 50
LinkedList : 183
==중간에 추가하기==
ArrayList : 1144
LinkedList : 9
==중간에서 삭제하기==
ArrayList : 6
LinkedList : 17
==순차적으로 추가하기==
ArrayList : 51
LinkedList : 179
==중간에 추가하기==
ArrayList : 1175
LinkedList : 9
==순차적으로 삭제하기==
ArrayList : 1150
LinkedList : 99
package List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ArrayListLinkedList2 {
public static void main(String[] args) {
ArrayList al = new ArrayList(1000000);
LinkedList li = new LinkedList();
add(al);
add(li);
System.out.println("==접근 시간 테스트==");
System.out.println("ArrayList : " + access(al));
System.out.println("LinkedList : " + access(li));
System.out.println();
}
private static long access(List list) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
list.get(i);
}
long end = System.currentTimeMillis();
return end - start;
}
private static void add(List list) {
for(int i=0; i<100000; i++){
list.add(i + "");
}
}
}
==접근 시간 테스트==
ArrayList : 0
LinkedList : 77
- LinkedList는 불연속적으로 위치한 각 요소들이 서로 연결된 것이라 처음부터 n번째 데이터까지 차례대로 따라가야만 원하는 값을 얻을 수 있다.
- 따라서 LinkedList는 저장해야하는 데이터의 개수가 많아질수록 데이터를 읽어 오는 시간, 즉 접근시간(access time)이 길어진다는 단점이 있다.
ArrayList | LinkedList | |
---|---|---|
읽기(접근시간) | 빠름 | 느림 |
추가/삭제 | 느림 | 빠름 |
비고 | 순차적인 추가삭제는 더 빠름, 비효율적인 메모리 사용 | 데이터가 많을수록 접근성이 떨어진다 |
package StackQueue;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class StackQueue1 {
public static void main(String[] args) {
Stack st = new Stack();
Queue q = new LinkedList();
st.push("0");
st.push("1");
st.push("2");
q.offer("0");
q.offer("1");
q.offer("2");
System.out.println("= Stack =");
while(!st.empty()){
System.out.println(st.pop());
}
System.out.println("= Queue =");
while (!q.isEmpty()){
System.out.println(q.poll());
}
}
}
= Stack =
2
1
0
= Queue =
0
1
2
package StackQueue;
import java.util.PriorityQueue;
import java.util.Queue;
public class PriorityQueue1 {
public static void main(String[] args) {
Queue pq = new PriorityQueue();
pq.offer(15);
pq.offer(9);
pq.offer(7);
pq.offer(8);
pq.offer(10);
System.out.println(pq);
Object obj = null;
// Queue에서 .poll() 객체를 꺼내서 반환
while((obj=pq.poll()) != null){
System.out.println(obj);
}
System.out.println(pq);
}
}
[7, 8, 9, 15, 10]
7
8
9
10
15
[]
Integer -> 오름차순 정렬 (Comparable 인터페이스 -> int compareTo
HashSet
, TreeSet
package Set;
import java.util.*;
public class HashSet1 {
public static void main(String[] args) {
// Object타입의 배열
Object[] obj = {"1", Integer.valueOf(5), "2", "4", Double.valueOf(1.5), "1", "2", 5};
Set set = new HashSet();
for(int i=0; i<obj.length; i++){
set.add(obj[i]);
}
System.out.println(set);
}
}
[1, 2, 4, 5, 1.5]
중복이 제거됨
💡 이진 검색 트리
- 모든 노드는 최대 두 개의 자식노드를 가질 수 있다.
- 왼쪽 자식노드의 값은 부모노드의 값보다 작고 오른쪽자식노드의 값은 부모노드의 값모다 커야 한다.
- 노드의 추가 삭제에 시간이 걸린다.(순차적으로 저장하지 않으므로)
- 검색(범위검색)과 정렬에 유리하다.
- 중복된 값을 저장하지 못한다.
package Set;
import java.util.Set;
import java.util.TreeSet;
public class TreeSet1 {
public static void main(String[] args) {
TreeSet set = new TreeSet();
int[] score = {80, 95, 50, 35, 45, 65, 10, 100};
for(int i=0; i<score.length; i++){
set.add(Integer.valueOf(score[i]));
}
// .headSet -> 지정된 객체보다 작은 값 반환
System.out.println("50보다 작은 값 : " + set.headSet(Integer.valueOf(50)));
// .tailSet -> 지정된 객체보다 큰 값 반환
System.out.println("50보다 큰 값 : " + set.tailSet(Integer.valueOf(50)));
}
}
50보다 작은 값 : [10, 35, 45]
50보다 큰 값 : [50, 65, 80, 95, 100]
package Set;
import java.util.TreeSet;
public class TreeSet2 {
public static void main(String[] args) {
TreeSet set = new TreeSet();
String from = "b";
String to = "d";
set.add("abc");
set.add("BAT");
set.add("car");
set.add("Car");
set.add("dance");
set.add("dZZZ");
set.add("fan");
System.out.println(set);
System.out.println("range search : from " + from + " to " + to);
// .subSet -> 범위검색(fromElement와 toElement사이)의 결과를 반환
// (끝 범위인 toElement는 범위에 포함되지 않음)
System.out.println("result1 : " + set.subSet(from, to));
System.out.println("result2 : " + set.subSet(from, to + "zzz"));
}
}
result1 : [car]
result2 : [car, dZZZ, dance]
"hash"는 검색을 의미함
HashMap
, TreeMap
, Hashtable
, Properties
Map에 저장되는 key-value쌍을 다루기 위해 내부적으로 Entry(데이터) 인터페이스를 정의해 놓은 것
// Map 인터페이스의 소스코드 일부
public interface Map {
...
interface Entry {
Object getKey();
Object getValue();
Object setValue(Object value);
boolean equals(Object o);
int hashCode();
...
}
}
public class HashMap extends AbstractMap implements Map, Cloneable, Serializable {
transient Entry[] table;
...
static class Entry implements Map.Entry {
final Object key;
Object value;
...
}
...
}
package Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Scanner;
public class HashMap1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("dani", "dani");
map.put("HKD", "1234");
map.put("Id", "Pw");
map.put("LLL", "12345");
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("아이디를 입력하세요");
String id = sc.nextLine().trim(); // .trim() -> 공백제거
if(map.containsKey(id)) {
System.out.println("비밀번호를 입력하세요");
String pw = sc.nextLine().trim();
if(map.get(id).equals(pw)){
System.out.println("로그인 성공!");
break;
}
}else{
System.out.println("존재하지 않는 아이디입니다.");
}
}
}
}
50보다 작은 값 : [10, 35, 45]
50보다 큰 값 : [50, 65, 80, 95, 100]
package Map;
import java.util.*;
public class HashMap2 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("이병헌", Integer.valueOf(80));
map.put("김태리", Integer.valueOf(100));
map.put("유연석", Integer.valueOf(10));
Set set = map.entrySet(); // Map.Entry타입의 객체로 저장한 Set으로 반환
// Iterator : 컬렌션에 저장된 요소를 접근하는데 사용하는 인터페이스
// 컬렉션클래스에 .iterator()를 호출하여 반복문을 통해 클래스의 요소를 읽어온다.
Iterator iterator = set.iterator();
System.out.println(set); // 키=값
// hasNext() -> 읽어 올 요소가 남아 있는지
while (iterator.hasNext()){
// next() -> 다음 요소를 읽어온다
Map.Entry e = (Map.Entry) iterator.next();
System.out.println("이름 : " + e.getKey() + " 점수 : " + e.getValue());
}
set = map.keySet(); // 맵의 key값만 가져옴
System.out.println("참가자 명단 : " + set);
Collection values = map.values(); // 맵의 value만 가져옴
Iterator iterator1 = values.iterator();
int total = 0;
while (iterator1.hasNext()){
Integer i = (Integer) iterator1.next();
total += i.intValue();
}
System.out.println("총점 : " + total);
System.out.println("평균 : " + (float) total / set.size());
System.out.println("최고 점수 : " + Collections.max(values));
System.out.println("최저 점수 : " + Collections.min(values));
}
}
[김태리=100, 유연석=10, 이병헌=80]
이름 : 김태리 점수 : 100
이름 : 유연석 점수 : 10
이름 : 이병헌 점수 : 80
참가자 명단 : [김태리, 유연석, 이병헌]
총점 : 190
평균 : 63.333332
최고 점수 : 100
최저 점수 : 10
package Map;
import java.util.*;
public class TreeMap1 {
public static void main(String[] args) {
String[] data = {"I", "P","P","P","P","P", "A", "D", "I", "P", "A", "D", "D", "A", "A", "A"};
TreeMap map = new TreeMap();
for(int i=0; i<data.length; i++){
if(map.containsKey(data[i])){
// 인티저 타입으로 변환
// .get()은 반환타입이 Object임
Integer integer = (Integer) map.get(data[i]);
map.put(data[i], integer + Integer.valueOf(1));
}else{
map.put(data[i], Integer.valueOf(1));
}
}
Iterator iterator = map.entrySet().iterator();
System.out.println("== 기본 정렬 ==");
while (iterator.hasNext()){
Map.Entry e = (Map.Entry ) iterator.next();
int i = ((Integer) e.getValue()).intValue();
System.out.println(e.getKey() + " : " + printBar('#', i) + " " + i);
}
System.out.println();
Set set = map.entrySet();
List list = new ArrayList(set);
Collections.sort(list, new ValueComparator());
iterator = list.iterator();
System.out.println("==값의 크기가 큰 순서로 정렬 ==");
while (iterator.hasNext()){
Map.Entry e = (Map.Entry ) iterator.next();
int i = ((Integer) e.getValue()).intValue();
System.out.println(e.getKey() + " : " + printBar('#', i) + " " + i);
}
}
static class ValueComparator implements Comparator{
public int compare(Object o1, Object o2){
if(o1 instanceof Map.Entry<?,?> && o2 instanceof Map.Entry){
Map.Entry e1 = (Map.Entry) o1;
Map.Entry e2 = (Map.Entry) o2;
int v1 = ((Integer)e1.getValue()).intValue();
int v2 = ((Integer)e2.getValue()).intValue();
return v2 - v1;
}
return -1;
}
}
private static Object printBar(char c, int a) {
char[] ch = new char[a];
for(int i=0; i< ch.length; i++){
ch[i] = c;
}
return new String(ch);
}
}
== 기본 정렬 ==
A : ##### 5
D : ### 3
I : ## 2
P : ###### 6
==값의 크기가 큰 순서로 정렬 ==
P : ###### 6
A : ##### 5
D : ### 3
I : ## 2
💡 intvalue() vs parseInt()
✅ parseInt()
static 이므로 Integer를 생성하지 않는다.
Integer 생성안한다 == 즉 Integer(Object) 라는 박스를 만들지 않고 (래퍼 클래스와 언박싱,박싱) 내용물(String) -> 내용물(int) 교체한다.✅ intvalue()
static이 아니고 Integer(Object)에서 int로 값을 추출해낸다. 객체(Integer) -> 내용물(int)
package Map;
import java.util.Enumeration;
import java.util.Properties;
public class Properties1 {
public static void main(String[] args) {
Properties prop = new Properties();
// prop에 키와 값을 저장한다.
prop.setProperty("time", "시간");
prop.setProperty("apple", "사과");
prop.setProperty("banana", "바나나");
// key값을 불러옴
Enumeration enumeration = prop.propertyNames();
while (enumeration.hasMoreElements()){
String s = (String) enumeration.nextElement();
System.out.println(s + "=" + prop.getProperty(s));
}
// apple=사과
// banana=바나나
// time=시간
System.out.println();
prop.setProperty("time", "시각"); // 값을 변경한다
System.out.println(prop); // prop 출력
// {banana=바나나, apple=사과, time=시각}
prop.list(System.out);// prop에 저장된 요소들을 화면(System.out)에 출력한다.
// -- listing properties --
// banana=바나나
// apple=사과
// time=시각
}
}
apple=사과
banana=바나나
time=시간
{banana=바나나, apple=사과, time=시각}
-- listing properties --
banana=바나나
apple=사과
time=시각
/**
* 1. 작성자 : Dani
* 2. 작성일 : 12월 10일
* 3. Properties 기본 문법
* a.HashMap의 구버전인 Hashtable을 상속받아 구현한 것
* b.Properties는 (String, String)의 형태로 저장
* c.애플리케이션의 환경 설정과 관련된 속성(property)를
* 저장하는데 사용되며 데이터를 파일로 부터 읽고 쓰는 편리한 기능을 제공
*/
package Basics;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class PropertiesEx1 {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
prop.setProperty("key1", "value1");
prop.setProperty("key2", "value2");
prop.setProperty("key3", "value3");
prop.list(System.out);
// 파일을 생성함
prop.store(new FileWriter("test.properties"), "(❁´◡`❁)");
prop.storeToXML(new FileOutputStream("test.xml"), "(❁´◡`❁)");
// 파일을 읽어옴
prop.loadFromXML(new FileInputStream("test.xml"));
prop.list(System.out);
}
}
-- listing properties --
key1=value1
key2=value2
key3=value3
-- listing properties --
key1=value1
key2=value2
key3=value3
파일이 생성된다.
Arrays
가 배열과 관련된 메서드를 제공하는 것처럼, Collections
는 컬렉션과 관련된 메서드를 제공한다. java.util.Collections
fill()
, copy()
, sort()
, binarySearch()
등의 메서드는 두 클래스 모두 포함되어 있고 같은 기능을 한다.편의기능이라
static
이며final
클래스로 되어 있다. 상속을 하지 않음
static Collection synchronizedCollection(Collection c)
static List synchronizedList(List list)
static Set sychronizedSet(Set s)
static Map sychronizedMap(Map m)
static SortedSet synchronizedSortedSet(SortedSet s)
static SortedMap synchronizedSortedMap(SortedMap m)
멀티쓰레드 프로그래밍에서 여러 쓰레드가 하나의 객체에 동시에 접근할 수 있기 때문에 데이터의 일관성을 유지하기 위해서 동기화가 필요하다.
static Collection unmodifiableCollection(Collection c)
static List unmodifiableList(List list)
static Set unmodifiableSet(Set s)
static Map unmodifiableMap(Map m)
static NavigableSet unmodifiableNavigableSet(NavigableSet s)
static SortedSet unmodifiableSortedSet(SortedSet s)
static NavigableMap unmodifiableNavigableMap(NavigableMap m)
static SortedMap unmodifiableSortedMap(SortedMap m)
static List singletonList(Object o)
static Set singleton(Object o)
static Map singletonMap(Object key, Object value)
static Collection checkedCollection(Collection c, Class type)
static List checkedList(List list, Class type)
static Set checkedSet(Set s, Class type)
static Map checkedMap(Map m, Class keyType, Class valueType)
static Queue checkedQueue(Queue queue, Class type)
static NavigableSet checkedNavigableSet(NavigableSet s, Class type)
static SortedSet checkedSortedSet(SortedSet s, Class type)
static NavigableMap checkedNavigableMap(NavigableMap m, Class keyType, Class valueType)
static SortedMap checkedSortedMap(SortedMap m, Class keyType, Class valueType)
참고 :
https://www.tcpschool.com/java/java_collectionFramework_concept
https://hudi.blog/java-collection-framework-1/
https://coding-factory.tistory.com/550
https://wikidocs.net/122193#get