[자바] 컬렉션 프레임워크

ChoRong0824·2023년 7월 7일
0

Java

목록 보기
28/31
post-thumbnail

23.07.16 스터디 정리

Collections Framework

컬렉션, 다수의 데이터를 다루는 데 필요한 다양하고 풍부한 클래스들을 제공하기 때문에 프로그래머의 짐을 상당히 덜어주고 있으며,
인터페이스와 다형성을 이용한 객체지향적 설계를 통해 표준화되어 있기 때문에 사용법을 익히기에도 편리하고 재사용성이 높은 코드를 작성할 수 있다는 장점이 있습니다.

컬렉션 프레임웍의 핵심 인터페이스

  1. List : 순서가 있는 데이터의 집합이며, 데이터의 중복을 허용합니다.
    예) 대기자 명단
    구현클래스 : ArrayList, LinkedList, Stack, Vector 등

  2. Set : 순서를 유지하지 않는 데이터의 집합이며, 데이터의 중복을 허용되지 않습니다.
    예) 양의 정수집합, 소수의 집합
    구현클래스 : HashSet, TreeSet 등

  3. Map : 키Key와 값Value의 쌍pair으로 이루어진 데이터의 집합이며,
    순서는 유지되지 않으며, 키는 중복을 허용하지 않고, 값은 중복을 허용합니다.
    예) 우편번호, 지역번호(전화번호)
    구현클래스 : HashMap, TreeMap, Hashtable, Properties 등

참고
키Key란, 데이터 집합 중에서 어떤 값Value을 찾는데 열쇠Key가 된다는 의미에서 붙여진 이름이다.
따라서, 키Key는 중복을 허용하지 않습니다.

실제 개발 시에는 다루고자 하는 컬렉션 등의 특징을 파악하고 어떤 인터페이스를 구현한 컬렉션 클래스를 사용해야하는지 결정해야하므로 각 인터페이스의 특징과 차이를 잘 이해하고 있어야 합니다.
모든 컬렉션 클래스들의 큰 틀은 List, Set, Map 입니다.


👉 Collection 인터페이스

List와 Set의 조상인 Collection 인터페이스에는 다음과 같은 메서드들이 정의되어 있습니다.

  1. boolean add(Object o): 주어진 객체 'o'를 컬렉션에 추가하고, 성공적으로 추가되면 true를 반환합니다.
  2. boolean addAll(Collection c): 주어진 컬렉션 'c'의 모든 요소를 현재 컬렉션에 추가하고, 요소가 하나 이상 추가되면 true를 반환합니다.
  3. void clear(): 컬렉션의 모든 요소를 제거합니다.
  4. boolean contains(Object o): 컬렉션이 주어진 객체 'o'를 포함하고 있다면 true를 반환합니다.
  5. boolean containsAll(Collection c): 컬렉션이 주어진 컬렉션 'c'의 모든 요소를 포함하고 있다면 true를 반환합니다.
  6. boolean equals(Object o): 주어진 객체와 컬렉션이 같은지 테스트합니다.
  7. int hashCode(): 컬렉션에 대한 해시 코드를 반환합니다.
  8. boolean isEmpty(): 컬렉션이 비어있는 경우 true를 반환합니다.
  9. Iterator iterator(): 컬렉션의 요소를 순회하는 Iterator 객체를 반환합니다.
  10. boolean remove(Object o): 컬렉션에서 지정된 객체 'o'를 제거하고, 제거에 성공하면 true를 반환합니다.
  11. boolean removeAll(Collection c): 주어진 컬렉션 'c'의 모든 요소를 현재 컬렉션에서 제거하고, 하나 이상 제거되면 true를 반환합니다.
  12. boolean retainAll(Collection c): 주어진 컬렉션 'c'에 있는 요소만을 현재 컬렉션에서 유지하고, 다른 요소는 제거합니다. 컬렉션이 변경되면 true를 반환합니다.
  13. int size(): 컬렉션의 요소 수를 반환합니다.
  14. Object[] toArray(): 컬렉션의 모든 요소가 포함된 Object 배열을 반환합니다.
  15. Object[] toArray(Object[] a): 컬렉션의 모든 요소가 포함된 지정된 타입의 배열을 반환하며, 넘겨준 배열의 크기보다 컬렉션의 크기가 크다면 새 배열이 생성됩니다.
    --> 즉, 지정된 배열에 Collection의 객체를 저장해서 반환합니다.

다양한 Collection 구현체(예: List, Set, Queue 등)는 이러한 메서드를 구현하여 공통된 작업을 수행할 수 있지만, 구현체마다 일부 동작이 서로 다를 수 있습니다.


👉 List 인터페이스

  • List 인터페이스는 중복을 허용하면서 저장순서가 유지되는 컬렉션을
    구현하는데 사용됩니다.
  • 객체를 일렬로 늘어놓은 구조를 가지고 있습니다.
  • 객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동 인덱스가 부여되고
    인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공합니다.

List 인터페이스에 정의된 메서드


💁‍♂️ ArrayList

  • 배열기반입니다.
  • ArrayList는 List 인터페이스를 상속받는 클래스로, 데이터를 이름표 없이
    무제한으로 보관할 수 있다.
  • ArrayList에 추가되는 데이터는 저장순서가 유지되고 중복을 허용한다.
  • ArrayList에서 특정 인덱스의 객체를 삭제하거나 추가할 경우
    바로 뒤 인덱스부터 인덱스가 바뀐다.

다시말해, Java에서 제공하는 가변 크기의 배열로, 여러 개의 객체를 동적으로 저장하고 관리할 수 있는 데이터 구조입니다.
Java의 java.util 패키지 안에 있는 List 인터페이스를 구현한 클래스이기 때문에, 리스트의 인덱스를 이용해 원하는 위치에 객체를 삽입하거나 삭제할 수 있습니다.

