본문 바로가기

Java

Java Collection Framework

Java Collections Framework

자바의 컬렉션 프레임워크는 데이터를 저장, 관리, 조작하기 위한 다양한 자료 구조와 알고리즘을 제공합니다. 이를 사용하면 데이터를 효율적으로 조작할 수 있으며, 반복문과 같은 일반적인 작업을 단순화할 수 있습니다.

Java Collection Framework의 View

자바 컬렉션 프레임워크에서 view는 컬렉션을 특정한 방식으로 보여주는 개념을 의미합니다. View는 기존 컬렉션을 읽기 전용 또는 필터링된 형태로 제공하여 컬렉션을 제한하거나 변형할 수 있는 방법을 제공합니다. 기본 컬렉션과는 별개의 객체로써, 원본 컬렉션에 대한 변경이 해당 view에 반영됩니다.

컬렉션 프레임워크에서 제공하는 몇 가지 주요한 view 개념은 다음과 같습니다:

1. subList(): List 인터페이스의 메소드로, 원본 리스트의 일부분을 나타내는 새로운 List view를 반환합니다. 이 view는 원본 리스트와 연결되어 변경 사항이 상호 반영됩니다.

2. keySet(), values(), entrySet(): Map 인터페이스의 메소드로, 각각 키(key), 값(value), 키-값(entry) 쌍들을 나타내는 view를 반환합니다. 이러한 view는 Map의 원본 데이터를 참조하여 동작하므로, 원본 Map의 변경 사항이 반영됩니다.

3. Collections.unmodifiableXXX(): Collections 클래스에서 제공하는 메소드로, 읽기 전용 view를 생성합니다. unmodifiableXXX() 메소드를 사용하여 기존 컬렉션을 래핑하면 해당 컬렉션은 변경할 수 없는 읽기 전용 컬렉션으로 사용될 수 있습니다.

View는 기존 컬렉션의 일부분 또는 변형된 형태를 표현하기 때문에 메모리 사용량이 추가로 필요하지 않습니다. 이는 효율적인 데이터 처리를 가능하게 합니다. 또한, View는 원본 컬렉션의 변경 사항을 실시간으로 반영하므로, 데이터 일관성을 유지하고 중복 작업을 피할 수 있습니다.

View를 활용하여 원본 컬렉션을 제한하거나 변형하여 원하는 형태로 데이터를 조작할 수 있습니다. 이를 통해 컬렉션 프레임워크의 다양한 기능을 유연하게 활용할 수 있습니다.

뷰 컬렉션(View Collections)은 다양한 용도로 사용될 수 있습니다. 몇 가지 일반적인 사용 사례는 다음과 같습니다:

1. 읽기 전용 뷰: Collections.unmodifiableXXX() 메소드를 사용하여 원본 컬렉션을 읽기 전용으로 래핑하는 뷰 컬렉션을 생성할 수 있습니다. 이를 통해 원본 컬렉션을 보호하고, 읽기 작업에 대한 안전성을 확보할 수 있습니다.

2. 동기화된 뷰: Collections.synchronizedXXX() 메소드를 사용하여 원본 컬렉션을 동기화된 뷰 컬렉션으로 래핑할 수 있습니다. 이를 통해 멀티스레드 환경에서의 안전성을 보장하고, 동시 접근에 대한 동기화를 처리할 수 있습니다.

3. 부분 뷰: List.subList(), NavigableSet.subSet(), Map.entrySet() 등의 메소드를 사용하여 원본 컬렉션의 일부분을 나타내는 뷰 컬렉션을 생성할 수 있습니다. 이를 활용하여 원본 컬렉션의 특정 범위 또는 부분에 집중하여 작업을 수행할 수 있습니다.

  1. 필터링 뷰: 람다식이나 특정 조건을 만족하는 요소들로 이루어진 뷰 컬렉션을 생성할 수 있습니다. 이를 활용하여 원본 컬렉션의 특정 요건에 맞는 요소들을 필터링하여 작업할 수 있습니다.

