개발 일기

[Java] 자료 구조 - Array와 Collection Framework의 인터페이스들 본문

Back-End/Java

[Java] 자료 구조 - Array와 Collection Framework의 인터페이스들

개발 일기장 주인 2024. 10. 22. 18:11

자료구조(Data Structure)란?

컴퓨터 프로그램에서 데이터를 효율적으로 저장하고 관리하기 위해 설계된 구조로 Array, List, Map 등이 대표적이다.

배열(Array)

  • 배열은 인덱스와 값을 일대일 대응해 관리하는 자료구조
  • 데이터를 저장할 수 있는 모든 공간은 인덱스와 일대일 대응하므로 어떤 위치에 있는 데이터든 한 번에 접근할 수 있다.
  • 일반적으로 배열은 선언할때 크기가 고정
  • 특정 인덱스에 있는 데이터를 읽거나 변경하는 데 O(1)의 시간 복잡도
  • 데이터를 순차적으로만 접근할 수 있어 위치를 모르는 경우 효율이 떨어짐.
  • 배열에 들어가는 데이터는 모두 동일한 자료형 이어야 함.
  • 배열 중간에 값을 추가하려면 기존 데이터를 모두 이동시켜야 함.

// 1차원 배열
int[] arr = {0, 0, 0, 0, 0, 0}; // ➊
int[] arr = new int[6]; // ➋ 결괏값 동일

// 2차원 배열
int[][] arr = new int[2][3];
int[][] arr = {{1, 2, 3}, {4, 5 ,6}};
arr[1][2] = 7; // 값 변경

1차원 배열과 2차원 배열의 메모리 저장

int[] arr = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(arr));
// 출력: [1, 2, 3, 4, 5]
int index = Arrays.binarySearch(arr, 3);
System.out.println(index);
// 출력: 2 (3이 있는 위치의 인덱스)
int[] newArr = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(newArr));
// 출력: [1, 2, 3]
int[] newArr = Arrays.copyOfRange(arr, 1, 4);
System.out.println(Arrays.toString(newArr));
// 출력: [2, 3, 4]
int sum = Arrays.stream(arr).sum();
System.out.println(sum);
// 출력: 15

int[] arr = {5, 2, 8, 3, 1};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// 출력: [1, 2, 3, 5, 8]

int[] arr = new int[5];
Arrays.fill(arr, 7);
System.out.println(Arrays.toString(arr));
// 출력: [7, 7, 7, 7, 7]

int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = {1, 2, 4};
System.out.println(Arrays.equals(arr1, arr2)); // 출력: true
System.out.println(Arrays.equals(arr1, arr3)); // 출력: false

int[][] arr1 = {{1, 2}, {3, 4}};
int[][] arr2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(arr1, arr2));
// 출력: true

String[] arr = {"apple", "banana", "cherry"};
List<String> list = Arrays.asList(arr);
System.out.println(list);
// 출력: [apple, banana, cherry]
ArrayList와의 차이점?
자바에서 유사한 기능을 가진 자료구조인  ArrayList가 있는데 배열과의 차이점을 정리해보면
배열은 처음 선언할 때 배열의 크기가 결정되고, ArraList의 크기는 동적이다.
즉, 정확한 데이터의 개수를 알 수 있다면 코드가 더 간결하고 속도가 더 빠른 배열을 사용하면 되고, 저장해야 할 데이터의 개수를 정확히 알 수 없다면 ArrayList를 사용하는 것이 유리하다.

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

컬렉션 프레임워크는 자바에서 데이터를 저장하는 클래스들을 표준화한 설계 구조

Collection, Map, List, Set 인터페이스를 중심으로 다음과 같은 클래스 계층구조를 형성

자바 컬렉션 프레임워크 구조


우선 4개의  주요 인터페이스에 대해 하나씩 알아보자

컬렉션 인터페이스 (Collection Interface)

  • List와 Set 의 상위 인터페이스
  •  List 와 Set 를 구현한 모든 클래스들은 Collection 인터페이스의 메서드를 사용할 수 있으므로 구현클래스와 상관없이 동일한 방법으로 데이터를 다룰 수 있다.
  • 컬렉션안에 들어가는 데이터들은 모든 타입이 가능하지만 타입이 다른 경우 일관된 처리가 어렵기 때문에 타입 파라미터를 사용
Collection<String> c = new HashSet<>(); // new HashSet<String>();인데 타입 추론으로 생략 가능

컬렉션 메소드

 

컬렉션 객체 생성

  • Java의 모든 컬렉션 클래스는 java.util 패키지에 포함
  • 일반적으로 상위 인터페이스 타입을 사용하여 참조 변수를 선언하고, 구체적인 클래스를 사용하여 객체를 생성하는 것이 좋다.
// 권장되지 않는 방법: 구체적인 클래스 타입으로 참조
ArrayList<String> list = new ArrayList<>(); // 비권장

// 권장되는 방법: 상위 인터페이스 타입으로 참조
List<String> list = new ArrayList<>(); // 권장

 

컬렉션에 데이터 추가, 삭제, 확인 그리고 반환