ArrayList의 주요 특징

  1. 가변 크기: ArrayList는 내부적으로 배열을 사용하여 원소를 저장하고, 배열이 꽉 찰 경우 새로운 크기의 배열을 생성하여 기존의 원소들을 복사합니다. 따라서 필요에 따라 크기를 조절할 수 있습니다.

  2. 순차 접근: 인덱스를 이용해 특정 원소에 직접 접근할 수 있어, 순차적인 데이터 처리에 효율적입니다.

  3. 객체 저장: ArrayList는 Object 타입의 객체를 저장하므로, 다양한 타입의 객체들을 저장할 수 있습니다.
    하지만 원시 데이터 타입은 저장할 수 없으므로, 이를 감싸주는 wrapper 클래스를 사용해야 합니다(예: int -> Integer).

주의할 점

ArrayList는 내부적으로 배열을 사용하기 때문에, 크기 변경 시 새로운 배열을 생성하고 기존 데이터를 복사하는 과정에서 성능 저하가 발생할 수 있습니다.
삭제 작업 중 가장 마지막 원소를 제외하면, 삭제된 위치 이후의 모든 원소들을 이동시켜야 해서 오버헤드가 발생합니다. 이러한 경우 LinkedList를 고려할 수 있습니다.

📌 일반 배열과의 차이점 📌

  - 배열은 생성할 때 크기가 고정되고 사용 중에 크기를 변경할 수 없지만,
  ArrayList는 저장 용량(capacity)가 초과한 객체들이 들어오면 
  자동적으로 저장 용량(capacity)이 늘어난다. 

ArrayList의 선언

List<저장할 객체 타입> list = new ArrayList<저장할 객체 타입>();

🧑‍💻 ArrayList 코드 예시

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

public class Main {

	public static void main(String[] args) {
		
		List<Integer> numberList = new ArrayList<Integer>();
		
		// 데이터 추가하기
		for (int i = 1; i <= 10; i++) {
			numberList.add(i);
		}
		
		System.out.println("데이터의 크기 : " + numberList.size());
		System.out.println("--------------------------------");
		
		// 5번째 요소의 값 얻기, 
		System.out.println("5번째 요소의 값 : " + numberList.get(4));
		System.out.println("--------------------------------");
		
		// 5번째 요소의 값 삭제
		numberList.remove(4);
		
		// 하나를 삭제 후, 전체 크기 다시 조회
		System.out.println("데이터의 크기 : " + numberList.size());
		System.out.println("--------------------------------");
			
		// 5번째 요소의 값 다시 얻기
		System.out.println("5번째 요소의 값 : " + numberList.get(4));
		System.out.println("--------------------------------");
		
		// 5번째 자리에 데이터 추가
		numberList.add(4, 123);
		System.out.println("5번째 요소의 값 : " + numberList.get(4));
		System.out.println("--------------------------------");
		
		// 전체 삭제
		numberList.clear();
		System.out.println("데이터의 크기 : " + numberList.size());
		
	}

}

👉 실행 결과

데이터의 크기 : 10
--------------------------------
5번째 요소의 값 : 5
--------------------------------
데이터의 크기 : 9
--------------------------------
5번째 요소의 값 : 6
--------------------------------
5번째 요소의 값 : 123
--------------------------------
데이터의 크기 : 0

💁‍♂️ LinkedList

  • 구조가 간단하며, 사용하기 쉽고 데이터를 읽어오는데 걸리는 시간이 가장 빠르다는 장점을 가지고 있지만 다음과 같은 단점도 가지고 있다.

    1. 크기를 변경할 수 없다.
      -> 크기를 변경할 수 없으므로 새로운 배열을 생성해서 데이터를 복사해야한다.
      -> 실행속도를 향상시키기 위해서는 충분히 큰 크기의 배열을 생성해야 하므로 메모리 낭비된다.
    2. 비순차적인 데이터의 추가 또는 삭제에 시간이 많이 걸린다.
      -> 차례대로 데이터를 추가하고 마지막에서부터 데이터를 삭제하는 것은 빠르지만,
      -> 배열의 중간에 데이터를 추가하려면, 빈자리를 만들기 위해 다른 데이터들을 복사하기 위해 이동해야 한다.
  • LinkedList는 List 구현 클래스이므로 ArrayList와 사용 방법은 똑같지만
    내부 구조는 완전히 다르다.

  • ArrayList는 모든 데이터가 연속적으로 존재하지만
    LinkedList는 불연속적으로 존재하는 데이터를 서로 연결(link)한 형태로 구성되어 있다.
    -> LinkedList의 각 요소들은 자신과 연결된 다음 요소에 대한 참조(주소값)와
    데이터로 구성되어 있다.

  • 또한, 특정 인덱스의 객체를 추가 또는 삭제할 경우 앞뒤 링크만 변경되고 나머지 링크는
    변경되지 않는다.
    --> 앞뒤 링크 정보만 변경하면 되므로 처리속도가 매우 빠르다.

  • LinkedList는 불연속적으로 위치한 각 요소들이 서로 연결된 것이라
    처음부터 n번째 데이터까지 차례대로 따라가야만 원하는 값을 얻을 수 있다.
    그래서 LinkedList는 저장해야하는 데이터의 개수가 많아질수록
    데이터를 읽어 오는 시간(접근시간)이 길어진다는 단점이 있다.

다시말해,
LinkedList는 Java에서 제공하는 연결 리스트를 구현한 데이터 구조입니다.
java.util 패키지의 List 인터페이스를 구현한 클래스로, 각 노드가 이전 노드와 다음 노드를 참조하여 연결되어 있는 구조입니다.
연결 리스트의 각 노드는 두 부분으로 구성되어 있으며, 하나는 데이터를 저장하고 다른 하나는 다음 노드에 대한 참조를 저장합니다.

