Study/다재다능 코틀린 프로그래밍

Day 5. 컬렉션 사용하기

주지민 2021. 10. 31. 19:03
반응형

본 포스트는 지인들과 스터디한 내용을 정리한 포스트입니다

 

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788931463422

 

다재다능 코틀린 프로그래밍 - 교보문고

다양한 프로그래밍 패러다임으로 살펴보는 코틀린 기본서 | 코틀린은 멀티패러다임 프로그래밍 언어다. 코틀린은 스크립트로 사용할 수도 있고, 객체지향 코드나 함수형 코드, 비동기 프로그램

www.kyobobook.co.kr


1. 컬렉션 사용하기

  • Java에서 사용 가능한 컬렉션은 코틀린에서 바로 사용 가능하다
  • 그 외에 추가적으로 컬렉션에 대한 몇 가지 뷰 인터페이스를 제공한다

 

2. 컬렉션 특징

  • Java의 뮤터블 컬렉션 인터페이스는 코틀린에서 이뮤터블 읽기전용 인터페이스와 뮤터블 읽기-쓰기 인터페이스로 분리됐다
  • 지원되는 대표 컬렉션 종류
    • Pair: 값이 두 개인 튜플
    • Triple: 값이 세 개인 튜플
    • Array: 객체나 Primitive 타입으로 구성되어 순번이 있고, 크기가 고정된 컬렉션
    • List: 객체들이 정렬된 컬렉션
    • Set: 객체들이 정렬되지 않은 컬렉션
    • Map: 연관 사전 혹은 키와 값의 맵
  • 코틀린 스탠다드 라이브러리에 포함된 kotlin.collections 패키지에 Java 컬렉션에 유용한 함수들이 많이 추가되었다
  • 예를 들어 기존 Java의 for-each는 index를 활용할 수 없는 단점이 있었지만 코틀린의 withIndex() 메서드를 통해 인덱스와 값 모두를 사용할 수 있다
    // extension.kts
    // kotlin style
    val names = listOf("Tom", "Jerry")
    println(names.javaClass)
    for (index, value) in names.withIndex()) {
        println("$index $value")
    }​

    • listOf() 메서드를 이용해 ArrayList 객체를 만들고, withIndex() 메서드를 통해 인덱스와 값을 가져왔다
    • withIndex() 메서드는 IndexedValue라는 특별한 반복자를 리턴한다 ( data class )
    • 이뮤터블 컬렉션은 동시성을 사용하는 함수형 프로그래밍 또는 비동기 처리를 하는 어플리케이션을 사용할 때 훨씬 안정적이다
    • 리스트, 셋, 맵은 각기 뷰를 두 가지 씩 가지고 있다
      • 읽기전용 뷰로 통하는 뮤터블 뷰
      • 읽기-쓰기 뷰로 불리기도 하는 뮤터블 뷰
    • 해당 뷰들을 이용하면 런타임시 오버헤드가 없고, 컴파일 시간이나 실행 시간에 변환이 발생하지 않는다
    • 읽기 전용 뷰에 쓰기 시도를 할 경우 컴파일 단계에서 실패
    • 예를 들어 List읽기전용 뷰인 List, 쓰기전용 뷰인 MutableList가 있다
    • 읽기전용 뷰는 쓰레드 안정성을 제공하지 않는다

 