// 컬렉션에 데이터 추가, 삭제
c1.add("one");
c1.addAll(c2);
c1.remove("one");
c1.remove("c2");
c1.retainAll("four");
c1.clear();

// 컬렉션 데이터 확인 및 변환
c1.isEmpty();
c1.contains("zero");
c1.containsAll(c2);
int size = c1.size();
Object[] converted1 = c1.toArray();
String[] converted2 = c1.toArray(new String[c1.size()]);

 

 

컬렉션 데이터의 사용

  • 컬렉션에 들어있는 데이터를 사용하기 위해서는 다소 복잡한 과정
  • 컬렉션 자체는 구체적인 구현이 아니므로 직접적으로 데이터에 접근하는 방법은 포함되어 있지 않는다.

리스트 인터페이스 (List Interface) - 순서 O, 중복 O

  • Collection의 하위 인터페이스
  • 중복을 허용하며, 삽입된 순서대로 저장하는 특징
  • Java의 List 인터페이스는 java.util 패키지에 속하며, 이를 구현한 대표적인 클래스들은 ArrayList, LinkedList, Vector 등이 있다.
  • 배열과 달리, List는 크기가 가변적

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

public class ListExample {
    public static void main(String[] args) {
        // List 선언 및 초기화 (ArrayList 사용)
        List<String> fruits = new ArrayList<>();
        
        // 요소 추가 (add 메서드)
        fruits.add("apple");       // 리스트에 "apple" 추가
        fruits.add("banana");      // 리스트에 "banana" 추가
        fruits.add("cherry");      // 리스트에 "cherry" 추가
        System.out.println("After adding elements: " + fruits);
        
        // 인덱스 위치에 요소 추가 (add(int index, E element) 메서드)
        fruits.add(1, "blueberry");  // 인덱스 1에 "blueberry" 추가
        System.out.println("After adding 'blueberry' at index 1: " + fruits);
        
        // 요소 접근 (get 메서드)
        String secondFruit = fruits.get(1); // 인덱스 1에 있는 요소 가져오기
        System.out.println("Element at index 1: " + secondFruit);
        
        // 요소 수정 (set 메서드)
        fruits.set(2, "mango");     // 인덱스 2의 요소를 "mango"로 변경
        System.out.println("After setting index 2 to 'mango': " + fruits);
        
        // 요소 제거 (remove 메서드)
        fruits.remove(3);           // 인덱스 3의 요소 제거 ("cherry")
        System.out.println("After removing element at index 3: " + fruits);
        
        // 리스트 크기 확인 (size 메서드)
        System.out.println("Size of the list: " + fruits.size());
        
        // 특정 요소 포함 여부 확인 (contains 메서드)
        boolean hasApple = fruits.contains("apple");
        System.out.println("Does the list contain 'apple'? " + hasApple);
        
        // 리스트가 비어 있는지 여부 확인 (isEmpty 메서드)
        boolean isEmpty = fruits.isEmpty();
        System.out.println("Is the list empty? " + isEmpty);
        
        // 전체 리스트 출력
        System.out.println("Final list: " + fruits);
    }
}
/*
 * 출력 결과
 * After adding elements: [apple, banana, cherry]
 * After adding 'blueberry' at index 1: [apple, blueberry, banana, cherry]
 * Element at index 1: blueberry
 * After setting index 2 to 'mango': [apple, blueberry, mango, cherry]
 * After removing element at index 3: [apple, blueberry, mango]
 * Size of the list: 3
 * Does the list contain 'apple'? true
 * Is the list empty? false
 * Final list: [apple, blueberry, mango]
 */

세트 인터페이스 (Set Interface) - 순서 X, 중복 X

    • Collection의 하위 인터페이스
    • 중복이 허용되지 않고 기본적으로는 순서가 유지 되지 않는다.
    • 구현 클래스로는 HashSet, TreeSet, LinkedHashSet, EnumSet, CopyOnWriteArraySet 등
    • 순서가 필요한 경우 LinkedHashSet 클래스나 SortedSet 인터페이스를 구현한 TreeSet등을 사용

Collection 인터페이스와 메소드 동일

추가적으로,

집합과 관련된 메소드

위 표는 집합과 관련된 메소드로 컬렉션에 변화가 있으면 true, 없으면 false이다.