LinkedList의 주요 특징

  1. 동적 크기: 연결 리스트의 크기는 노드 추가와 삭제에 따라 동적으로 변경되며, 별도의 리사이징 과정이 없습니다. 따라서 자주 크기가 변경되는 경우에 유용합니다.

  2. 노드 추가/삭제 효율성: 시작 노드와 끝 노드에서의 추가/삭제 작업이 O(1)의 시간 복잡도를 가지며, 중간 노드에서의 추가/삭제 작업은 O(n)의 시간 복잡도를 가집니다. 데이터의 양이 많아질수록 더 효율적입니다.

  3. 객체 저장: LinkedList도 ArrayList와 마찬가지로 Object 타입의 객체를 저장할 수 있습니다. 원시 데이터 타입은 저장할 수 없으므로, wrapper 클래스를 이용해야 합니다.

주의할 점

LinkedList는 순차적으로 노드를 탐색해야 하므로, 인덱스를 이용한 특정 원소에 대한 접근이 느립니다.
또한, 각 노드의 참조 정보를 저장하고 있어 메모리 공간을 더 많이 사용합니다.
이러한 단점을 고려하여 사용 시 주목할 점입니다.

ArrayList와 LinkedList의 비교

LinkedList의 선언

List<저장할 객체 타입> list = new LinkedList<저장할 객체 타입>();

결론

  1. 순차적으로 추가/삭제하는 경우에는 ArrayList가 LinkedList보다 빠르다.
    --> AL은 마지막 데이터부터 삭제할 경우 각 요소들의 재배치가 필요하지 않기 때문에 상당히 빠르다. 단지 마지막 요소의 값을 null로만 바꾸면 되기 때문이다.
  2. 중간 데이터를 추가/삭제하는 경우에는 Linkedlist가 ArrayList보다 빠르다.
    --> LL은 각 요소간의 연결만 변경해주면 되기 때문에 처리속도가 상당히 빠르다.
    반면, AL는 각 요소들을 재배치하여 추가할 공간을 확보하거나 빈 공간을 채워야하기 때문에 처리속도가 늦다.

👉Set 인터페이스

  • Java의 Set 인터페이스는 java.util 패키지에 위치한 컬렉션 클래스들의 한 종류로,
    중복된 원소를 저장하지 않고 유니크한 원소들만 저장할 수 있는 데이터 구조입니다.
  • Set 인터페이스는 Collection 인터페이스를 상속받으며 주로 경량화된 객체 모델을 제공하는데 목적이 있습니다.

    다시말해,

    • 중복을 허용하지 않고 저장순서가 유지되지 않는다.
      -> 하나의 null만 저장할 수 있다.
    • 인덱스로 관리하지 않기 때문에 인덱스를 매개값으로 갖는 메서드가 없다.

Set 인터페이스의 주요 특징

  1. 중복 원소 허용 X: Set 인터페이스는 원소의 중복을 허용하지 않으며, 같은 데이터를 여러 번 추가해도 한 번만 저장됩니다. 이 때, 원소의 동일성 비교를 위해 equals() 메서드와 hashCode() 메서드가 사용됩니다.

  2. 순서 보장 X: 대부분의 Set 인터페이스 구현체는 원소의 저장 순서를 보장하지 않습니다. 하지만, LinkedHashSet은 원소가 추가된 순서를 유지합니다.

  3. 객체 저장: Set 인터페이스도 기본적으로 Object 타입의 객체만 저장할 수 있으며, 원시 데이터 타입은 wrapper 클래스를 사용하여 저장해야 합니다.

대표적인 Set 인터페이스 구현체

  1. HashSet: 가장 일반적으로 사용되는 Set 구현체로, 해시 테이블을 이용해 원소를 저장합니다. 해시 기반의 알고리즘을 사용하여 빠른 검색과 삽입/삭제 성능을 제공하나, 원소의 순서는 보장하지 않습니다.

  2. LinkedHashSet: HashSet을 기반으로 하되, 원소간의 추가된 순서에 따른 연결 정보(링크)를 추가로 저장합니다. 원소의 삽입 순서가 유지되지만, 추가적인 메모리가 사용됩니다.

  3. TreeSet: 이진 검색 트리를 기반으로한 정렬된 Set 구현체로, 원소들이 정렬되어 저장됩니다. 원소들의 순서를 유지하고 자동 정렬 되지만, 검색 및 추가 삭제의 성능은 HashSet에 비해 상대적으로 느립니다.

Set 인터페이스에 정의된 메서드

  • Set 컬렉션은 인덱스로 객체를 검색해서 가져오는 메서드가 없다.
    그래서 전체 객체를 대상으로 한번씩 반복해서 가져오는 반복자(Iterator)를
    제공한다.

💁‍♂️ Iterator 인터페이스

  • Iterator는 컬렉션에 저장된 요소를 접근하는데 사용되는 인터페이스이다.
  • iterator() 메서드를 호출하면 얻을 수 있다.

Iterator 인터페이스에 선언된 메서드



💁‍♂️ HashSet

  • Java의 Set 인터페이스를 구현한 컬렉션 클래스로, 중복된 원소를 허용하지 않고 유니크한 원소들만 저장하는 데이터 구조입니다.
  • HashSet은 내부적으로 해시 테이블(hash table)을 사용하여 데이터를 저장하고 조회합니다.
    해시 기반의 알고리즘을 이용하여 빠른 검색, 삽입, 삭제 성능을 제공하나, 원소의 순서는 보장하지 않습니다.

    다시말해,

    • Set인터페이스를 구현한 가장 대표적인 컬렉션이며, 중복된 요소를
      저장하지 않는다.
    • 이미 저장되어 있는 요소와 중복된 요소를 추가하고자 한다면
      add메서드는 false를 리턴함으로써 중복된 요소이기 때문에 추가에
      실패했다는 것을 알린다.
    • 저장 순서를 유지하지 않으므로 저장순서를 유지하고자 한다면
      LinkedHashSet을 사용해야한다.