뷰 컬렉션은 원본 컬렉션을 보다 제한적인 형태로 표현하거나, 기능을 확장시킬 수 있는 방법을 제공합니다. 이를 통해 코드의 가독성, 유연성, 재사용성을 높일 수 있습니다. 또한, 메모리 사용량을 줄이고 작업을 효율적으로 수행할 수 있는 장점도 가지고 있습니다.

public static void testViewCollection() {
    List<String> originalList = new ArrayList<>();
    originalList.add("Apple");
    originalList.add("Banana");
    originalList.add("Cherry");

    // Create an unmodifiable view collection
    List<String> unmodifiableView = Collections.unmodifiableList(originalList);

    // Trying to modify the view collection will throw an UnsupportedOperationException
    // unmodifiableView.add("Durian"); // This will throw an exception

    // Changes made to the original list are reflected in the view collection
    originalList.add("Elderberry");
    originalList.remove("Apple");

    // Iterating over the view collection
    for (String fruit : unmodifiableView) {
        System.out.println(fruit);
    }
}

Java Collection Framework의 Bulk

Bulk 개념은 자바 컬렉션 프레임워크에서 대량의 요소를 한 번에 처리하는 개념을 말합니다. Bulk 연산은 컬렉션에 대해 한 번에 여러 개의 요소를 추가, 제거 또는 조작하는 작업을 수행할 수 있는 방법을 제공합니다. 이를 통해 코드의 가독성과 성능을 개선할 수 있습니다.

컬렉션 프레임워크에서 제공하는 주요 Bulk 연산은 다음과 같습니다:

1. addAll(Collection<? extends E> c): 주어진 컬렉션 c의 모든 요소를 현재 컬렉션에 추가합니다.

2. removeAll(Collection<?> c): 주어진 컬렉션 c와 현재 컬렉션의 공통 요소를 모두 제거합니다.

3. retainAll(Collection<?> c): 현재 컬렉션과 주어진 컬렉션 c의 교집합인 요소만을 남기고 나머지 요소를 제거합니다.

Bulk 연산은 반복문을 사용하여 하나씩 요소를 처리하는 것보다 더 효율적입니다. 내부적으로 최적화되어 있으며, 컬렉션의 크기와 상관없이 고속으로 작업을 수행할 수 있습니다. Bulk 연산을 사용하면 코드의 가독성이 향상되고, 중복 작업을 피할 수 있어 프로그램의 효율성을 높일 수 있습니다.

Bulk 연산은 주로 Collection 인터페이스의 구현체들에서 제공됩니다. 다양한 컬렉션 클래스들은 Bulk 연산을 지원하므로, 프로그램의 요구사항에 맞게 선택하여 활용할 수 있습니다. Bulk 연산을 사용하여 컬렉션을 효율적으로 처리하고 관리하는 것은 컬렉션 프레임워크의 장점 중 하나입니다.

Iterable

자바의 Iterable 인터페이스는 컬렉션 객체가 반복 가능한(iterable) 객체임을 나타내는 인터페이스입니다. 이 인터페이스를 구현하는 클래스는 Iterator 객체를 반환할 수 있어야 합니다. Iterator 객체는 컬렉션 내의 요소를 하나씩 반복적으로 접근할 수 있는 메서드를 제공합니다.

Iterable 인터페이스를 설계한 이유는 다음과 같습니다

