Skip to content

Java 및 Kotlin의 컬렉션

컬렉션(Collections)은 해결하려는 문제에서 중요하며 공통적으로 조작되는 가변적인 수의 항목(0개일 수도 있음) 그룹입니다. 이 가이드는 Java와 Kotlin의 컬렉션 개념 및 연산을 설명하고 비교합니다. 이 가이드는 Java에서 Kotlin으로 마이그레이션하고 Kotlin다운 방식으로 코드를 작성하는 데 도움이 될 것입니다.

이 가이드의 첫 번째 부분은 Java와 Kotlin에서 동일한 컬렉션에 대한 연산 용어집을 요약하여 담고 있습니다. 이 부분은 Java와 Kotlin에서 동일한 연산Java 표준 라이브러리에 없는 연산으로 나뉩니다. 가변성(Mutability)부터 시작하는 가이드의 두 번째 부분에서는 특정 사례를 통해 몇 가지 차이점을 설명합니다.

컬렉션 입문은 컬렉션 개요를 참조하거나 Kotlin Developer Advocate인 Sebastian Aigner의 비디오를 시청하세요.

아래 모든 예제는 Java 및 Kotlin 표준 라이브러리 API만을 사용합니다.

Java와 Kotlin에서 동일한 연산

Kotlin에는 Java의 대응되는 기능과 정확히 동일하게 보이는 컬렉션 연산이 많이 있습니다.

리스트, 세트, 큐, 데크에 대한 연산

설명공통 연산기타 Kotlin 대안
요소 또는 요소들을 추가합니다.add(), addAll()plusAssign(+=) 연산자를 사용하세요: collection += element, collection += anotherCollection.
컬렉션에 요소 또는 요소들이 포함되어 있는지 확인합니다.contains(), containsAll()in 키워드를 사용하여 연산자 형태로 contains()를 호출하세요: element in collection.
컬렉션이 비어 있는지 확인합니다.isEmpty()isNotEmpty()를 사용하여 컬렉션이 비어 있지 않은지 확인하세요.
특정 조건에서 제거합니다.removeIf()
선택된 요소만 남깁니다.retainAll()
컬렉션에서 모든 요소를 제거합니다.clear()
컬렉션에서 스트림을 가져옵니다.stream()Kotlin은 스트림을 처리하는 자체적인 방식인 시퀀스(sequences)map()filter() 같은 메서드를 제공합니다.
컬렉션에서 반복자(iterator)를 가져옵니다.iterator()

맵에 대한 연산

설명공통 연산기타 Kotlin 대안
요소 또는 요소들을 추가합니다.put(), putAll(), putIfAbsent()Kotlin에서 map[key] = value 할당은 put(key, value)와 동일하게 작동합니다. 또한 plusAssign(+=) 연산자map += Pair(key, value) 또는 map += anotherMap을 사용할 수 있습니다.
요소 또는 요소들을 교체합니다.put(), replace(), replaceAll()put()replace() 대신 인덱싱 연산자 map[key] = value를 사용하세요.
요소를 가져옵니다.get()요소를 가져오려면 인덱싱 연산자 map[index]를 사용하세요.
맵에 요소 또는 요소들이 포함되어 있는지 확인합니다.containsKey(), containsValue()in 키워드를 사용하여 연산자 형태로 contains()를 호출하세요: element in map.
맵이 비어 있는지 확인합니다.isEmpty()isNotEmpty()를 사용하여 맵이 비어 있지 않은지 확인하세요.
요소를 제거합니다.remove(key), remove(key, value)minusAssign(-=) 연산자map -= key를 사용하세요.
맵에서 모든 요소를 제거합니다.clear()
맵에서 스트림을 가져옵니다.엔트리(entries), 키(keys), 값(values)에 대한 stream()

리스트에만 존재하는 연산

설명공통 연산기타 Kotlin 대안
요소의 인덱스를 가져옵니다.indexOf()
요소의 마지막 인덱스를 가져옵니다.lastIndexOf()
요소를 가져옵니다.get()요소를 가져오려면 인덱싱 연산자 list[index]를 사용하세요.
하위 리스트(sublist)를 가져옵니다.subList()
요소 또는 요소들을 교체합니다.set(), replaceAll()set() 대신 인덱싱 연산자 list[index] = value를 사용하세요.