중복을 걸러내는 과정

  1. 원소 추가 시, 해당 원소의 hashCode() 메서드를 호출하여 해시 코드를 계산합니다.
    이 해시 코드는 논리적으로 동일한 객체에 대해서는 같은 값을 반환하게 되어있습니다.

  2. 해시 코드를 이용하여 해시 테이블 내에 버킷(bucket) 인덱스를 결정합니다. 이 때, 버킷 인덱스는 원소가 저장될 위치를 나타냅니다. 만약 해당 인덱스에 아직 원소가 저장되지 않았다면, 이 위치에 새로운 원소를 저장합니다.

  3. 해시 충돌(hash collision)이 발생했다면, 즉 해당 버킷 인덱스에 이미 다른 원소가 저장되어 있다면, 원래 저장된 원소와 새로운 원소의 equals() 메서드를 호출하여 두 원소가 동일한지 확인합니다.

  4. 만약 equals() 메서드의 결과가 false라면, 두 원소가 다른 것으로 판단하고, 해당 버킷에 두 원소를 연결 리스트 형태로 저장합니다. 이렇게 저장된 원소들은 다음번 조회 및 삭제 시 동일한 해시 코드와 같은 인덱스로 찾아 진행됩니다.

  5. 만약 equals() 메서드의 결과가 true라면, 두 원소가 중복으로 판단되어 새로운 원소는 추가되지 않고, 기존 원소가 유지됩니다.

이러한 과정을 통해 HashSet은 중복된 원소를 효과적으로 걸러내고,
빠른 검색, 삽입, 삭제 성능을 제공합니다.
하지만 원소의 순서를 보장하지 않기 때문에,
순서가 중요한 경우에는 LinkedHashSet이나 TreeSet을 고려할 수 있습니다.

다시말해,

  • HashSet은 객체를 저장하기 전에 먼저 객체의 hashCode() 메서드를 호출해서
    해시코드를 얻어낸다.
    -> 그리고 이미 저장되어 있는 객체들의 해시코드와 비교한다.
  • 동일한 해시코드가 있다면 다시 equals() 메서드로 두 객체를 비교해서 true가
    나오면 동일한 객체로 판단하고 중복 저장을 하지 않는다.

HashSet의 선언

Set<저장할 객체 타입> set = new HashSet<저장할 객체 타입>(); 

🧑‍💻 HashSet 코드 예시

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {

	public static void main(String[] args) {
			
		Set<String> color = new HashSet<String>();
		
		// 데이터 추가하기
		color.add("Red");
		color.add("Blue");
		color.add("Black");
		color.add("White");
		color.add("Pink");
		
		System.out.println("데이터의 크기 : " + color.size());
		System.out.println("numSet = " + color);	// 저장순서를 유지하지 않는다.
		System.out.println("--------------------------------");
		
		// 중복된 값 추가
		// 	-> 중복된 값은 저장되지 않는다.
		color.add("Red");
		System.out.println("데이터의 크기 : " + color.size());		
		System.out.println("--------------------------------");
		
		// 데이터 전체 출력하기 
		Iterator<String> ite = color.iterator();
		
		while(ite.hasNext()) { 	// 값이 있으면 true, 없으면 false
			System.out.print(ite.next() + " "); 
		}
		System.out.println("\n--------------------------------");

		// 데이터 삭제
		color.remove("Blue");
		
		// 값 검색하기
		System.out.println(color.contains("Blue"));
		System.out.println(color.contains("Red"));
		
	}

}

실행 결과

데이터의 크기 : 5
numSet = [Red, White, Pink, Blue, Black]
--------------------------------
데이터의 크기 : 5
--------------------------------
Red White Pink Blue Black 
--------------------------------
false
true

💁‍♂️ TreeSet

  • 이진 탐색 트리라는 자료구조의 형태로 데이터를 저장하는 컬렉션 클래스이다.
  • 중복된 데이터의 저장을 허용하지 않으며 정렬된 위치에 저장하므로
    저장순서를 유지하지도 않는다.

TreeSet의 선언

TreeSet<저장할 객체 타입> treeSet = new TreeSet<저장할 객체 타입>();

이진 탐색 트리(binary search tree)

  • 이진 트리의 한 종류로
    정렬, 검색, 범위검색(range search)에 높은 성능을 보이는 자료구조이다.
👉 이진 트리(binary tree)란? 👈

- 여러 개의 노드(node)가 트리 형태로 연결된 구조로,
'루트(root)'라고 불리는 하나의 노드에서부터 시작해서 각 노드에 최대 2개의
노드를 연결할 수 있는 구조를 가지고 있다.
- 위 아래로 연결된 두 노드를 '부모-자식관계'에 있다고 하며 
위의 노드를 부모 노드, 아래의 노드를 자식 노드라 한다.
- 하나의 부모 노드는 최대 두 개의 자식 노드와 연결될 수 있다.

  • 이진 탐색 트리는 왼쪽 자식노드의 값은 부모노드의 값보다 작고,
    오른쪽 자식노드의 값은 부모노드의 값보다 커야한다.


왼쪽 마지막 값에서부터 오른쪽 값까지 값을 '왼쪽 노드 -> 부모 노드 -> 오른쪽노드'
순으로 읽어오면 오름차순으로 정렬된 순서를 얻을 수 있다.

  • 즉, 정렬에 유리하다.
    TreeSet은 이처럼 정렬된 상태을 유지하기 때문에 단일 값 검색과 범위검색이
    매우 빠르다.
    하지만, 반복 비교로 자리를 찾아 저장하기 때문에 노드의 추가 삭제에 시간이 걸린다.