1. 향상된 for 루프 지원: Iterable 인터페이스를 구현하는 클래스는 향상된 for 루프(for-each loop)를 사용하여 컬렉션의 요소에 접근할 수 있습니다. 이를 통해 반복 작업을 더 간단하고 직관적으로 처리할 수 있습니다.
2. 일관성 있는 반복 인터페이스 제공: Iterable 인터페이스는 컬렉션 프레임워크의 핵심 인터페이스 중 하나입니다. 이를 구현함으로써 다양한 컬렉션 클래스들이 일관성 있는 반복 작업 인터페이스를 제공할 수 있습니다. 이는 다양한 컬렉션 객체에 대해 통일된 방식으로 반복 작업을 수행할 수 있게 해주며, 코드의 가독성과 유지 보수성을 향상시킵니다.
3. 다형성과 호환성 확보: Iterable 인터페이스를 구현하는 클래스는 다형성을 활용할 수 있습니다. 즉, 컬렉션을 다루는 다양한 메서드나 알고리즘에서 Iterable 인터페이스를 Argument로 전달 받아서 동작할 수 있습니다. 이를 통해 호환성이 확보되며, 유연하고 재사용 가능한 코드를 작성할 수 있습니다.
4. 내부 반복을 위한 Iterator 패턴: Iterable 인터페이스는 Iterator 패턴을 지원하기 위해 설계되었습니다. Iterator 객체를 반환하는 iterator() 메서드를 제공하여 컬렉션의 요소에 접근하는 데 사용됩니다. Iterator 패턴은 컬렉션의 내부 구현과 반복 작업을 분리시키고, 컬렉션의 내부 구조를 노출시키지 않고도 안전하게 반복 작업을 수행할 수 있게 해줍니다.

따라서, Iterable 인터페이스는 자바 컬렉션 프레임워크의 일관성과 유연성을 높이고, 반복 작업을 보다 쉽고 효율적으로 처리하기 위해 설계되었습니다.

Iterator 패턴은 객체의 컬렉션에서 순차적으로 요소에 접근하는 데 사용되는 디자인 패턴입니다. 이 패턴은 컬렉션의 내부 구조와 구체적인 요소에 대한 정보를 노출시키지 않고 반복 작업을 수행할 수 있도록 합니다.
Iterator 패턴은 다음과 같은 구성 요소로 이루어져 있습니다:
1. Iterator (반복자): 컬렉션의 요소를 순차적으로 접근하기 위한 인터페이스입니다. 주요 메서드로는 다음 요소의 존재 여부를 확인하는 hasNext() 메서드와 현재 요소를 반환하고 다음 요소로 이동하는 next() 메서드가 있습니다.
2. ConcreteIterator (구체적인 반복자): Iterator 인터페이스를 구현한 실제 반복자입니다. 컬렉션 내부의 요소에 접근하고 반복 작업을 수행하는 역할을 합니다.
3. Aggregate (집합체): 객체의 컬렉션을 나타내는 인터페이스입니다. 이 인터페이스를 구현한 클래스는 Iterator 객체를 생성하는 메서드인 iterator()를 제공해야 합니다.
4. ConcreteAggregate (구체적인 집합체): Aggregate 인터페이스를 구현한 실제 컬렉션 클래스입니다. Iterator 객체를 생성하는 iterator() 메서드를 구현하여 구체적인 반복자를 반환합니다.

Iterator 패턴을 사용하면 다음과 같은 이점이 있습니다
- 컬렉션의 내부 구조를 알 필요 없이 컬렉션의 모든 요소에 접근할 수 있습니다.
- 컬렉션의 구현과 반복 작업이 분리되어 컬렉션의 내부 구조가 변경되어도 반복 작업에 영향을 주지 않습니다.
- 다양한 종류의 컬렉션에 대해 일관된 반복 인터페이스를 제공하여 코드의 가독성과 재사용성을 향상시킵니다.

Iterator 패턴은 자바의 Iterable 인터페이스와 Iterator 인터페이스를 구현하여 사용됩니다. Iterable 인터페이스는 iterator() 메서드를 제공하여 Iterator 객체를 반환하고, Iterator 객체는 컬렉션의 요소에 접근하는 데 사용됩니다.

Iterable 인터페이스는 다음과 같이 정의됩니다

package java.lang;

import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

위의 코드에서 T는 컬렉션에 저장된 Element의 타입을 나타냅니다.