약간 다른 연산들

모든 컬렉션 타입에 대한 연산

설명JavaKotlin
컬렉션의 크기를 가져옵니다.size()count(), size
중첩된 컬렉션 요소에 평면적으로 접근합니다.collectionOfCollections.forEach(flatCollection::addAll) 또는 collectionOfCollections.stream().flatMap().collect()flatten() 또는 flatMap()
모든 요소에 지정된 함수를 적용합니다.stream().map().collect()map()
컬렉션 요소에 제공된 연산을 순차적으로 적용하고 누적된 결과를 반환합니다.stream().reduce()reduce(), fold()
분류기(classifier)에 의해 요소를 그룹화하고 개수를 셉니다.stream().collect(Collectors.groupingBy(classifier, counting()))eachCount()
조건에 따라 필터링합니다.stream().filter().collect()filter()
컬렉션 요소가 조건을 만족하는지 확인합니다.stream().noneMatch(), stream().anyMatch(), stream().allMatch()none(), any(), all()
요소를 정렬합니다.stream().sorted().collect()sorted()
처음 N개 요소를 가져옵니다.stream().limit(N).collect()take(N)
서술어(predicate)를 만족하는 요소를 가져옵니다.stream().takeWhile().collect()takeWhile()
처음 N개 요소를 건너뜁니다.stream().skip(N).collect()drop(N)
서술어를 만족하는 요소를 건너뜁니다.stream().dropWhile().collect()dropWhile()
컬렉션 요소와 그와 연관된 특정 값으로 맵을 생성합니다.stream().collect(toMap(keyMapper, valueMapper))associate()

맵에서 위에 나열된 모든 연산을 수행하려면 먼저 맵의 entrySet을 가져와야 합니다.

리스트에 대한 연산

설명JavaKotlin
리스트를 자연 순서(natural order)로 정렬합니다.sort(null)sort()
리스트를 내림차순으로 정렬합니다.sort(comparator)sortDescending()
리스트에서 요소를 제거합니다.remove(index), remove(element)removeAt(index), remove(element) 또는 collection -= element
리스트의 모든 요소를 특정 값으로 채웁니다.Collections.fill()fill()
리스트에서 중복되지 않는 고유한 요소를 가져옵니다.stream().distinct().toList()distinct()

Java 표준 라이브러리에 없는 연산

zip(), chunked(), windowed() 및 기타 연산에 대해 자세히 알아보려면 Sebastian Aigner의 Kotlin 고급 컬렉션 연산에 관한 영상을 시청하세요.

가변성(Mutability)

Java에는 가변 컬렉션이 있습니다.

java
// Java
// 이 리스트는 가변적입니다!
public List<Customer> getCustomers() { ... }

부분적으로 가변적인 컬렉션도 있습니다.

java
// Java
List<String> numbers = Arrays.asList("one", "two", "three", "four");
numbers.add("five"); // 런타임에 `UnsupportedOperationException` 발생

그리고 불변 컬렉션도 있습니다.

java
// Java
List<String> numbers = new LinkedList<>();
// 이 리스트는 불변입니다!
List<String> immutableCollection = Collections.unmodifiableList(numbers);
immutableCollection.add("five"); // 런타임에 `UnsupportedOperationException` 발생

IntelliJ IDEA에서 마지막 두 코드를 작성하면 IDE는 불변 객체를 수정하려 한다는 경고를 표시합니다. 이 코드는 컴파일되지만 런타임에 UnsupportedOperationException과 함께 실패합니다. 타입만 보고는 컬렉션이 가변적인지 알 수 없습니다.

Java와 달리 Kotlin에서는 필요에 따라 가변 컬렉션 또는 읽기 전용 컬렉션을 명시적으로 선언합니다. 읽기 전용 컬렉션을 수정하려고 하면 코드가 컴파일되지 않습니다.

kotlin
// Kotlin
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five")            // 문제 없음
val immutableNumbers = listOf("one", "two")
//immutableNumbers.add("five") // 컴파일 에러 - Unresolved reference: add

불변성에 대한 자세한 내용은 Kotlin 코딩 컨벤션 페이지를 참조하세요.

