# Table of Contents

# 컬렉션 프레임워크

Java API는 같은 타입의 여러 데이터를 한꺼번에 효율적으로 관리하기 위해 컬렉션 프레임워크(Collection Framework)를 제공한다.

컬렉션 프레임워크는 java.util 패키지에 포함되어있으며, 가장 중요한 요소는 List, Set, Map이다.

# List

배열은 생성할 때 크기가 결정된다. 따라서 배열이 생성되면 동적으로 데이터를 추가하거나 삭제할 수 없다. 데이터를 동적으로 추가, 삭제하려면 List를 사용해야한다.

List의 특징은 다음과 같다.

  • 순서가 있다.
  • 중복을 허용한다.

컬렉션 프레임워크가 제공하는 List의 정의는 다음과 같다.

public interface List<E> extends Collection<E> {
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    boolean add(E e);
    boolean remove(Object o);
    E set(int index, E element);
    E get(int index);
    ...
}

List는 인터페이스이므로 인스턴스를 생성할 수 없으며, 구현체가 필요하다. 자바 API에서는 Vector, ArrayList, LinkedList 등 다양한 구현체를 제공한다.

# Vector

Vector는 다음과 같이 선언하고 초기화한다.

Vector<String> vector = new Vector<String>();

다음과 같이 요소를 추가한다.

vector.add("Joey");
vector.add("Chandler");
vector.add("Ross");

System.out.println(vector.toString());   // [Joey, Chandler, Ross]

요소 값에 접근할 수 있다.

vector.get(0);   // Joey

요소 값을 변경할 수 있다.

vector.set(0, "Rachel");

다음과 같이 요소를 삭제할 수 있다.

// 인덱스로 삭제
vector.remove(0);

// 요소로 삭제
vector.remove("Ross");

# ArrayList

ArrayList는 가장 많이 사용되는 List의 구현체다. ArrayList는 다음과 같이 선언하고 초기화한다.

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

다음과 같이 요소를 추가한다.

list.add("Ronaldo");
list.add("Benzema");
list.add("Bale");

System.out.println(list.toString());    // [Ronaldo, Benzema, Bale]

요소 값에 접근할 수 있다.

System.out.println(list.get(0));        // Son

요소 값을 변경할 수 있다.

list.set(0, "Son");

System.out.println(list.toString());    // [Son, Bale]

다음과 같이 요소를 삭제할 수 있다.

list.remove(0);

list.remove("Ronaldo");

# LinkedList

LinkedList는 다음과 같이 선언하고 초기화한다.

List<String> linkedList = new LinkedList<String>();

다음과 같이 요소를 추가한다.

linkedList.add("Chandler");
linkedList.add("Ross");
linkedList.add("Joey");

System.out.println(linkedList.toString());  // [Chandler, Ross, Joey]

요소 값에 접근할 수 있다.

linkedList.get(1);

요소를 변경할 수 있다.

linkedList.set(0, "Monica");

요소를 삭제할 수 있다.

// 요소로 삭제
linkedList.remove("Ross");

// 인덱스로 삭제
linkedList.remove(0);

# Vector vs. ArrayList

Vector는 동기화가 되어있다. 다시 말해 한 순간에 하나의 스레드만 접근할 수 있기 때문에 스레드에 안전(Thread Safe)하다. 대신 속도가 느리다. 반면 ArrayList는 동기화되지 않았기 때문에 동시에 여러 스레드가 접근할 수 있으며 속도가 빠르다.

따라서 싱글 스레드 환경에서는 ArrayList를 사용하고 멀티 스레드 환경에서는 Vector를 사용하는 것이 바람직하다.

# ArrayList vs. LinkedList

ArrayList는 내부적으로 배열을 사용한다.

public class ArrayList<E> {
    transient Object[] elementData; 
    private int size;
    // 생략 ...
}

ArrayList를 생성할 때 내부적으로 크기가 고정된 배열을 생성한다. 그리고 고정된 배열이 꽉 차면 더 큰 크기의 새로운 배열을 생성하고 복사한다.

반면 LinkedList는 내부적으로 Node를 사용한다.

public class LinkedList<E> {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    // 생략 ...
}

보통 데이터를 순차적으로 추가, 삭제하는 경우 ArrayList가 더 빠르다. 반면 데이터를 중간에 추가, 삭제하는 경우 LinkedList가 더 빠르다. 따라서 상황에 따라 적절한 것을 선택하자.

# Array로 List 생성하기

Arrays.asList()를 사용하면 ArrayList를 생성할 수 있다.

List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));

# List를 특정 크기의 값으로 초기화하기

Collections.nCopies()메소드를 사용하면 List를 특정 크기의 값으로 쉽게 초기화할 수 있다.

 



List<Integer> list = new ArrayList<Integer>(Collections.nCopies(10, 1));

System.out.println(list.toString());    // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

# List 깊은 복사

ArrayList 생성자에 기존 List를 전달하는 방식으로 깊은 복사르르 할 수 있다.







 







List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);

// 깊은 복사
List<Integer> copy = new ArrayList<Integer>(list);
copy.add(4);

System.out.println(list.toString());    // [1, 2, 3]
System.out.println(copy.toString());    // [1, 2, 3, 4]
System.out.println(list.hashCode());    // 30817
System.out.println(copy.hashCode());    // 955331

# List 메소드 정리

List인터페이스가 제공하는 메소드는 다음과 같다.

# List.add()

List.add()를 사용하면 List의 맨 끝에 요소를 추가할 수 있다.

List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));

list.add(6);

System.out.println(list);  // [1, 2, 3, 4, 5, 6]

첫 번째 인자로 인덱스를 지정할 수 있다.

List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));

list.add(0, 20);