public class SetTest {
    public static void main(String[] args) {
        // create new Set
        Set<String> s1 = new HashSet<>();
        Set<String> s2 = Set.of("three", "four");

        // add element to Set
        s1.addAll(Arrays.asList("one", "two"));
        s1.addAll(s2);
        s1.add("five");
        s1.add("two");
        s1.remove("five");

        System.out.println("## element in Set");
        System.out.println(s1);

        // print all elements using stream api
        s1.stream().forEach(System.out::println);

        System.out.println("## check exist element in Set");
        System.out.println(s1.contains("one"));

        // create new LinkedHashSet and add elements
        Set<String> lset = new LinkedHashSet<>();
        lset.addAll(Arrays.asList("one", "two", "three", "four"));
        lset.add("five");

        System.out.println("\n## element in LinkedHashSet");
        System.out.println(lset);

        // get Iterator from LinkedHashSet
        System.out.println("## print element using Iterator");
        Iterator<String> iter = lset.iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());
        }

        // create new TreeSet and add elements
        Set<Integer> tset = new TreeSet<>();
        tset.addAll(Arrays.asList(50, 10, 60, 20));

        System.out.println("\n## elements in TreeSet");
        System.out.println(tset);

        // Descending sort with stream api
        System.out.println("## Descending sort with stream api");
        tset.stream().sorted((o1, o2) -> o2.toString().compareTo(o1.toString())).forEach(System.out::println);
    }
}
/**
    ## element in Set
    [one, two, three, four]

    ## check exist element in Set
    true

    ## element in LinkedHashSet
    [one, two, three, four, five]

    ## print element using Iterator
    one
    two
    three
    four
    five

    ## elements in TreeSet
    [10, 20, 50, 60]

    ## Descending sort with stream api
    60
    50
    20
    10

 */

맵 인터페이스(Map Interface) - 순서 X, 중복(키 X, 값 O)

  • Java Collections Framework(JCF)에서 제공하는 인터페이스 중 하나
  • 키-값(Key-Value) 쌍으로 데이터를 저장하는 자료구조
  • Map은 Collection 인터페이스를 상속하지 않는다.
  • Key 집합은 Set 으로 볼 수 있고 Value 집합은 Collection 으로 볼 수 있다.
  • Map 인터페이스는 java.util 패키지에 속하며, 이를 구현한 대표적인 클래스들은 HashMap, TreeMap, LinkedHashMap 등이 있고 Hashtable이 HashMap의 old  버전
  • 특정 키(Key)를 사용하여 데이터를 효율적으로 검색하거나 수정할 수 있도록 합니다. Map은 순차적인 구조인 List와 달리, 순서를 유지하지 않고 데이터를 저장할 수 있지만, 키를 통해 빠르게 값을 찾을 수 있는 장점

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

public class MapExample {
    public static void main(String[] args) {
        // HashMap 생성
        Map<String, Integer> fruitMap = new HashMap<>();

        // 1. put() 메서드로 키-값 쌍 추가
        fruitMap.put("apple", 1);
        fruitMap.put("banana", 2);
        fruitMap.put("orange", 3);

        // 2. get() 메서드로 값 조회
        System.out.println("apple의 수: " + fruitMap.get("apple")); // 출력: apple의 수: 1

        // 3. containsKey() 메서드로 키 존재 여부 확인
        System.out.println("banana 존재 여부: " + fruitMap.containsKey("banana")); // 출력: banana 존재 여부: true

        // 4. containsValue() 메서드로 값 존재 여부 확인
        System.out.println("값 2 존재 여부: " + fruitMap.containsValue(2)); // 출력: 값 2 존재 여부: true

        // 5. size() 메서드로 맵의 크기 확인
        System.out.println("현재 맵 크기: " + fruitMap.size()); // 출력: 현재 맵 크기: 3

        // 6. keySet() 메서드로 모든 키 조회
        Set<String> keys = fruitMap.keySet();
        System.out.println("모든 키: " + keys); // 출력: 모든 키: [banana, apple, orange]

        // 7. values() 메서드로 모든 값 조회
        System.out.println("모든 값: " + fruitMap.values()); // 출력: 모든 값: [2, 1, 3]

        // 8. entrySet() 메서드로 모든 키-값 쌍 조회
        Set<Map.Entry<String, Integer>> entries = fruitMap.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            System.out.println("키: " + entry.getKey() + ", 값: " + entry.getValue());
        }
        // 출력:
        // 키: banana, 값: 2
        // 키: apple, 값: 1
        // 키: orange, 값: 3

        // 9. remove() 메서드로 특정 키-값 쌍 제거
        fruitMap.remove("orange");
        System.out.println("제거 후 맵 크기: " + fruitMap.size()); // 출력: 제거 후 맵 크기: 2

        // 10. getOrDefault() 메서드로 키에 대한 기본값 조회
        System.out.println("kiwi의 수: " + fruitMap.getOrDefault("kiwi", 0)); // 출력: kiwi의 수: 0

        // 11. replace() 메서드로 값 수정
        fruitMap.replace("banana", 4);
        System.out.println("수정된 banana의 수: " + fruitMap.get("banana")); // 출력: 수정된 banana의 수: 4

        // 12. replace(K key, V oldValue, V newValue) 사용 예
        boolean replaced = fruitMap.replace("apple", 1, 5);
        System.out.println("apple의 값 변경 여부: " + replaced); // 출력: apple의 값 변경 여부: true
        System.out.println("변경된 apple의 수: " + fruitMap.get("apple")); // 출력: 변경된 apple의 수: 5

        // 13. clear() 메서드로 맵의 모든 항목 제거
        fruitMap.clear();
        System.out.println("모든 항목 제거 후 맵 크기: " + fruitMap.size()); // 출력: 모든 항목 제거 후 맵 크기: 0
    }
}