TreeSet의 메서드

  • NavigableSet tailSet(Object toElement, boolean inclusive)
    --> 지정된 객체보다 높은 값의 객체들을 반환하며,
    inclusive가 true이면, 같은 값의 객체도 포함

  • NavigableSet sunSet(Object fromElement, boolean fromInclusive, Object toElement, boolean toInclusive)
    -->범위 검색(fromElement와 toElement사이)의 결과를 반환하며,
    fromInclusive가 true면 시작값이 포함되고, toInclusive가 true면 끝값이 포함된다.

👨🏻‍💻 TreeSet 코드 예시

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

public class Main {

	public static void main(String[] args) {
			
		TreeSet<Integer> scores = new TreeSet<Integer>();
		
		// 데이터 추가하기
		scores.add(new Integer(87));
		scores.add(new Integer(64));
		scores.add(new Integer(98));
		scores.add(new Integer(75));
		scores.add(new Integer(95));
		scores.add(new Integer(80));
		
		// 특정 객체 찾기
		System.out.println("가장 낮은 점수 : " + scores.first());
		System.out.println("가장 높은 점수 : " + scores.last() + "\n");
		
		System.out.println("90점 아래 점수 : " + scores.lower(new Integer(90)));
		System.out.println("90점 위의 점수 : " + scores.higher(new Integer(90)) + "\n");
		
		// 객체 정렬하기
		NavigableSet<Integer> descendingSet = scores.descendingSet();
		for (Integer score : descendingSet) {
			System.out.print(score + " ");
		}
		System.out.println("\n");
		
		// pollFirst() 메서드
		while (!scores.isEmpty()) {
			System.out.println(scores.pollFirst() + " [남은 객체 수 : " 
								+ scores.size() + "]" );
		}
		
	}

}

💁‍♂️ 실행 결과

가장 낮은 점수 : 64
가장 높은 점수 : 98

90점 아래 점수 : 87
90점 위의 점수 : 95

98 95 87 80 75 64 

64 [남은 객체 수 : 5]
75 [남은 객체 수 : 4]
80 [남은 객체 수 : 3]
87 [남은 객체 수 : 2]
95 [남은 객체 수 : 1]
98 [남은 객체 수 : 0]

👨🏻‍💻 TreeSet 코드 예시2

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

public class Main {

	public static void main(String[] args) {
			
		TreeSet<String> words = new TreeSet<String>();
		
		words.add("apple");
		words.add("cherry");
		words.add("forever");
		words.add("zoo");
		words.add("dance");
		words.add("car");
		words.add("flower");
		words.add("green");
		
		// 범위 검색하기
		System.out.println("[c~f 사이의 단어 검색]");
		
		NavigableSet<String> rangeSearch = words.subSet("c", true, "g", true);
		for(String word : rangeSearch) {
			System.out.println(word);
		}
		
	}

}

💁‍♂️ 실행 결과

[c~f 사이의 단어 검색]
car
cherry
dance
flower
forever

👉 Map 인터페이스

  • Map 인터페이스는 java.util 패키지에 있는 데이터 구조로, 키(key)와 값(value) 쌍으로 이루어진 객체를 저장하는 컬렉션입니다.
  • 키와 값은 모두 객체로, 하나의 키는 유일한 값을 가리키며 중복을 허용하지 않습니다.
  • 반면에, 값은 중복이 허용됩니다.

Map 인터페이스의 주요 특징

  1. 키-값 쌍: Map 인터페이스는 데이터를 키와 값의 쌍으로 저장하며, 키를 사용하여 해당 값에 빠르게 접근할 수 있습니다.
  2. 중복 키 허용 X: 키는 유일하고 중복을 허용하지 않으며, 같은 키로 데이터를 추가하게 되면 기존의 값이 대체됩니다.
  3. 순서 보장 X: 대부분의 Map 인터페이스 구현체는 키-값 쌍 저장 순서를 보장하지 않습니다. 그러나 LinkedHashMap은 삽입된 순서를 보장하며 TreeMap은 키에 대해 정렬된 순서를 보장합니다.
  4. 객체 저장: 키와 값은 모두 객체로 저장되므로, 원시 데이터 타입은 저장할 수 없으며 wrapper 클래스를 사용하여 저장해야 합니다.

대표적인 Map 인터페이스 구현체

  1. HashMap: 해시 테이블을 기반으로한 Map 구현체로, 키-값 쌍을 빠르게 검색할 수 있습니다. 키의 순서는 보장하지 않으며 해시 충돌이 발생할 경우 연결 리스트를 사용하여 데이터를 저장합니다.
  2. LinkedHashMap: HashMap을 기반으로 하되, 삽입 순서 또는 최근 접근 순서에 따라 키-값 쌍을 저장합니다. 순서가 유지되므로 순회 속도가 HashMap보다 빠르지만, 추가적인 메모리를 사용합니다.
  3. TreeMap: 이진 검색 트리를 기반으로한 정렬된 Map 구현체로, 키들이 정렬된 순서로 저장됩니다. 키를 기준으로 정렬되어 저장되기 때문에, 범위 검색이나 정렬된 데이터가 필요한 경우에 사용할 수 있습니다. 그러나 HashMap에 비해 상대적으로 접근 속도가 느립니다.

    다시말해,

    • 키(key)와 값(value)을 하나의 쌍으로 묶어서 저장하는 컬렉션 클래스를 구현하는데 사용된다. --> 여기서 키와 값은 모두 객체이다.
    • 키는 중복될 수 없지만 값은 중복을 허용한다.
    • 기존에 저장된 데이터와 중복된 키와 값을 저장하려면 기존의 값은 없어지고,
      마지막에 저장된 값이 남게 된다.
    • 키로 객체들을 관리하기 때문에 키를 매개값으로 갖는 메서드가 많다.

Map 인터페이스에 정의된 메서드