System.out.println(list);  // [20, 1, 2, 3, 4, 5]

# List.addAll()

List.addAll()를 사용하면 ListList에 추가할 수 있다.

List<Integer> list1 = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList(Arrays.asList(6, 7, 8, 9, 10));

list1.addAll(list2);
System.out.println(list1);  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# List.get()

특정 인덱스의 요소를 가져올 수 있다.

List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));

System.out.println(list.get(0));    // 1

# List.set()

특정 인덱스의 요소를 변경할 수 있다.

List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
list.set(0, 10);

System.out.println(list);   // [10, 2, 3, 4, 5]

# List.remove()

특정 요소를 삭제할 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Kane"));

// 인덱스로 삭제
list.remove(0);

// 요소로 삭제
list.remove("Kane");

System.out.println(list);    // [John, Smith]

# List.indexOf()

특정 요소의 첫 번째 인덱스를 가져올 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

System.out.println(list.indexOf("Paul"));       // 0

# List.lastIndexOf()

특정 요소의 마지막 인덱스를 가져올 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

System.out.println(list.lastIndexOf("Paul"));   // 3

# List.size()

List의 크기를 가져올 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

System.out.println(list.size());           // 4

# List.forEach()

List를 순회할 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

list.forEach((String item) -> {
    // ...
});

# List.contains()

요소 포함 여부를 확인할 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

if (list.contains("Paul")) {
    // ...
}

# List.isEmpty()

List가 비어있는지 확인할 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

if (list.isEmpty()) {
    // ...
}

# List.subList()

List의 일부분으로 새로운 List를 생성할 수 있다.

List<String> list = Arrays.asList("Paul", "John", "Smith", "Monica", "Chandler", "Ross");
List<String> subList = list.subList(1, 4);      // [John, Smith, Monica]

# List.clear()

List를 클리어할 수 있다.

List<String> list = new ArrayList<>();
list.add("john");
list.add("paul");
list.add("smith");
System.out.println(list);   // [john, paul, smith]

list.clear();
System.out.println(list);   // []

# List.toArray()

List를 배열로 변환할 수 있다.

List<String> list = new ArrayList(Arrays.asList("Paul", "John", "Smith", "Paul"));

String[] arr = list.toArray(new String[list.size()]);   // [Paul, John, Smith, Paul]

# Set

Set은 다음과 같은 특징이 있는 자료구조다.

  • 순서가 없다.
  • 중복을 허용하지 않는다.

자바 API에서 제공하는 Set의 정의는 다음과 같다.

public interface Set<E> extends Collection<E> {
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    boolean add(E e);
    boolean remove(Object o);
    boolean removeAll(Collection<?> c);
    void clear();
}

Set은 인터페이스이므로 인스턴스를 생성할 수 없으며 구현체가 필요하다. 자바 API에서는 HashSet, TreeSet, LinkedHashSet이라는 구현체를 제공한다.

# TreeSet

TreeSet은 다음과 같이 생성한다.

Set<Integer> treeSet = new TreeSet<Integer>();

다음과 같이 데이터를 추가할 수 있다.

treeSet.add(1);

중복을 허용하지 않는 것을 확인할 수 있다.

Set<Integer> treeSet = new TreeSet<Integer>();

treeSet.add(3);
treeSet.add(1);
treeSet.add(1);
treeSet.add(1);
treeSet.add(2);
treeSet.add(2);

System.out.println(treeSet.toString());     // [1, 2, 3]

TreeSet은 내부적으로 Binary Search Tree를 사용하기 때문에 자동으로 정렬된다.

Set<Integer> treeSet = new TreeSet<>();

treeSet.add(3);
treeSet.add(8);
treeSet.add(90);
treeSet.add(6);
treeSet.add(65);
treeSet.add(1);

System.out.println(treeSet.toString());     // [1, 3, 6, 8, 65, 90]

# HashSet

HashSet은 다음과 같이 생성한다.

Set<Integer> hashSet = new HashSet<Integer>();

다음과 같이 데이터를 추가한다.

hashSet.add(1);

중복을 허용하지 않는 것을 확인할 수 있다.

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(3);
hashSet.add(1);
hashSet.add(1);
hashSet.add(1);
hashSet.add(2);
hashSet.add(2);

System.out.println(hashSet.toString());     // [1, 2, 3]

HashSet은 내부적으로 Hash Table을 사용하기 때문에 데이터가 임의로 저장된다.

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(3);
hashSet.add(8);
hashSet.add(90);
hashSet.add(6);
hashSet.add(65);
hashSet.add(1);

System.out.println(hashSet.toString());     // [65, 1, 3, 6, 8, 90]

# LinkedHashSet

LinkedHashSet은 다음과 같이 생성한다.

Set<Integer> linkedHashSet = new LinkedHashSet<>();

다음과 같이 데이터를 추가한다.

linkedHashSet.add(1);

LinkedHashSet 역시 중복을 허용하지 않는다.

Set<Integer> linkedHashSet = new LinkedHashSet<>();

linkedHashSet.add(3);
linkedHashSet.add(1);
linkedHashSet.add(1);
linkedHashSet.add(2);
linkedHashSet.add(2);

System.out.println(linkedHashSet.toString());   // [3, 1, 2]

LinkedHashSet은 데이터의 삽입 순서를 보장한다.

Set<Integer> linkedHashSet = new LinkedHashSet<>();

linkedHashSet.add(3);
linkedHashSet.add(8);
linkedHashSet.add(90);
linkedHashSet.add(6);
linkedHashSet.add(65);
linkedHashSet.add(1);

System.out.println(linkedHashSet.toString());     // [3, 8, 90, 6, 65, 1]

# TreeSet vs. HashSet