※Spliterator 인터페이스Spliterator(분할자)는 자바 8부터 추가된 인터페이스로, 병렬 처리를 위한 컬렉션의 분할과 반복 작업을 지원합니다. Spliterator는 "split"과 "iterator"를 합친 용어로, 컬렉션을 분할하고 각 부분에서 요소를 반복 처리하는 기능을 제공합니다. Spliterator는 컬렉션을 분할하여 병렬 처리를 가능하게 하며, 내부적으로 분할된 부분을 동기화하거나 정렬하는 등의 기능을 제공합니다. Spliterator를 사용하여 컬렉션을 병렬로 처리하면 멀티스레드 환경에서 성능을 향상시킬 수 있습니다.

Iterator 인터페이스는 다음과 같이 정의됩니다

package java.util;

import java.util.function.Consumer;

public interface Iterator<E> {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

Iterable 인터페이스를 구현하는 예제 클래스를 살펴보겠습니다. 아래의 코드는 정수를 저장하는 간단한 컬렉션 클래스인 MyCollection을 구현한 예제입니다.

import java.util.Iterator;

public class MyCollection implements Iterable<Integer> {
    private int[] elements;
    private int size;

    public MyCollection(int[] elements) {
        this.elements = elements;
        this.size = elements.length;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<Integer> {
        private int currentIndex;

        public MyIterator() {
            this.currentIndex = 0;
        }

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @Override
        public Integer next() {
            if (hasNext()) {
                return elements[currentIndex++];
            } else {
                throw new NoSuchElementException();
            }
        }
    }

    public static void main(String[] args) {
        int[] elements = {1, 2, 3, 4, 5};
        MyCollection collection = new MyCollection(elements);

        for (int num : collection) {
            System.out.println(num);
        }
    }
}

위의 코드에서 MyCollection 클래스는 Iterable 인터페이스를 구현합니다. 따라서 iterator() 메서드를 구현해야 합니다. 이 메서드는 MyIterator 클래스의 객체를 반환합니다.

MyIterator 클래스는 Iterator 인터페이스를 구현하며, hasNext() 메서드와 next() 메서드를 구현합니다. 이를 통해 컬렉션의 요소에 접근할 수 있습니다.

마지막으로 main() 메서드에서는 MyCollection 객체를 생성하고 for-each 문을 사용하여 컬렉션의 요소에 접근합니다. 이를 통해 컬렉션의 요소를 반복적으로 출력합니다.

Collection Interface

Collection 인터페이스는 자바의 컬렉션 프레임워크에서 가장 기본이 되는 인터페이스 중 하나입니다. Collection 인터페이스는 객체들의 그룹을 나타내는 컬렉션을 표현하기 위한 공통 동작을 정의합니다.

Collection 인터페이스는 다음과 같은 주요 메소드들을 정의하고 있습니다:

메소드설명

int size() 컬렉션에 포함된 요소의 개수를 반환합니다.
boolean isEmpty() 컬렉션이 비어있는지 여부를 반환합니다.
boolean contains(Object o) 컬렉션에 특정 요소가 포함되어 있는지 여부를 반환합니다.
Iterator iterator() 컬렉션의 요소를 하나씩 순회하는 데 사용되는 Iterator 객체를 반환합니다.
Object[] toArray() 컬렉션의 모든 요소를 배열로 반환합니다.
boolean add(E e) 컬렉션에 요소를 추가합니다.
boolean remove(Object o) 컬렉션에서 특정 요소를 제거합니다.
boolean containsAll(Collection<?> c) 컬렉션이 다른 컬렉션의 모든 요소를 포함하고 있는지 여부를 반환합니다.
boolean addAll(Collection<? extends E> c) 다른 컬렉션의 모든 요소를 현재 컬렉션에 추가합니다.
boolean removeAll(Collection<?> c) 다른 컬렉션과 현재 컬렉션의 공통 요소를 모두 제거합니다.
boolean retainAll(Collection<?> c) 현재 컬렉션과 다른 컬렉션의 교집합인 요소만 남기고 제거합니다.
void clear() 컬렉션의 모든 요소를 제거하여 비웁니다.

Collection 인터페이스는 순서가 없는 요소들의 그룹을 표현하며, 중복된 요소를 허용할 수 있습니다. Collection 인터페이스는 List, Set, Queue 등의 다양한 구현체를 가질 수 있으며, 각각은 특정한 동작과 특성을 가지고 있습니다. 이를 통해 데이터를 효율적으로 저장, 관리, 조작할 수 있습니다.

Collection 인터페이스의 정의는 다음과 같습니다.

package java.util;

import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public interface Collection<E> extends Iterable<E> {
    // Query Operations
    int size();
    boolean isEmpty();    
    boolean contains(Object o);    
    Iterator<E> iterator();    
    Object[] toArray();    
    <T> T[] toArray(T[] a);    
    default <T> T[] toArray(IntFunction<T[]> generator) {
        return toArray(generator.apply(0));
    }

    // Modification Operations    
    boolean add(E e);    
    boolean remove(Object o);

    // Bulk Operations    
    boolean containsAll(Collection<?> c);    
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }    
    boolean retainAll(Collection<?> c);    
    void clear();