공변성(Covariance)

Java에서는 자손 타입의 컬렉션을 조상 타입의 컬렉션을 받는 함수에 전달할 수 없습니다. 예를 들어 RectangleShape를 상속하는 경우, Rectangle 요소의 컬렉션을 Shape 요소의 컬렉션을 받는 함수에 전달할 수 없습니다. 코드를 컴파일 가능하게 하려면 ? extends Shape 타입을 사용하여 함수가 Shape의 모든 상속자를 갖는 컬렉션을 받을 수 있게 해야 합니다.

java
// Java
class Shape {}

class Rectangle extends Shape {}

public void doSthWithShapes(List<? extends Shape> shapes) {
/* List<Shape>만 사용할 경우 아래와 같이 List<Rectangle>을 
인자로 하여 이 함수를 호출할 때 코드가 컴파일되지 않습니다. */
}

public void main() {
    var rectangles = List.of(new Rectangle(), new Rectangle());
    doSthWithShapes(rectangles);
}

Kotlin에서 읽기 전용 컬렉션 타입은 공변성(covariant)을 갖습니다. 이는 Rectangle 클래스가 Shape 클래스를 상속하는 경우, List<Shape> 타입이 필요한 모든 곳에 List<Rectangle> 타입을 사용할 수 있음을 의미합니다. 즉, 컬렉션 타입은 요소 타입과 동일한 서브타이핑 관계를 갖습니다. 맵은 값 타입에 대해서는 공변적이지만 키 타입에 대해서는 그렇지 않습니다. 가변 컬렉션은 공변적이지 않습니다. 이는 런타임 실패로 이어질 수 있기 때문입니다.

kotlin
// Kotlin
open class Shape(val name: String)

class Rectangle(private val rectangleName: String) : Shape(rectangleName)

fun doSthWithShapes(shapes: List<Shape>) {
    println("The shapes are: ${shapes.joinToString { it.name }}")
}

fun main() {
    val rectangles = listOf(Rectangle("rhombus"), Rectangle("parallelepiped"))
    doSthWithShapes(rectangles)
}

컬렉션 타입에 대한 자세한 내용은 여기를 참조하세요.

범위(Ranges) 및 프로그레션(Progressions)

Kotlin에서는 범위(ranges)를 사용하여 구간을 만들 수 있습니다. 예를 들어 Version(1, 11)..Version(1, 30)1.11부터 1.30까지의 모든 버전을 포함합니다. in 연산자를 사용하여 해당 버전이 범위 내에 있는지 확인할 수 있습니다: Version(0, 9) in versionRange.

Java에서는 Version이 양쪽 경계에 맞는지 수동으로 확인해야 합니다.

java
// Java
class Version implements Comparable<Version> {

    int major;
    int minor;

    Version(int major, int minor) {
        this.major = major;
        this.minor = minor;
    }

    @Override
    public int compareTo(Version o) {
        if (this.major != o.major) {
            return this.major - o.major;
        }
        return this.minor - o.minor;
    }
}

public void compareVersions() {
    var minVersion = new Version(1, 11);
    var maxVersion = new Version(1, 31);

   System.out.println(
           versionIsInRange(new Version(0, 9), minVersion, maxVersion));
   System.out.println(
           versionIsInRange(new Version(1, 20), minVersion, maxVersion));
}

public Boolean versionIsInRange(Version versionToCheck, Version minVersion, 
                                Version maxVersion) {
    return versionToCheck.compareTo(minVersion) >= 0 
            && versionToCheck.compareTo(maxVersion) <= 0;
}

Kotlin에서는 범위를 하나의 전체 객체로 다룹니다. 두 개의 변수를 만들고 Version과 비교할 필요가 없습니다.

kotlin
// Kotlin
class Version(val major: Int, val minor: Int): Comparable<Version> {
    override fun compareTo(other: Version): Int {
        if (this.major != other.major) {
            return this.major - other.major
        }
        return this.minor - other.minor
    }
}

fun main() {
    val versionRange = Version(1, 11)..Version(1, 30)

    println(Version(0, 9) in versionRange)
    println(Version(1, 20) in versionRange)
}