TreeSet은 내부적으로 Binary Search Tree를 통해 구현되어있다. 따라서 데이터를 삽입, 삭제하거나 포함 여부를 확인할 때 O(log n)의 시간복잡도를 가진다. 또한 Binary Search Tree를 사용하기 때문에 데이터가 정렬된다.

Set<Integer> treeSet = new TreeSet<>();

treeSet.add(7);
treeSet.add(90);
treeSet.add(15);
treeSet.add(10);
treeSet.add(6);
treeSet.add(60);
treeSet.add(1);

System.out.println(treeSet);    // [1, 6, 7, 10, 15, 60, 90]

HashSet은 내부적으로 Hash Table을 통해 구현되어있다. 해시 함수를 통한 직접 접근을 하기 때문에 데이터를 삽입, 삭제, 탐색에 O(1)의 시간복잡도를 가진다. 물론 충돌로 인한 오버헤드가 발생할 수 있다. 그리고 데이터가 임의의 순서로 배치된다.

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(7);
hashSet.add(90);
hashSet.add(15);
hashSet.add(10);
hashSet.add(6);
hashSet.add(60);
hashSet.add(1);

System.out.println(hashSet);    // [1, 6, 7, 90, 10, 60, 15]

데이터의 삽입, 삭제, 탐색이 빈번할 때는 HashSet을 사용한다. 반면 정렬이 필요하면 LinkedHashSet이나 TreeSet을 사용한다.

# Set 순회

Set은 다음과 같은 방법으로 순회할 수 있다.











 
 
 

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(7);
hashSet.add(90);
hashSet.add(15);
hashSet.add(10);
hashSet.add(6);
hashSet.add(60);
hashSet.add(1);

for (Integer e: hashSet) {
    System.out.println(e);
}

# HashSet vs. LinkedHashSet

HashSet데이터 삽입 순서를 보장하지 않는다.

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(5);
hashSet.add(3);
hashSet.add(1);
hashSet.add(2);
hashSet.add(4);

System.out.println(hashSet.toString());   // [1, 2, 3, 4, 5]

반면 LinkedHashSet은 데이터 삽입 순서를 보장한다.

Set<Integer> linkedHashSet = new LinkedHashSet<>();

linkedHashSet.add(5);
linkedHashSet.add(3);
linkedHashSet.add(1);
linkedHashSet.add(2);
linkedHashSet.add(4);

System.out.println(linkedHashSet.toString());   // [5, 3, 1, 2, 4]

데이터 삽입 순서를 보장하려면 LinkedHashSet을 사용한다.

# Set 깊은 복사

Set의 깊은 복사는 다음과 같이 할 수 있다.








 








Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
set.add(3);

// 깊은 복사
Set<Integer> copy = new HashSet<>(set);
copy.add(4);

System.out.println(set.toString());     // [1, 2, 3]
System.out.println(copy.toString());    // [1, 2, 3, 4]

System.out.println(set.hashCode());     // 6
System.out.println(copy.hashCode());    // 10

# HashSet 정렬하기

TreeSet을 생성한 후 요소들을 삽입하는 방식으로 정렬할 수 있다.

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(3);
hashSet.add(8);
hashSet.add(90);
hashSet.add(6);
hashSet.add(65);
hashSet.add(1);

Set<Integer> treeSet = new TreeSet<Integer>();

for (Integer element: hashSet) {
    treeSet.add(element);
}

System.out.println(treeSet);   // [1, 3, 6, 8, 65, 90]

또는 HashSetList로 변환한 후 Collections.sort()메소드로 정렬할 수 있다.

Set<Integer> hashSet = new HashSet<Integer>();

hashSet.add(3);
hashSet.add(8);
hashSet.add(90);
hashSet.add(6);
hashSet.add(65);
hashSet.add(1);

List<Integer> list = new ArrayList<Integer>(hashSet);
Collections.sort(list);

System.out.println(list);   // [1, 3, 6, 8, 65, 90]

# Set의 메소드

Set이 제공하는 주요 메소드는 다음과 같다.

# Set.add()

Set.add()는 데이터를 추가할 때 사용한다.

Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
set.add(3);

System.out.println(set);    // [1, 2, 3]

Set.add()는 불리언 타입의 데이터를 반환한다. 이를 통해 데이터 삽입 여부를 확인할 수 있다.

Set set = new HashSet();
System.out.println(set.add(1)); // true
System.out.println(set.add(1)); // false
System.out.println(set.add(1)); // false
System.out.println(set.add(2)); // true
System.out.println(set.add(2)); // false
System.out.println(set.add(3)); // true

# Set.remove()

특정 요소를 삭제할 수 있다.

set.remove(3);

# Set.contains()

특정 요소의 포함 여부를 확인할 수 있다.

if (set.contains(3)) {
    // ...
}

# Set.isEmpty()

Set이 비어있는지 확인할 수 있다.

if (set.isEmpty()) {
    // ...
}

# Set.size()

요소의 개수를 반환한다.

if (set.size() > 10) {
    // ...
}

# Set.forEach()

set.forEach((Integer element) -> {
    // ...
});
for (Integer element: set) {
    // ...
}

# Set.clear()

Set을 클리어할 수 있다.

Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
set.add(3);

System.out.println(set);  // [1, 2, 3]

set.clear();

System.out.println(set);  // []

# Map

맵(Map)Key-Value로 구성된 데이터 집합이다. 키를 통해 값을 저장하거나 읽어오거나 변경하거나 삭제할 수 있다. Map의 특징은 다음과 같다.

  • 키(Key)에는 중복된 값이 입력될 수 없다.
  • 값(Value)는 중복된 값이 입력될 수 있다.

자바 API에서 제공하는 Map의 정의는 다음과 같다.

