Iterable, Iterator, Collection

DoDoBest

·

2024. 5. 21. 17:06

사진: Unsplash 의 Arno Senoner

전체적인 구조

Collection, Iterable, Iterator의 전체적인 구조는 다음과 같다. 각 Interface는 어떻게 구성되어 있고 어떤 역할을 하는지 알아보자.

 

 

Iterable

 

Iterator를 반환하는 함수만 있는 인터페이스다. 이 인터페이스를 구현하는 클래스는 iterate 될 수 있는 sequence를 의미한다.

iterate의 사전적 의미 : (계산·컴퓨터 처리 절차를) 반복하다

 

public interface Iterable<out T> {
    public operator fun iterator(): Iterator<T>
}

 

operator

 

iterator 함수 앞에 operator 키워드가 붙어 있다. operator는 1 + 3 과 같이 함수(add)를 symbol(+)로 호출할 수 있도록 해주는 키워드다.

여기서 쓰인 operator의 의미는 약간 다르다. Kotlin 공식 문서 For loops 부분을 보면 다음과 같이 적혀 있다.

As mentioned before, for iterates through anything that provides an iterator. This means that it:

  • has a member or an extension function iterator() that returns Iterator<>, which:
    • has a member or an extension function next()
    • has a member or an extension function hasNext() that returns Boolean.

All of these three functions need to be marked as operator.
https://kotlinlang.org/docs/control-flow.html#for-loops

 

Iterator

 

Collection의 내부 구조를 노출하지 않고 원소에 순차적으로 접근할 수 있게 해주는 객체다.

 

For traversing collection elements, the Kotlin standard library supports the commonly used mechanism of iterators – objects that provide access to the elements sequentially without exposing the underlying structure of the collection. Iterators are useful when you need to process all the elements of a collection one-by-one, for example, print values or make similar updates to them. https://kotlinlang.org/docs/iterators.html

 

 

public interface Iterator<out T> {
    /**
     * Returns the next element in the iteration.
     *
     * @throws NoSuchElementException if the iteration has no next element.
     */
    public operator fun next(): T

    /**
     * Returns `true` if the iteration has more elements.
     */
    public operator fun hasNext(): Boolean
}

 

Iterator는 어떻게 구현할까? 자주 사용하는 List 인터페이스에 대해 알아보자. Kotlin List 인터페이스의 내부 코드를 따라가보면 결국 Java의 ArrayList로 구현하고 있는 것을 볼 수 있다. 따라서 아래에서는 Java 코드를 확인했다.

 

List 인터페이스는 Collection 인터페이스를 구현하고 있고, Collection 인터페이스를 확인해보면 Iterable을 구현하고 있으며, Iterable 인터페이스 명세에 따라 Iterator를 반환하고 있다.

 

 

ArrayList 클래스는 List 인터페이스를 구현하고 있다.

 

 

ArrayList는 Iterator를 아래와 같이 구현하고 반환하고 있다.

 

 

Collection

 

Collection은 원소들의 집합을 의미한다.

 

public interface Collection<out E> : Iterable<E> {
    // Query Operations
    /**
     * Returns the size of the collection.
     */
    public val size: Int

    /**
     * Returns `true` if the collection is empty (contains no elements), `false` otherwise.
     */
    public fun isEmpty(): Boolean

    /**
     * Checks if the specified element is contained in this collection.
     */
    public operator fun contains(element: @UnsafeVariance E): Boolean

    override fun iterator(): Iterator<E>

    // Bulk Operations
    /**
     * Checks if all elements in the specified collection are contained in this collection.
     */
    public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}

 

Iterator가 없으면 어떻게 되는가?

 

Iterator를 사용하면 내부 구조를 노출하지 않고 원소에 접근할 수 있게 해준다는 설명을 봤다. 그렇다면 내부 구조를 노출한다는 것은 어떤 의미일까?

예를 들어 List를 탐색한다고 해보자. Iterator가 없다면 아래와 같이 List의 원소의 갯수를 알아야 하며, List의 각 원소에 직접 접근해야 한다. 이 과정에서 내부 구조에 대한 정보를 알게 됐다.

 

fun main() {
    val nums = listOf(1,2,3)
    for (idx in nums.indices) {
        println(nums.get(idx))
    }
}

 

Iterator를 쓰면 어떻게 될까? 원소의 갯수를 직접 알 필요가 없으며, 각 원소를 간접적으로 얻게 된다.

 

fun main() {
    val nums = listOf(1,2,3)
    val iterator = nums.iterator()
    while (iterator.hasNext()) {
        println(iterator.next())
    }
}

 

위 코드는 너무 번거롭다. for in을 사용하면 컴파일러가 위 코드로 변환해준다.

 

fun main() {
    val nums = listOf(1,2,3)
    for (num in nums) {
        println(num)
    }
}