3. 페어와 트리플 사용하기

  • 튜플은 작고 셀 수 있는 크기의 객체 배열이다
  • 코틀린은 두 가지 사이즈의 튜플만 허용한다 ( 2개: Pair, 3개: Triple )
  • 2개 혹은 3개의 객체를 컬렉션으로 만들고 싶을때 유용하다
  • 페어 생성 예제
    // createPair.kts
    val myPair = Pair("Tom", "Jerry")
    println(myPair) // (Tom, Jerry)
    println(myPair.javaClass) // class kotlin.Pair
    
    val myPairOfMap = mapOf("Tom" to "Cat", "Jerry" to "Mouse")
    println(myPairOfMap) // {Tom=Cat, Jerry=Moude}
    println(myPairOfMap.javaClass) // class java.util.LinkedHashMap​

    • Pair의 생성자를 통해 인스턴스를 생성했다
    • to() 확장 함수를 통해 Map의 엔트리가 될 페어를 만들었다
    • to() 확장 함수는 코틀린의 모든 객체에서 사용이 가능하다
  • 코틀린의 페어는 각기 다른 타입의 객체를 컬렉션으로 만들 때 타입 안정성을 제공해주고 코드의 혼란을 줄여준다 
    • Java에서는 String, Double 타입 속성을 갖게하려면 Object 부모클래스로 배열을 만들거나 두 타입을 갖는 새로운 클래스를 만들어야하는 불편함이 있었다
    • 예제코드
      // airporttemperatures.kts
      val airportCodes = listOf("LAX", "SFO", "PDX", "SEA")
      val temperatures = airportCodes.map { code -> code to mappingValue(code) }
      println(temperatures)
      
      fun mappingValue(code: String): String {
          return "$code hi"
      }​
  • 페어와 트리플 모두 이뮤터블이다
  • 만약 3개보다 더 많은 이뮤터블 값들을 그룹핑하고 싶다면 데이터 클래스를 만드는 것을 고려해 보도록 해라

 

4. 객체 배열과 Primitive 배열

  • Array<T> 클래스는 코틀린의 배열을 상징한다
  • 배열은 낮은 수준의 최적화가 필요할 때만 사용하도록하고, 그 외에는 List같은 다른 자료구조를 사용하다
  • 배열을 만드는 가장 쉬운 방법은 arrayOf()라는 최상위 함수를 사용하는 것 
  • 배열을 만들면 인덱스 연산자 []를 이용해서 요소에 접근할 수 있다
  • 예제 코드
    // arrays.kts
    val friends = arrayOf("Tintin", "Snowy", "Haddock", "Calculus")
    println(friends::class) // class kotlin.Array
    println(friends.javaClass) // class [Ljava.lang.String;
    println("${friends[0]} and ${friends[1]}") // Tintin and Snowy​
  • 배열의 타입은 kotlin.Array이지만 실제 JVM에서 실행될 떄 적용되는 진짜 타입은 Java.String이다
  • 실제 arrayOf() 메서드를 통해 Array<T>를 생성하면 Primitive Type이라도 Boxing된 클래스 타입으로 생성된다 ( Integer, Double, Long ... ), 이는 불필요한 오버헤드를 발생시키므로 intArrayOf() 메서드 함수등 특수한 함수를 이용하자 ( int 배열 )
  • 배열을 만들 때 값을 계산해서 생성할 수 있다
    // calculateArrays.kts
    println(Array(5) { i -> (i + 1) * (i + 1) }.sum())

 

5. 리스트 사용하기

  • 리스트를 만드는 첫 단계에서 코틀린은 개발자에게 이뮤터블 또는 뮤터블인지를 선택하도록 한다
  • 이뮤터블 리스트를 만들려면 listOf() 메서드를 사용하면 된다
  • 정말 꼭 뮤터블 리스트를 만들어야한다면 mutableListOf() 메서드를 이용해 생성하면 된다 ( 뮤터블은.. 권장 X )
  • listOf() 함수는 kotlin.collections.List<T> 인터페이스 참조를 리턴한다
  • 예제 코드
    // lists.kts
    val fruites: List<String> = listOf("Apple", "Banana", "Grape")
    println(fruites) // [Apple, Banana, Grape]​
  • 값을 가져오려면 인덱스 연산자 [] 혹은 get() 메서드 둘 다 가능하다 ( 인덱스 연산자 [] 또한 내부적으로 get() 메서드를 호출한다 )
  • 인덱스 연산자를 사용하는 편이 get() 보다 노이즈가 적고 편리하다
  • 리스트가 특정 값을 가지고 있는지를 확인하려면 contain() 메서드 혹은 in을 사용하면 된다
    // lists.kts
    println(fruites.contains("Apple"))
    println("Apple" in fruites)​
  • listOf()가 리턴하는 참조를 사용할 때 리스트를 변경할 수는 없다
  • kotlin.collections.List<T>의 인터페이스는 컴파일 시간에 Java에서 많이 사용했을 Arrays.asList()로 만든 JDK 객체의 뷰로 동작하지만 이 인터페이스는 변화를 허용하지 않는다 ( 이뮤터블이다 )
  • 기존의 이뮤터블 리스트를 변화하지 않으면서 요소를 추가하여 새로운 리스트를 반환하는데 편리한 '+'를 쓸 수 있다
    // lists.kts
    val fruits2 = fruits + "Orange"
    println(fruits)
    println(fruits2)​

    • 합리적인 추론으로 '-"도 있지 않을까 생각되면 당연히 있다 ( '-': 요소를 제거하여 새로운 리스트 반환 )

 