public interface Map<K, V> {
    int size();
    boolean isEmpty();
    V get(Object key);
    V put(K key, V value);
    V remove(Object key);
    void putAll(Map<? extends K, ? extends V> m);
    void clear();
    ... 

    interface Entry<K,V> {
        K getKey();
        V getValue();    
        // ...
    }
}

Map은 인터페이스이므로 인스턴스를 생성할 수 없으며 구현체가 필요하다. Java API에서는 HashMap, TreeMap, LinkedMap이라는 구현체를 제공한다.

# TreeMap

TreeMap은 다음과 같이 선언하고 초기화한다.

Map<Integer, String> players = new TreeMap<Integer, String>();

다음과 같이 데이터를 추가한다.

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

players.toString(); // {7=Ronaldo, 9=Benzema, 11=Bale}

다음과 같이 데이터에 접근할 수 있다.

players.get(7); // Ronaldo

다음과 같이 데이터를 변경할 수 있다.

players.replace(7, "Son");

System.out.println(players.toString()); // {7=Son, 9=Benzema, 11=Bale}

다음과 같이 데이터를 삭제할 수 있다.

players.remove(7);
System.out.println(players.toString()); // {9=Benzema, 11=Bale}

TreeMap은 기본적으로 키 값을 기준으로 오름차순으로 정렬한다.

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

players.toString(); // {7=Ronaldo, 9=Benzema, 11=Bale}

다음과 같이 내림차순으로 정렬할 수도 있다.

Map<Integer, String> players = new TreeMap<Integer, String>(Collections.reverseOrder());

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

System.out.println(players.toString()); // {11=Bale, 9=Benzema, 7=Ronaldo}

키가 객체인 경우 ComparatorComparable을 사용할 수 있다.

Map<Person, String> players = new TreeMap<Person, String>(new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
});

players.put(new Person("paul", 35), "paul");
players.put(new Person("smith", 25), "smith");
players.put(new Person("john", 45), "paul");

System.out.println(players.toString()); // {{name: smith, age: 25}=smith, {name: paul, age: 35}=paul, {name: john, age: 45}=paul}

# HashMap

HashMap은 다음과 같이 선언하고 초기화한다.

Map<Integer, String> players = new HashMap<Integer, String>();

다음과 같이 데이터를 추가한다.

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

System.out.println(players.toString()); // {7=Ronaldo, 9=Benzema, 11=Bale}

다음과 같이 데이터에 접근할 수 있다.

players.get(7); // Ronaldo

다음과 같이 데이터를 변경할 수 있다.

players.replace(7, "Son");

System.out.println(players.toString()); // {7=Son, 9=Benzema, 11=Bale}

다음과 같이 데이터를 삭제할 수 있다.

players.remove(7);

System.out.println(players.toString()); // {9=Benzema, 11=Bale}

# LinkedHashMap

LinkedHashMap은 다음과 같이 선언하고 초기화한다.

Map<Integer, String> players = new LinkedHashMap<Integer, String>();

다음과 같이 데이터를 추가한다.

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

System.out.println(players.toString()); // {7=Ronaldo, 9=Benzema, 11=Bale}

다음과 같이 데이터에 접근할 수 있다.

players.get(7); // Ronaldo

다음과 같이 데이터를 변경할 수 있다.

players.replace(7, "Son");

System.out.println(players.toString()); // {7=Son, 9=Benzema, 11=Bale}

다음과 같이 데이터를 삭제할 수 있다.

players.remove(7);

System.out.println(players.toString()); // {9=Benzema, 11=Bale}

# TreeMap vs. HashMap

TreeMap은 내부적으로 Binary Search Tree를 통해 구현되어있다.

public class TreeMap<K,V> {

    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        // ...
    }

    private transient Entry<K,V> root;

    // ..
}

따라서 데이터를 삽입, 삭제하거나 포함 여부를 확인할 때 O(log n)의 시간복잡도를 가진다. 또한 Binary Search Tree를 사용하기 때문에 데이터가 정렬된다.

반면 HashMap은 내부적으로 Hash Table을 통해 구현되어있다. 또한 충돌 해결 알고리즘으로 Chaining(LinkedList)를 사용한다.

public class HashMap<K,V> {

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    transient Node<K,V>[] table;

    // ...
}

HashMap은 데이터를 삽입할 때 hashcode()값을 해시 테이블의 인덱스 주소값으로 사용한다. 또한 충돌을 판별할 때 equal()을 사용한다. 따라서 충돌을 줄이고 성능을 향상시키려면 객체의 hashcode()equal()메소드를 잘 정의하는 것이 중요하다.

HashMap은 데이터를 삽입, 삭제하거나 포함여부를 확인할 때 O(1)의 시간복잡도를 가진다. 또한 데이터가 임의의 순서로 배치된다.

요약하자면 데이터의 삽입, 삭제, 포함여부 확인이 빈번할 때는 HashMap을 사용한다. 반면 정렬이 필요하면 TreeMap을 사용한다.

# Map 순회

Map.keySet()을 사용하여 Map을 순회할 수 있다.






 
 
 

Map<Integer, String> players = new HashMap<Integer, String>();
players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

for (Integer key: players.keySet()) {
    System.out.println(players.get(key));
}

Map.entrySet()을 사용할 수도 있다.






 
 
 

Map<Integer, String> players = new HashMap<Integer, String>();
players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

for (Map.Entry<Integer, String> player: players.entrySet()) {
    System.out.println(player.getKey() + " : " + player.getValue());
}

# HashMap vs. LinkedHashMap

HashMap데이터 삽입 순서를 보장하지 않는다.

Map<Integer, String> hashMap = new HashMap<Integer, String>();

hashMap.put(11, "Bale");
hashMap.put(7, "Ronaldo");
hashMap.put(1, "Courtois");
hashMap.put(9, "Benzema");
hashMap.put(4, "Ramos");
hashMap.put(8, "Kroos");