버전이 최소 버전보다 크거나 같고(>=) 최대 버전보다 작은지(<) 확인하는 경우와 같이 경계 중 하나를 제외해야 할 때는 이러한 포함 범위(inclusive ranges)가 도움이 되지 않습니다.

여러 기준으로 비교

Java에서 여러 기준으로 객체를 비교하려면 Comparator 인터페이스의 comparing()thenComparingX() 함수를 사용할 수 있습니다. 예를 들어 사람들을 이름과 나이로 비교하는 경우입니다.

java
class Person implements Comparable<Person> {
    String name;
    int age;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return this.name + " " + age;
    }
}

public void comparePersons() {
    var persons = List.of(new Person("Jack", 35), new Person("David", 30), 
            new Person("Jack", 25));
    System.out.println(persons.stream().sorted(Comparator
            .comparing(Person::getName)
            .thenComparingInt(Person::getAge)).collect(toList()));
}

Kotlin에서는 비교하고자 하는 필드를 나열하기만 하면 됩니다.

kotlin
data class Person(
    val name: String,
    val age: Int
)

fun main() {
    val persons = listOf(Person("Jack", 35), Person("David", 30), 
        Person("Jack", 25))
    println(persons.sortedWith(compareBy(Person::name, Person::age)))
}

시퀀스(Sequences)

Java에서는 다음과 같은 방식으로 숫자 시퀀스를 생성할 수 있습니다.

java
// Java
int sum = IntStream.iterate(1, e -> e + 3)
    .limit(10).sum();
System.out.println(sum); // 145 출력

Kotlin에서는 _시퀀스(sequences)_를 사용합니다. 시퀀스의 다단계 처리는 가능한 경우 지연(lazily) 실행됩니다. 실제 계산은 전체 처리 체인의 결과가 요청될 때만 발생합니다.

kotlin
fun main() {
    // Kotlin
    val sum = generateSequence(1) {
        it + 3
    }.take(10).sum()
    println(sum) // 145 출력
}

시퀀스는 일부 필터링 연산을 수행하는 데 필요한 단계 수를 줄일 수 있습니다. IterableSequence의 차이점을 보여주는 시퀀스 처리 예제를 참조하세요.

리스트에서 요소 제거

Java에서 remove() 함수는 제거할 요소의 인덱스를 인자로 받습니다.

정수 요소를 제거할 때는 Integer.valueOf() 함수를 remove() 함수의 인자로 사용하세요.

java
// Java
public void remove() {
    var numbers = new ArrayList<>();
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
    numbers.add(1);
    numbers.remove(1); // 인덱스에 의한 제거
    System.out.println(numbers); // [1, 3, 1]
    numbers.remove(Integer.valueOf(1));
    System.out.println(numbers); // [3, 1]
}

Kotlin에는 두 가지 유형의 요소 제거가 있습니다. removeAt()을 사용한 인덱스에 의한 제거와 remove()를 사용한 값에 의한 제거입니다.

kotlin
fun main() {
    // Kotlin
    val numbers = mutableListOf(1, 2, 3, 1)
    numbers.removeAt(0)
    println(numbers) // [2, 3, 1]
    numbers.remove(1)
    println(numbers) // [2, 3]
}

맵 탐색

Java에서는 forEach를 통해 맵을 탐색할 수 있습니다.

java
// Java
numbers.forEach((k,v) -> System.out.println("Key = " + k + ", Value = " + v));

Kotlin에서는 Java의 forEach와 유사하게 for 루프 또는 forEach를 사용하여 맵을 탐색합니다.

kotlin
// Kotlin
for ((k, v) in numbers) {
    println("Key = $k, Value = $v")
}
// 또는
numbers.forEach { (k, v) -> println("Key = $k, Value = $v") }

비어 있을 수 있는 컬렉션에서 첫 번째와 마지막 항목 가져오기

Java에서는 컬렉션의 크기를 확인하고 인덱스를 사용하여 첫 번째와 마지막 항목을 안전하게 가져올 수 있습니다.

java
// Java
var list = new ArrayList<>();
//...
if (list.size() > 0) {
    System.out.println(list.get(0));
    System.out.println(list.get(list.size() - 1));
}

Deque 및 그 상속자들에 대해서는 getFirst()getLast() 함수를 사용할 수도 있습니다.