6. 셋 사용하기

  • 셋은 정렬되지 않은 요소의 모음이다
  • 이뮤터블/읽기전용 버전과 뮤터블/읽기-쓰기 버전 모두가 있다
  • Set<T> 인스턴스를 만들기 위해서는 setOf() 메서드를 사용, MutableSet<T>를 만들기 위해서는 mutableSetOf()를 사용하면 된다
    // sets.kts
    val fruits: Set<String> = setOf("Apple", "Banana", "Apple")
    println(fruits) // [Apple, Banana]
    println(fruits::class) // class java.util.LinkedHashSet   
    println(fruits.javaClass) // class java.util.LinkedHashSet​
  • set<T>와 MutableSet<T>에는 위에서 소개된 List처럼 +, -, contains, in 등 많은 함수가 포함되어있다

 

7. 맵 사용하기

  • 맵은 키-값 페어를 보관하는 콜렉션이다
  • 기타 컬렉션들과 마찬가지로 읽기전용과 읽기-쓰기용 인터페이스를 제공한다
  • mapOf()를 사용해서 Map<K,V>의 읽기전용 맵을 만들 수 있다
  • mutableMapOf()를 사용하면 MutableMap<K,V>의 읽기-쓰기 맵을 만들 수 있다
  • JDK의 참조
    • HashMap -> hashMapOf()
    • LinkedHashMap -> linkedMapOf()
    • SortedMap -> sortedMapOf()
  • 예제코드
    // usingmap.kts
    val sites = mapOf(
        "pragprog" to "https://www.pragprog.com",
        "agiledeveloper" to "https://agiledeveloper.com"
    )
    
    println(sites) // {pragprog=https://www.pragprog.com, agiledeveloper=https://agiledeveloper.com}
    println(sites.size) // 2
    println(sites::class) // class java.util.LinkedHashMap
    println(sites.javaClass) // class java.util.LinkedHashMap​
  • 키-값 페어가 to() 확장함수를 통해서 만들어진다
  • to() 확장함수는 코틀린의 모든 객체에서 사용 가능하고, mapOf()는 Pair<K,V>를 인자로 취급한다
  • keys 속성은 맵에 존재하는 모든 키를, values는 맵에 존재하는 모든 값을 반복할 수 있다
  • containsKey(), containsValue() 는 각각 키와 값이 존재하는지 체크할 수 있고, 키 존재 여부는 contains(), in으로도 가능하다
  • 키로 맵에서 값을 꺼내올 때는 get() 메서드 혹은 인덱스 연산자 []를 이용할 수 있는데 키가 맵에 없을 경우 nullable 타입을 리턴한다
    // usingmap.kts
    val pragProgSite: String? = sites.get("pragprog")
    val pragProgSite2: String? = sites["pragprog"]
    println(pragProgSite)
    println(pragProgSite2)​
  • 이런 nullable 타입을 피하기 위해 키가 없으면 정해진 기본값을 리턴하게 할 수 있다 ( getOrDefault )
    // usingmap.kts
    val agiledeveloper = sites.getOrDefault("agiledeveloper", "http://www.example.com")
    println(agiledeveloper)
    println(agiledeveloper::class) // class kotlin.String
    println(agiledeveloper.javaClass) // class java.lang.String​
  • 위에서 소개한 리스트, 셋과 마찬가지로 '+', '-'를 이용해서 요소를 추가한, 제거한 새로운 맵을 얻을 수 있다
728x90
반응형