System.out.println(hashMap.toString()); 
// {1=Courtois, 4=Ramos, 7=Ronaldo, 8=Kroos, 9=Benzema, 11=Bale}

반면 LinkedHashMap데이터 삽입 순서를 보장한다.

Map<Integer, String> linkedHashMap = new LinkedHashMap<>();

linkedHashMap.put(11, "Bale");
linkedHashMap.put(7, "Ronaldo");
linkedHashMap.put(1, "Courtois");
linkedHashMap.put(9, "Benzema");
linkedHashMap.put(4, "Ramos");
linkedHashMap.put(8, "Kroos");

System.out.println(linkedHashMap.toString()); 
// {11=Bale, 7=Ronaldo, 1=Courtois, 9=Benzema, 4=Ramos, 8=Kroos}

# Map 깊은 복사

Map의 깊은 복사는 다음과 같이 할 수 있다.






 








Map<String, String> map = new HashMap<String, String>();
map.put("name", "Paul");
map.put("nation", "USA");

// 깊은 복사
Map<String, String> copy = new HashMap<String, String>(map);
copy.put("job", "Programmer");

System.out.println(map.toString());     // {nation=USA, name=Paul}
System.out.println(copy.toString());    // {name=Paul, job=Programmer, nation=USA}

System.out.println(map.hashCode());     // -1051088633
System.out.println(copy.hashCode());    // 974907474

# HashMap을 TreeMap으로 변환

HashMap은 다음과 같은 방법으로 TreeMap으로 변환할 수 있다.










 


 
 
 



HashMap<Person, String> hashMap = new HashMap<Person, String>();

hashMap.put(new Person("paul", 35), "paul");
hashMap.put(new Person("smith", 25), "smith");
hashMap.put(new Person("john", 45), "paul");

System.out.println(hashMap.toString());     // {{name: paul, age: 35}=paul, {name: smith, age: 25}=smith, {name: john, age: 45}=paul}

// TreeMap 생성
TreeMap<Person, String> treeMap = new TreeMap<Person, String>((p1, p2) -> p1.getAge() - p2.getAge());

// HashMap의 요소를 TreeMap에 삽입
for (Map.Entry<Person, String> entry : hashMap.entrySet()) {
    treeMap.put(entry.getKey(), entry.getValue());
}

System.out.println(treeMap.toString());     // {{name: smith, age: 25}=smith, {name: paul, age: 35}=paul, {name: john, age: 45}=paul}

# TreeMap을 HashMap으로 변환

TreeMap은 다음과 같은 방법으로 HashMap으로 변환할 수 있다.









 



TreeMap<Person, String> treeMap = new TreeMap<Person, String>((p1, p2) -> p1.getAge() - p2.getAge());

treeMap.put(new Person("paul", 35), "paul");
treeMap.put(new Person("smith", 25), "smith");
treeMap.put(new Person("john", 45), "paul");

System.out.println(treeMap.toString());     // {{name: smith, age: 25}=smith, {name: paul, age: 35}=paul, {name: john, age: 45}=paul}

HashMap<Person, String> hashMap = new HashMap<Person, String>(treeMap);

System.out.println(hashMap.toString());     // {{name: john, age: 45}=paul, {name: smith, age: 25}=smith, {name: paul, age: 35}=paul}

# HashMap를 Key로 정렬하기

새로운 TreeMap을 생성한 후 요소를 추가하는 방식으로 HashMap을 정렬할 수 있다.










 


 
 
 



HashMap<Person, String> hashMap = new HashMap<Person, String>();

hashMap.put(new Person("paul", 35), "paul");
hashMap.put(new Person("smith", 25), "smith");
hashMap.put(new Person("john", 45), "paul");

System.out.println(hashMap.toString());     // {{name: paul, age: 35}=paul, {name: smith, age: 25}=smith, {name: john, age: 45}=paul}

// TreeMap 생성
TreeMap<Person, String> treeMap = new TreeMap<Person, String>((p1, p2) -> p1.getAge() - p2.getAge());

// HashMap의 요소를 TreeMap에 삽입
for (Map.Entry<Person, String> entry : hashMap.entrySet()) {
    treeMap.put(entry.getKey(), entry.getValue());
}

System.out.println(treeMap.toString());     // {{name: smith, age: 25}=smith, {name: paul, age: 35}=paul, {name: john, age: 45}=paul}

다음과 같이 keySet()List로 변환한 후 Collections.sort()메소드로 정렬할 수도 있다.









 
 





HashMap<Integer, String> hashMap = new HashMap<>();

hashMap.put(9, "Benzema");
hashMap.put(11, "Bale");
hashMap.put(7, "Ronaldo");
hashMap.put(4, "Ramos");
hashMap.put(10, "Kane");

List<Integer> list = new ArrayList<>(hashMap.keySet());
Collections.sort(list);

for (Integer key: list) {
    System.out.println(hashMap.get(key));
}
Ramos
Ronaldo
Benzema
Kane
Bale

# HashMap를 Value로 정렬하기










 


 
 
 





HashMap<Integer, String> hashMap = new HashMap<>();

hashMap.put(9, "Benzema");
hashMap.put(11, "Bale");
hashMap.put(7, "Ronaldo");
hashMap.put(4, "Ramos");
hashMap.put(10, "Kane");

// KeySet을 List로 변환
List<Map.Entry<Integer, String>> entryList = new ArrayList<>(hashMap.entrySet());

// List 정렬
Collections.sort(entryList, (entry1, entry2) -> {
    return entry1.getValue().compareTo(entry2.getValue());
});