java
// Java
var deque = new ArrayDeque<>();
//...
if (deque.size() > 0) {
    System.out.println(deque.getFirst());
    System.out.println(deque.getLast());
}

Kotlin에는 특별한 함수인 firstOrNull()lastOrNull()이 있습니다. Elvis 연산자를 사용하면 함수의 결과에 따라 즉시 추가 작업을 수행할 수 있습니다. 예를 들어 firstOrNull()입니다.

kotlin
// Kotlin
val emails = listOf<String>() // 비어 있을 수 있음
val theOldestEmail = emails.firstOrNull() ?: ""
val theFreshestEmail = emails.lastOrNull() ?: ""

리스트로부터 세트 생성

Java에서 List로부터 Set을 생성하려면 Set.copyOf 함수를 사용할 수 있습니다.

java
// Java
public void listToSet() {
    var sourceList = List.of(1, 2, 3, 1);
    var copySet = Set.copyOf(sourceList);
    System.out.println(copySet);
}

Kotlin에서는 toSet() 함수를 사용하세요.

kotlin
fun main() {
    // Kotlin
    val sourceList = listOf(1, 2, 3, 1)
    val copySet = sourceList.toSet()
    println(copySet)
}

요소 그룹화

Java에서는 CollectorsgroupingBy() 함수로 요소를 그룹화할 수 있습니다.

java
// Java
public void analyzeLogs() {
    var requests = List.of(
        new Request("https://kotlinlang.org/docs/home.html", 200),
        new Request("https://kotlinlang.org/docs/home.html", 400),
        new Request("https://kotlinlang.org/docs/comparison-to-java.html", 200)
    );
    var urlsAndRequests = requests.stream().collect(
            Collectors.groupingBy(Request::getUrl));
    System.out.println(urlsAndRequests);
}

Kotlin에서는 groupBy() 함수를 사용하세요.

kotlin
data class Request(
    val url: String,
    val responseCode: Int
)

fun main() {
    // Kotlin
    val requests = listOf(
        Request("https://kotlinlang.org/docs/home.html", 200),
        Request("https://kotlinlang.org/docs/home.html", 400),
        Request("https://kotlinlang.org/docs/comparison-to-java.html", 200)
    )
    println(requests.groupBy(Request::url))
}

요소 필터링

Java에서 컬렉션의 요소를 필터링하려면 Stream API를 사용해야 합니다. Stream API에는 intermediate(중간) 및 terminal(최종) 연산이 있습니다. filter()는 스트림을 반환하는 중간 연산입니다. 출력으로 컬렉션을 받으려면 collect()와 같은 최종 연산을 사용해야 합니다. 예를 들어 키가 1로 끝나고 값이 10보다 큰 쌍만 남기는 경우입니다.

java
// Java
public void filterEndsWith() {
    var numbers = Map.of("key1", 1, "key2", 2, "key3", 3, "key11", 11);
    var filteredNumbers = numbers.entrySet().stream()
        .filter(entry -> entry.getKey().endsWith("1") && entry.getValue() > 10)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    System.out.println(filteredNumbers);
}

Kotlin에서 필터링은 컬렉션에 기본적으로 포함되어 있으며, filter()는 필터링된 것과 동일한 컬렉션 타입을 반환합니다. 따라서 filter()와 그 서술어(predicate)만 작성하면 됩니다.

kotlin
fun main() {
    // Kotlin
    val numbers = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredNumbers = numbers.filter { (key, value) -> key.endsWith("1") && value > 10 }
    println(filteredNumbers)
}

맵 필터링에 대한 자세한 내용은 여기를 참조하세요.

타입별 요소 필터링

Java에서 타입별로 요소를 필터링하고 작업을 수행하려면 instanceof 연산자로 타입을 확인한 다음 타입 캐스팅을 수행해야 합니다.

java
// Java
public void objectIsInstance() {
    var numbers = new ArrayList<>();
    numbers.add(null);
    numbers.add(1);
    numbers.add("two");
    numbers.add(3.0);
    numbers.add("four");
    System.out.println("All String elements in upper case:");
    numbers.stream().filter(it -> it instanceof String)
        .forEach( it -> System.out.println(((String) it).toUpperCase()));
}