    // Comparison and hashing    
    boolean equals(Object o);    
    int hashCode();    
    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }    
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }    
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

Collection 인터페이스를 구현한 구체 클래스를 다음과 같이 정의할 수 있습니다.

import java.util.Arrays;
import java.util.Iterator;

public class MyCollection<T> implements Collection<T> {
    private static final int DEFAULT_CAPACITY = 10;
    private T[] elements;
    private int size;

    public MyCollection() {
        elements = (T[]) new Object[DEFAULT_CAPACITY];
        size = 0;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean contains(Object o) {
        for (T element : elements) {
            if (element != null && element.equals(o)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    @Override
    public Object[] toArray() {
        return Arrays.copyOf(elements, size);
    }

    @Override
    public <E> E[] toArray(E[] a) {
        if (a.length < size) {
            return (E[]) Arrays.copyOf(elements, size, a.getClass());
        }
        System.arraycopy(elements, 0, a, 0, size);
        if (a.length > size) {
            a[size] = null;
        }
        return a;
    }

    @Override
    public boolean add(T e) {
        if (size == elements.length) {
            expandCapacity();
        }
        elements[size++] = e;
        return true;
    }

    @Override
    public boolean remove(Object o) {
        for (int i = 0; i < size; i++) {
            if (elements[i] != null && elements[i].equals(o)) {
                removeElementAtIndex(i);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        for (Object element : c) {
            if (!contains(element)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean addAll(Collection<? extends T> c) {
        boolean modified = false;
        for (T element : c) {
            if (add(element)) {
                modified = true;
            }
        }
        return modified;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        for (Object element : c) {
            modified |= remove(element);
        }
        return modified;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean modified = false;
        for (int i = 0; i < size; i++) {
            if (!c.contains(elements[i])) {
                removeElementAtIndex(i);
                modified = true;
                i--;
            }
        }
        return modified;
    }

    @Override
    public void clear() {
        Arrays.fill(elements, null);
        size = 0;
    }

    // Helper method to remove an element at the specified index
    private void removeElementAtIndex(int index) {
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        elements[--size] = null;
    }

    // Helper method to expand the capacity of the internal array
    private void expandCapacity() {
        int newCapacity = elements.length * 2;
        elements = Arrays.copyOf(elements, newCapacity);
    }

    // Custom iterator implementation
    private class MyIterator implements Iterator<T> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return elements[currentIndex++];
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}

그리고 이 구체 클래스를 사용하는 클라이언트인 Main 클래스를 다음과 같이 정의합니다.

public class Main {
    public static void main(String[] args) {
        MyCollection<Integer> collection = new MyCollection<>();
        collection.add(10);
        collection.add(20);
        collection.add(30);

        for (int num : collection) {
            System.out.println(num);
        }

        collection.remove(20);
        System.out.println("Size: " + collection.size());
        System.out.println("Contains 20: " + collection.contains(20));
    }
}

'Java' 카테고리의 다른 글

Nested Classes(중첩 클래스)  (0) 2024.04.08
Method Class  (0) 2024.04.08
Lambda Expressions  (0) 2024.04.08
Java Advanced Programming Quiz  (0) 2024.04.08
Scope  (0) 2024.04.08