for(Map.Entry<Integer, String> entry : entryList) {
    System.out.println(entry.getKey() + " : " + entry.getValue());
}
11 : Bale
9 : Benzema
10 : Kane
4 : Ramos
7 : Ronaldo

# Map 메소드 정리

Map인터페이스가 제공하는 메소드는 다음과 같다.

# Map.put()

put()은 값이 없으면 새로운 값을 삽입한다.

Map<Integer, String> players = new TreeMap<Integer, String>();

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

System.out.println(players);    // {7=Ronaldo, 9=Benzema, 11=Bale}

값이 있으면 새로운 값으로 대치한다.

System.out.println(players);    // {7=Ronaldo, 9=Benzema, 11=Bale}

players.put(11, "Neymar");

System.out.println(players);    // {7=Ronaldo, 9=Benzema, 11=Neymar}

# Map.get()

키로 값을 조회한다.

Map<Integer, String> players = new TreeMap<Integer, String>();

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

String player1 = players.get(7);    // "Ronaldo"

키가 없으면 null을 반환한다.

String player2 = players.get(4);    // null

# Map.getOrDefault()

키가 없을 때 반환할 기본값을 지정할 수 있다.

String player3 = players.getOrDefault(4, "Ramos");  // "Ramos"

# Map.replace()

값을 대치하고 이전 값을 반환한다.







 



Map<Integer, String> players = new TreeMap<Integer, String>();

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

System.out.println(players.replace(7, "Son"));      // Ronaldo

System.out.println(players);    // {7=Son, 9=Benzema, 11=Bale}

키가 없으면 null을 반환한다.

System.out.println(players.replace(4, "ramos"));    // null

# Map.remove()

키와 값을 삭제한다.

players.remove(7);

# Map.isEmpty()

Map이 비어있는지 확인한다.

if (players.isEmpty()) {
    // ...
}

# Map.containsKey()

특정 키를 포함하는지 확인한다.

if (players.containsKey(7)) {
    // ...
}

# Map.containsValue()

특정 값을 포함하는지 확인한다.

if (players.containsValue("Ronaldo")) {
    // ...
}

# Map.keySet()

키의 집합을 반환한다.







 

Map<Integer, String> players = new TreeMap<Integer, String>();

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

Set<Integer> keySet = players.keySet();

키의 집합을 통해 Map을 순회할 수 있다.

for (Integer key: keySet) {
    System.out.println(key);
}

# Map.entrySet()

키와 값의 쌍인 Entry의 집합을 반환한다.







 

Map<Integer, String> players = new TreeMap<Integer, String>();

players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");

Set<Map.Entry<Integer, String>> entries = players.entrySet();

Entry의 집합은 다음과 같이 순회할 수 있다. 또한 Entry.getKey()를 통해서 키를, Entry.getValue()를 통해서 값을 가져올 수 있다.

for (Map.Entry<Integer, String> entry : entries) {
    System.out.println("[Key] " + entry.getKey() + " [Value] " + entry.getValue());
}
[Key] 7 [Value] Ronaldo
[Key] 9 [Value] Benzema
[Key] 11 [Value] Bale

# Map.clear()

Map을 클리어할 수 있다.

Map<Integer, String> players = new TreeMap<Integer, String>();
players.put(7, "Ronaldo");
players.put(9, "Benzema");
players.put(11, "Bale");
System.out.println(players);    // {7=Ronaldo, 9=Benzema, 11=Bale}

players.clear();
System.out.println(players);    // {}

# Stack

Stack은 다음과 같이 생성한다.

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

push()로 가장 위에 요소를 추가할 수 있다.

stack.push(1);
stack.push(2);
stack.push(3);

peek()로 맨위 요소에 접근할 수 있다.

stack.peek();

pop()으로 가장 위의 요소를 제거하고 반환할 수 있다.

Integer value = stack.pop();

clear()로 모든 요소를 클리어할 수 있다.

stack.clear();

System.out.println(stack);  // []

# Queue

Queue인터페이스와 LinkedList클래스로 큐를 사용할 수 있다.

public interface Queue<E> extends Collection<E> {
    boolean add(E e);
    E remove();
    E peek();
    // ...
}

큐는 다음과 같이 생성한다.

Queue<Integer> queue = new LinkedList<Integer>();

add()로 데이터를 삽입할 수 있다.

Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
queue.add(2);

System.out.println(queue.toString());   // [1, 2]

peek()로 가장 먼저 들어온 요소에 접근할 수 있다.

System.out.println(queue.toString());   // [1, 2, 3]

Integer result = queue.peek();          // 1

remove()로 데이터를 삭제할 수 있다.

System.out.println(queue.toString());   // [1, 2, 3, 4]

System.out.println(queue.remove());     // 1
System.out.println(queue.remove());       // 2

System.out.println(queue.toString());   // [3, 4]

clear()로 모든 요소를 제거할 수 있다.

Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
queue.add(2);

queue.clear();

# Deque

Deque 인터페이스와 ArrayDeque로 데크를 구현할 수 있다.

public interface Deque<E> extends Queue<E> {
    void addFirst(E e);
    void addLast(E e);
    E removeFirst();
    E removeLast();
    E peekFirst();
    E peekLast();
    boolean contains(Object o);
    // ...
}

addFirst(), addLast()로 데이터를 삽입한다.

Deque<Integer> deque = new ArrayDeque<>();

deque.addLast(1);
deque.addLast(2);
System.out.println(deque.toString());   // [1, 2]

deque.addFirst(5);
deque.addFirst(6);
System.out.println(deque.toString());   // [6, 5, 1, 2]

peekFirst(), peekLast()로 데이터에 접근한다.

System.out.println(deque.toString());   // [6, 5, 1, 2]

System.out.println(deque.peekFirst());  // 6
System.out.println(deque.peekLast());   // 2