Kotlin에서는 컬렉션에서 filterIsInstance<NEEDED_TYPE>()를 호출하기만 하면 되며, 타입 캐스팅은 스마트 캐스트(Smart casts)에 의해 수행됩니다.

kotlin
// Kotlin
fun main() {
    // Kotlin
    val numbers = listOf(null, 1, "two", 3.0, "four")
    println("All String elements in upper case:")
    numbers.filterIsInstance<String>().forEach {
        println(it.uppercase())
    }
}

서술어 테스트(Test predicates)

일부 작업에서는 모든 요소, 어떤 요소도 없음, 또는 일부 요소가 조건을 만족하는지 확인해야 합니다. Java에서는 Stream API 함수인 allMatch(), noneMatch(), anyMatch()를 통해 이러한 모든 확인을 수행할 수 있습니다.

java
// Java
public void testPredicates() {
    var numbers = List.of("one", "two", "three", "four");
    System.out.println(numbers.stream().noneMatch(it -> it.endsWith("e"))); // false
    System.out.println(numbers.stream().anyMatch(it -> it.endsWith("e"))); // true
    System.out.println(numbers.stream().allMatch(it -> it.endsWith("e"))); // false
}

Kotlin에서는 모든 Iterable 객체에서 확장 함수(extension functions)none(), any(), all()을 사용할 수 있습니다.

kotlin
fun main() {
// Kotlin
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.none { it.endsWith("e") })
    println(numbers.any { it.endsWith("e") })
    println(numbers.all { it.endsWith("e") })
}

서술어 테스트에 대해 자세히 알아보세요.

컬렉션 변환 연산

요소 Zip 처리

Java에서는 두 컬렉션을 동시에 반복하여 동일한 위치의 요소들로 쌍을 만들 수 있습니다.

java
// Java
public void zip() {
    var colors = List.of("red", "brown");
    var animals = List.of("fox", "bear", "wolf");

    for (int i = 0; i < Math.min(colors.size(), animals.size()); i++) {
        String animal = animals.get(i);
        System.out.println("The " + animal.substring(0, 1).toUpperCase()
               + animal.substring(1) + " is " + colors.get(i));
   }
}

단순히 요소 쌍을 출력하는 것보다 복잡한 작업을 원한다면 레코드(Records)를 사용할 수 있습니다. 위의 예시에서 레코드는 record AnimalDescription(String animal, String color) {}가 됩니다.

Kotlin에서는 zip() 함수를 사용하여 동일한 작업을 수행할 수 있습니다.

kotlin
fun main() {
    // Kotlin
    val colors = listOf("red", "brown")
    val animals = listOf("fox", "bear", "wolf")

    println(colors.zip(animals) { color, animal -> 
        "The ${animal.replaceFirstChar { it.uppercase() }} is $color" })
}

zip()Pair 객체의 리스트(List)를 반환합니다.

컬렉션의 크기가 서로 다른 경우 zip()의 결과는 더 작은 크기를 따릅니다. 더 큰 컬렉션의 마지막 요소들은 결과에 포함되지 않습니다.

요소 연관(Associate) 처리

Java에서는 Stream API를 사용하여 요소를 특성과 연관시킬 수 있습니다.

java
// Java
public void associate() {
    var numbers = List.of("one", "two", "three", "four");
    var wordAndLength = numbers.stream()
        .collect(toMap(number -> number, String::length));
    System.out.println(wordAndLength);
}

Kotlin에서는 associate() 함수를 사용하세요.

kotlin
fun main() {
    // Kotlin
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.associateWith { it.length })
}

다음 단계는?

  • Kotlin Koans를 방문하세요 – Kotlin 구문을 배우기 위한 연습 문제를 완료하세요. 각 연습 문제는 실패하는 단위 테스트로 만들어져 있으며, 여러분의 역할은 이를 통과하게 만드는 것입니다.
  • 다른 Kotlin 관용구(idioms)를 살펴보세요.
  • Java to Kotlin 변환기를 사용하여 기존 Java 코드를 Kotlin으로 변환하는 방법을 알아보세요.
  • Kotlin의 컬렉션에 대해 알아보세요.

즐겨 사용하는 관용구가 있다면 풀 리퀘스트를 보내 공유해 주세요.