👉 HashMap

  • Java의 Map 인터페이스를 구현한 컬렉션 클래스로, 키와 값의 쌍을 저장하는 해시 테이블(hash table) 기반 데이터 구조입니다.
  • HashMap은 키의 중복을 허용하지 않으며, 값을 변경하거나 새로운 키-값 쌍을 추가하는 데 사용됩니다.
  • 키를 이용하면 값을 빠르게 검색할 수 있어, 검색 성능이 좋습니다.

HashMap의 주요 특징

  1. 해시 테이블 기반: 해시 테이블을 사용하여 키-값 쌍을 저장하고 검색하는 데 필요한 시간을 최소화합니다. 해시 알고리즘을 사용하여 항목을 저장하고 검색하므로 빠른 검색, 삽입 및 삭제 성능을 제공합니다.

  2. 순서 미보장: HashMap은 키-값 쌍의 저장 순서를 보장하지 않습니다. 따라서 순서에 의존하는 작업에는 부적합합니다. 순서가 중요한 경우 LinkedHashMap을 사용할 수 있습니다.

  3. Null 허용: HashMap은 키와 값 모두에 대해 null 값을 허용합니다. 하지만 키가 null인 경우는 하나만 허용되며 값은 여러 개의 null이 허용됩니다.

  4. 동기화 미지원: HashMap은 여러 스레드 환경에서 동기화를 지원하지 않습니다. 동시성을 처리해야 하는 경우 ConcurrentHashMap을 사용하는 것이 좋습니다.

  5. 객체 저장: 키와 값은 모두 객체로 저장되므로, 원시 데이터 타입은 저장할 수 없으며 wrapper 클래스로 변환해야 저장할 수 있습니다.

    다시말해,

    • Map 인터페이스를 구현한 대표적인 Map 컬렉션이다.
      Map의 특징, 키(key)와 값(value)을 묶어서 하나의 데이터(entry)로
      저장한다는 특징을 갖는다.
      해싱(hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는데 있어서
      뛰어난 성능을 보인다.
      키(key)는 주로 String을 많이 사용한다.
      키는 저장된 값을 찾는데 사용되는 것이기 때문에 컬렉션 내에서
      유일(unique)해야 한다.
      String은 문자열이 같은 경우 동등 객체가 될 수 있도록
      hashcod()와 equuals()메서드가 재정의 되어 있다.

해시 충돌 처리

  • 해시 충돌이 발생한 경우 즉, 같은 해시 코드를 가진 키-값 쌍이 이미 존재할 경우,
    HashMap은 연결 리스트를 사용하여 충돌이 발생한 버킷에 데이터를 저장합니다.
    Java 8부터는 연결 리스트의 길이가 일정 개수를 넘어설 경우 연결 리스트를 균형 이진 트리로 변환하여 검색 성능을 개선합니다.

    다시말해,

    • 해시 충돌은 서로 다른 두 개의 키가 같은 해시 값을 갖거나 같은 버킷 인덱스에 매핑되는 경우 발생합니다.
    • 해시 충돌을 처리하는 과정은 주로 Separate Chaining과 Open Addressing이라는 두 가지 방법이 있습니다.
    • HashMap에서는 Separate Chaining을 사용하여 해시 충돌을 처리하니, 이에 대해 설명하겠습니다.
      --> 1. 새로운 키-값 쌍이 추가될 때, 해당 키의 해시 값을 계산합니다.
      --> 2. 해시 값을 이용해 해시 테이블 내 버킷 인덱스를 구합니다.
      --> 3. 해당 버킷 인덱스에 아직 데이터가 없다면 새로운 키-값 쌍을 그 위치에 저장합니다.
      --> 4. 이미 해당 버킷 인덱스에 데이터가 있다면, 새로운 키와 기존 키를 비교합니다.
      또한, 같은 키가 이미 존재하면 기존 값에 대해 새로운 값으로 업데이트합니다.
      --> 5. 키가 다르고 해시 충돌이 발생한 경우, 기존 버킷에 연결된 연결 리스트에 새로운 키-값 쌍을 추가합니다. 연결 리스트의 길이가 일정 개수를 넘어서면, Java 8 이상에서는 이를 균형 이진 트리로 바꾸어 성능을 개선합니다.
    • Separate Chaining은 해시 충돌 발생 시 연결 리스트를 사용하여 키-값 쌍을 저장하는 방식으로, 충돌이 제한적인 경우 상대적으로 빠른 처리를 보장하며, 메모리 활용 면에서도 효율적입니다. 그러나 연결 리스트의 길이가 매우 길어질 경우 검색 속도가 느려질 수 있으므로, 충돌이 잦은 경우에는 Open Addressing 방식 또는 다른 해시 테이블 구현을 고려할 수 있습니다.

HashMap의 선언

Map< 타입,  타입> map = new HashMap< 타입,  타입>();

👨🏻‍💻 HashMap 코드 예시

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

public class Main {

	public static void main(String[] args) {
			
		Map<String, Integer> map = new HashMap<String, Integer>();
		
		// 데이터 추가하기
		// -> 중복을 허용하지 않는다. (마지막에 들어간 값이 저장된다.)
		map.put("국어", 95);
		map.put("수학", 90);
		map.put("영어", 85);
		map.put("수학", 100);
		map.put("영어", null); 	// 객체를 넣는 것이므로 Null 사용 가능
		
		// 저장된 갯수을 얻기
		System.out.println("HashMap size : " + map.size());
		System.out.println("-------------------------------------");
		
		// 데이터 꺼내기
		System.out.println(map.get("국어"));
		System.out.println(map.get("영어"));
		System.out.println(map.get("수학"));
		System.out.println("-------------------------------------");
				
		// 반복문으로 탐색 -> HashMap은 순서가 유지되지 않는다.
		Set<String> keySet = map.keySet();
		Iterator<String> it = keySet.iterator();
		while(it.hasNext()) {
			String key = it.next();
			Integer value = map.get(key);
			System.out.println(key + " : " + value);
		}
		
	}

}

💁‍♂️실행 결과

HashMap size : 3
-------------------------------------
95
null
100
-------------------------------------
국어 : 95
수학 : 100
영어 : null

👉 Arrays

자바에서 원시 타입 배열 및 객체 배열을 다루는 유틸리티 클래스로, 컬렉션 프레임워크의 인터페이스 중 어디에도 포함되지 않습니다.
java.util 패키지에 속한 Arrays 클래스는 배열의 정렬, 검색, 비교 및 변환 등 다양한 배열 작업에 사용되는 메서드를 제공합니다.
컬렉션 프레임워크는 List, Set, Queue, Deque 및 Map과 같은 인터페이스를 포함하며, ArrayList, LinkedList, HashSet, PriorityQueue, HashMap 등과 같은 구현 클래스가 있습니다.
이러한 컬렉션 프레임워크는 여러 객체를 그룹화하여 관리합니다.
따라서, Arrays 클래스는 컬렉션 프레임워크의 인터페이스와 별개로 사용되며 배열과 관련된 도움을 주는 유틸리티 클래스입니다.

💁‍♂️ 배열의 복사 - copyOf(), copyOfRange()

copyOf()

copyOf() 메서드는 원본 배열의 특정 길이만큼 새로운 배열로 복사해줍니다. 이 메서드는 원본 배열과 새로 생성된 배열 타입이 동일해야 합니다.

메서드의 형식

public static <T> T[] copyOf(T[] original, int newLength)

여기서 T는 제네릭 형태의 배열이며, original은 원본 배열, newLength는 요청한 새로운 배열 길이입니다.

예시

int[] original = {1, 2, 3, 4, 5};
int[] copied = Arrays.copyOf(original, 3);

위 사례에서 copied 배열은 원래 배열에서 처음 3개 요소인 {1, 2, 3}로 구성됩니다.

copyOfRange()

copyOfRange() 메서드는 원본 배열의 특정 범위를 지정하여 새로운 배열로 복사해주며 반환합니다.
이 메서드도 원본 배열과 새로 생성된 배열 타입이 동일해야 합니다.
또한, 해당 메서드는 늘 그렇듯이 copyOfRange()에 지정된 범위의 끝은 포함되지 않는다.

메서드의 형식

public static <T> T[] copyOfRange(T[] original, int from, int to)

여기서 T는 제네릭 형태의 배열이며, original은 원본 배열, from은 복사 시작 지점, to는 복사 끝나는 지점 직전입니다.

예시

int[] original = {1, 2, 3, 4, 5};
int[] copied = Arrays.copyOfRange(original, 1, 4);

위 사례에서 copied 배열은 원래 배열의 지정된 범위인 {2, 3, 4}를 포함합니다. 이 두 메서드를 통해 원본 배열의 전체 또는 일부를 새로운 배열로 복사할 수 있으며, 이를 이용해 배열을 변경하거나 조작할 수 있습니다. 새로 생성된 배열은 원본 배열과 독립적이므로, 원본 배열에 영향을 주지 않고 처리할 수 있습니다.

💁‍♂️ 배열 채우기 - fill(), setAll()

fill()

Arrays 클래스의 fill() 메서드는 배열의 모든 요소를 주어진 값으로 채워줍니다. 이 메서드는 배열의 모든 요소를 동일한 값으로 초기화할 때 유용합니다.

메서드의 형식

public static void fill(Object[] a, Object val)

여기서 a는 채워질 배열, val은 배열의 모든 요소에 설정되는 값입니다.

예시

int[] array = new int[5];
Arrays.fill(array, 10);

위 사례에서 array 배열은 {10, 10, 10, 10, 10}로 초기화됩니다.

setAll()

Arrays 클래스의 setAll() 메서드는 주어진 함수를 사용하여 모든 배열 인덱스의 값을 설정합니다. 이 메서드는 배열 요소를 사용자 정의 함수를 통해 초기화할 때 유용합니다.

메서드의 형식

public static <T> void setAll(T[] array, IntFunction<? extends T> generator)

여기서 T는 제네릭 형태의 배열이며, array는 값을 저장할 배열, generator는 인덱스를 기반으로 값을 생성하는 함수입니다.

예시

int[] array = new int[5];
Arrays.setAll(array, i -> i * 2);

위 사례에서 array 배열은 {0, 2, 4, 6, 8}로 초기화됩니다.
함수는 인덱스에 2를 곱한 값을 반환합니다. 이 두 메서드를 사용하여 배열의 각 요소를 특정 값으로 채우거나 주어진 함수를 기반으로 배열 요소를 설정할 수 있습니다. 이를 통해 배열을 쉽게 설정하고 업데이트할 수 있습니다.

💁‍♂️ 배열의 정렬과 검색 - sort(), binarySearch()

sort()

sort() 메서드는 배열을 오름차순으로 정렬해줍니다.
이 메서드는 기본적인 데이터 타입 배열과 객체 배열 모두에 적용할 수 있으며, 객체 배열의 경우 Comparable 인터페이스를 구현하여 정렬을 할 수 있습니다.

메서드의 형식

public static void sort(int[] a) // 원시 타입 배열
public static <T> void sort(T[] a) // 객체 배열

예시

int[] array = {5, 3, 2, 7, 1};
Arrays.sort(array);

위 사례에서 array 배열은 {1, 2, 3, 5, 7}로 정렬됩니다.

binarySearch()

binarySearch() 메서드는 오름차순으로 정렬된 배열에서 특정 값의 인덱스를 찾습니다.
이 메서드는 이진 검색 알고리즘을 사용하므로 정확히 정렬된 배열에서만 사용해야 합니다.

메서드의 형식

public static int binarySearch(int[] a, int key) // 원시 타입 배열
public static <T> int binarySearch(T[] a, T key) // 객체 배열

여기서 반환된 인덱스는 키가 발견된 위치입니다. 해당 값이 없을 경우는 -1 또는 음수 반환합니다.

예시

int[] array = {1, 2 3, 5, 7};
int index = Arrays.binarySearch(array, 3);

위 사례에서 index는 값 3의 인덱스인 2가 반환됩니다. 여기에 설명된 메서드를 사용하면 배열을 쉽게 정렬하거나 검색할 수 있습니다. 이 메서드들은 다양한 타입의 배열을 지원하며, 원하는 결과를 얻기 위해 정확한 정렬이 필요함에 유의해야 합니다.

💁‍♂️ 배열의 비교와 출력 - equals(), toString()

equals()

equals() 메서드는 두 배열이 논리적으로 동일한지 비교해줍니다.
두 배열의 길이와 각 요소의 값이 같은 경우에만 동일한 것으로 간주됩니다.
이 메서드는 기본 데이터 타입 배열과 객체 배열 모두에서 사용할 수 있습니다.

메서드의 형식

public static boolean equals(int[] a, int[] b) // 원시 타입 배열
public static boolean equals(Object[] a, Object[] b) // 객체 배열

예시

int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
boolean isEqual = Arrays.equals(array1, array2);

위 사례에서 isEqual은 배열 array1과 array2가 동일하므로 true를 반환합니다.

toString()

toString() 메서드는 배열의 모든 요소를 포함하는 문자열을 생성합니다.
이 메서드는 배열의 내용을 쉽게 출력하거나 로깅하는 데 유용하게 사용됩니다.
기본 데이터 타입 배열과 객체 배열 모두에서 사용할 수 있습니다.

메서드의 형식

public static String toString(int[] a) // 원시 타입 배열
public static String toString(Object[] a) // 객체 배열

예시

int[] array = {1, , 3};
String arrayString = Arrays.toString(array);

위 사례에서 arrayString은 배열 array의 내용을 문자열로 나타내고, [1, 2, 3] 값을 반환합니다.
이 두 메서드를 사용하여 배열을 비교하거나 출력할 수 있으며, 배열 작업을 효과적으로 관리할 수 있습니다.
이 메서드들은 배열에 대한 논리적 동일성 검사와 사용자가 읽을 수 있는열 생성에 유용한 도구를 제공합니다.

💁‍♂️ 배열을 List로 변환 - asList(Object... a)

asList()

자바의 Arrays 클래스에는 배열을 List로 변환하는 asList() 메서드가 있습니다. 이 메서드를 사용하면 배열의 요소를 고정 크기의 List로 변환할 수 있어, List 관련 메서드와 작업을 수행할 수 있습니다. asList() 메서드는 가변 매개변수 Object... a를 받아들이며, 반환 타입은 List입니다.

메서드의 형식

public static <T> List<T> asList(T... a)

여기서 T는 제네릭 형태의 타입이고 a는 사용자가 제공하는 배열입니다.

예시 1
Integer[] array = {1, 2, 3, 4, 5};
List<Integer> list = Arrays.asList(array);

위 사례에서 array 배열을 고정 크기의 list로 변환합니다. 변환된 리스트는 원래 배열과 같은 순서로 요소를 나타냅니다.

변환된 List에는 몇 가지 주의해야 할 점

  1. List의 크기는 고정되어 있어 새로운 요소를 추가하거나 삭제할 수 없습니다. 크기를 변경하려면 새로운 ArrayList를 생성해야 합니다.
  2. 반환된 List는 수정 가능하나 구조적 수정(주요 변경)은 허용되지 않습니다. 즉, 요소를 추가, 삭제하거나 크기를 변경할 수 없습니다.
  3. 반환된 List는 원본 배열과 연동되어 있으므로 배열의 내용을 변경하면 List에도 적용됩니다.

asList() 메서드를 사용하여 배열을 List로 변환하면 배열에서 List의 기능을 활용하여 작업을 수행할 수 있습니다.
하지만 고정 크기와 연동성을 고려하여 사용해야 합니다.

예시2

1. 문자열 배열을 List로 변환하기

String[] stringArray = {"apple", "banana", "cherry", "kiwi"};
List<String> stringList = Arrays.asList(stringArray);
System.out.println("String List: " + stringList);

위 예시에서 stringArray를 고정 크기의 stringList로 변환합니다.

출력 결과

String List: [apple, banana, cherry, kiwi]

2. 기본 데이터 타입 배열을 List로 변환하기 (unboxing 과정 필요)

int[] intArray = {1, 2, 3, 4, 5};
List<Integer> intList = Arrays.stream(intArray).boxed().collect(Collectors.toList());
System.out.println("Integer List: " + intList);

intArray를 고정 크기의 intList로 변환하기 위해 스트림을 사용하여 boxing 작업을 거칩니다.

출력 결과

Integer List: [1, 2, 3, 4, 5]

3. 소수 배열을 List로 변환

Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
List<Double> doubleList = Arrays.asList(doubleArray);
System.out.println("Double List: " + doubleList);

doubleArray를 고정 크기의 doubleList로 변환합니다.

출력 결과

Double List: [1.1, 2.2, 3.3, 4.4, 5.5]
profile
컴퓨터공학과에 재학중이며, 백엔드를 지향하고 있습니다. 많이 부족하지만 열심히 노력해서 실력을 갈고 닦겠습니다. 부족하고 틀린 부분이 있을 수도 있지만 이쁘게 봐주시면 감사하겠습니다. 틀린 부분은 댓글 남겨주시면 제가 따로 학습 및 자료를 찾아봐서 제 것으로 만들도록 하겠습니다. 귀중한 시간 방문해주셔서 감사합니다.

0개의 댓글