removeFirst(), removeLast()로 데이터를 삭제한다.

System.out.println(deque.toString());       // [6, 5, 1, 2]

System.out.println(deque.removeFirst());    // 6
System.out.println(deque.removeLast());     // 2

System.out.println(deque.toString());       // [5, 1]

# PriorityQueue

PriorityQueue 클래스로 힙을 구현할 수 있다.

PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>();

add()로 데이터를 삽입한다.

priorityQueue.add(3);
priorityQueue.add(1);
priorityQueue.add(7);
priorityQueue.add(4);
priorityQueue.add(9);
priorityQueue.add(5);

정렬 기준을 별도로 설정하지 않으면 가장 낮은 값이 높은 우선순위를 갖는다. 따라서 오름차순으로 졍렬된 것과 동일하게 된다.

System.out.println(priorityQueue.toString());   // [1, 3, 5, 4, 9, 7]

peek()으로 우선순위가 가장 높은 데이터에 접근한다.

System.out.println(priorityQueue.peek());       // 1

remove()으로 우선순위가 가장 높은 데이터를 삭제한다.

System.out.println(priorityQueue.toString());   // [1, 3, 5, 4, 9, 7]

System.out.println(priorityQueue.remove());     // 1
System.out.println(priorityQueue.remove());     // 3
System.out.println(priorityQueue.toString());   // [4, 7, 5, 9]

clear()PriorityQueue를 클리어할 수 있다.

priorityQueue.clear();

# 내림차순 정렬

PriorityQueue 객체를 생성할 때 Collections.reverseOrder()를 인자로 전달하여 큰 값에 높은 우선순위를 부여할 수 있다.

PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(Collections.reverseOrder());
priorityQueue.add(3);
priorityQueue.add(1);
priorityQueue.add(7);
priorityQueue.add(4);
priorityQueue.add(9);
priorityQueue.add(5);

System.out.println(priorityQueue.toString());   // [9, 7, 5, 1, 4, 3]

System.out.println(priorityQueue.remove());     // 9
System.out.println(priorityQueue.remove());     // 7
System.out.println(priorityQueue.toString());   // [5, 4, 3, 1]

# 우선순위 기준 직접 지정하기

요소가 객체인 경우 Comparable 또는 Comparator를 사용하여 정렬 기준을 설정할 수 있다.

Comparator 예제는 다음과 같다.

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "[name='" + name + "', age=" + age + ']';
    }
}
PriorityQueue<Person> priorityQueue = new PriorityQueue<Person>((p1, p2) -> p1.getAge() - p2.getAge());

priorityQueue.add(new Person("Monica", 23));
priorityQueue.add(new Person("Phoebe", 25));
priorityQueue.add(new Person("Rachel", 24));
priorityQueue.add(new Person("Ross", 27));
priorityQueue.add(new Person("Chandler", 26));
priorityQueue.add(new Person("Ross", 22));

System.out.println(priorityQueue.remove().toString());      // [name='Ross', age=22]
System.out.println(priorityQueue.remove().toString());      // [name='Monica', age=23]
System.out.println(priorityQueue.remove().toString());      // [name='Rachel', age=24]
System.out.println(priorityQueue.remove().toString());      // [name='Phoebe', age=25]
System.out.println(priorityQueue.remove().toString());      // [name='Chandler', age=26]
System.out.println(priorityQueue.remove().toString());      // [name='Ross', age=27]

Comparable 예제는 다음과 같다. 어린 나이에 높은 우선순위를 부여하고 있다.

 






























 
 
 
 


class Person implements Comparable<Person> {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "[name='" + name + "', age=" + age + ']';
    }

    @Override
    public int compareTo(Person p) {
        return this.getAge() - p.getAge();
    }
}
PriorityQueue<Person> priorityQueue = new PriorityQueue<Person>();

priorityQueue.add(new Person("Monica", 23));
priorityQueue.add(new Person("Phoebe", 25));
priorityQueue.add(new Person("Rachel", 24));
priorityQueue.add(new Person("Ross", 27));
priorityQueue.add(new Person("Chandler", 26));
priorityQueue.add(new Person("Ross", 22));

System.out.println(priorityQueue.remove().toString());      // [name='Ross', age=22]
System.out.println(priorityQueue.remove().toString());      // [name='Monica', age=23]
System.out.println(priorityQueue.remove().toString());      // [name='Rachel', age=24]
System.out.println(priorityQueue.remove().toString());      // [name='Phoebe', age=25]
System.out.println(priorityQueue.remove().toString());      // [name='Chandler', age=26]
System.out.println(priorityQueue.remove().toString());      // [name='Ross', age=27]

# Collections 클래스

Collections클래스는 List, Set, Map를 조작하기 위한 유용한 메소드들을 제공한다.

# nCopies()

첫 번째 인자로 전달된 크기의 List를 생성한 후 두 번째 인자로 전달된 객체들로 초기화하여 반환한다.

 


List list = Collections.nCopies(10, 1);
System.out.println(list);   // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Collections.nCopies()은 불변 리스트를 반환하므로 변경할 수 없다. 가변 리스트는 다음과 같이 생성한다.

List list = new ArrayList(Collections.nCopies(10, 1));

# fill()

List의 모든 요소를 특정 값으로 대체한다.




 


List list = new ArrayList(Collections.nCopies(4, 1));
System.out.println(list);   // [1, 1, 1, 1]

Collections.fill(list, 5);
System.out.println(list);   // [5, 5, 5, 5]

# sort()

List를 정렬한다.

List list = new ArrayList(Arrays.asList(3, 7, 1, 9));
Collections.sort(list);
System.out.println(list);   // [1, 3, 7, 9]

요소가 객체인 경우 sort()의 두 번째 인자로 Comparator를 전달하여 정렬 기준을 지정할 수 있다.

PriorityQueue<Person> priorityQueue = new PriorityQueue<Person>(new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
});

또는 요소가 Comparable 인터페이스를 구현하고 compareTo() 메소드를 오버라이드하여 정렬 기준을 지정할 수 있다.

class Person implements Comparable<Person> {

    // ...

    @Override
    public int compareTo(Person p) {
        return this.getAge() - p.getAge();
    }
}

# max()

최대값을 반환한다.

List list = new ArrayList(Arrays.asList(3, 7, 8, 1));

Collections.max(list);  // 8

요소가 객체인 경우 두 번째 인자로 Comparator를 전달할 수 있다.

List list = new ArrayList(Arrays.asList(
    new Person("Paul", 35),
    new Person("Smith", 24),
    new Person("Kane", 18)
));

Person old = Collections.max(list, (Person p1, Person p2) -> p1.getAge() - p2.getAge());

System.out.println(old.getName() + " " + old.getAge()); // Paul 35

# min()

최소값을 반환한다.

List list = new ArrayList(Arrays.asList(3, 7, 8, 1));

Collections.min(list);  // 1

요소가 객체인 경우 두 번째 인자로 Comparator를 전달할 수 있다.

# swap()

두 요소를 스왑한다.





 



List list = new ArrayList(Arrays.asList(1, 2, 3, 4));

System.out.println(list);   // [1, 2, 3, 4]

Collections.swap(list, 0, 3);

System.out.println(list);   // [4, 2, 3, 1]

# copy()

List를 복사한다. 목적지 List는 출발지 List보다 크거나 같아야한다. 그렇지 않으면 IndexOutOfBoundsException가 발생한다.



 


List src = new ArrayList(Arrays.asList(1, 1, 1));
List dest = new ArrayList(Arrays.asList(2, 2, 2, 2, 2));
Collections.copy(dest, src);    
System.out.println(dest);       // [1, 1, 1, 2, 2]

Collections.copy() 대신 새로운 ArrayList를 생성을 하는 방식을 사용할 수도 있다.





 







Integer[] arr = {1, 2, 3};
List<Integer> list = new ArrayList<Integer>(Arrays.asList(arr));

// 깊은 복사
List<Integer> copied = new ArrayList<Integer>(list);
copy.add(4);

System.out.println(list.toString());        // [1, 2, 3]
System.out.println(copied.toString());      // [1, 2, 3, 4]
System.out.println(list.hashCode());        // 30817
System.out.println(copied.hashCode());      // 955331

# addAll()

여러 요소들을 추가한다.

List list = new ArrayList(Arrays.asList(1, 2, 3, 4));
Collections.addAll(list, 5, 6, 7);
System.out.println(list);   // [1, 2, 3, 4, 5, 6, 7]

# replaceAll()

특정 요소를 다른 요소로 대체한다.

List list = new ArrayList(Arrays.asList(1, 1, 1, 2, 2, 2, 2));
Collections.replaceAll(list, 2, 3);
System.out.println(list);   // [1, 1, 1, 3, 3, 3, 3]

# binarySearch()

정렬된 List에서 요소를 찾는다.

List list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));

Collections.binarySearch(list, 1);  // 0
Collections.binarySearch(list, 4)); // 3
Collections.binarySearch(list, 10); // -6

요소가 검색된 경우 인덱스를, 요소가 존재하지 않으면 음수를 반환한다.

# reverse()

List의 순서를 뒤집는다.

List list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
Collections.reverse(list);
System.out.println(list);   // [5, 4, 3, 2, 1]

# shuffle()

요소의 순서를 무작위로 섞는다.

List list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
Collections.shuffle(list);
System.out.println(list);   // [3, 1, 4, 2, 5]

# rotate()

두 번째 인자로 전달된 값만큼 배열을 회전시킨다.

List list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
Collections.rotate(list, 2);
System.out.println(list);   // [4, 5, 1, 2, 3]

# emptyList()

빈 불변 List를 반환한다.

List list = Collections.emptyList();    
list.add(1);  // UnsupportedOperationException

# emptySet()

빈 불변 Set을 반환한다.

Set set = Collections.emptySet();
set.add(7);  // UnsupportedOperationException

# emptyMap()

빈 불변 Map을 반환한다.

Map map = Collections.emptyMap();
map.put(7, "Ronaldo");  // UnsupportedOperationException

# singleton()

하나의 요소만 포함하는 불변 Set을 생성한다.

Set<Person> set = Collections.singleton(new Person("paul", 3));

# singletonList()

하나의 요소만 포함하는 불변 List를 생성한다.

List<Person> list = Collections.singletonList(new Person("Paul", 3));

# singletonMap()

하나의 요소만 포함하는 불변 Map를 생성한다.

Map<String, Person> map = Collections.singletonMap("p", new Person("Paul", 3));

# unmodifiableList()

List를 불변 객체로 만들어 반환한다.




 


List list = new ArrayList(Arrays.asList(1, 2, 3, 4));
list.add(5);        // Success

List immutable = Collections.unmodifiableList(list);
immutable.add(6);   // Error, UnsupportedOperationException

# unmodifiableSet()

Set을 불변 객체로 만들어 반환한다.

Set set = new HashSet();
set.add(1);
set.add(2);

Set immutable = Collections.unmodifiableSet(set);
immutable.add(3);   // UnsupportedOperationException

# unmodifiableMap()

Map을 불변 객체로 만들어 반환한다.

Map map = new HashMap();
map.put(7, "Ronaldo");
map.put(11, "Bale");

Map immutable = Collections.unmodifiableMap(map);
immutable.put(9, "Benzema");  // UnsupportedOperationException