[Kotlin] 배열과 컬렉션

[Kotlin] 배열과 컬렉션

데이터 타입 배열과 컬렉션에 대해 알아보자!


  • Int와 Double 같은 타입은 모두 하나의 변수에 하나의 값만 저장하도록 설계되어 있는데 프로그래밍을 하다 보면 하나의 변수에 여러 개의 값을 저장해야 할 필요성이 있다.
  • Kotlin 뿐만 아니라 대부분의 컴퓨터 언어들은 이처럼 여러 개의 값을 하나의 변수에 저장할 수 있도록 배열(Array)과 컬렉션(Collection)이라는 데이터 타입을 제공한다.

1) 배열

  • 배열이란? 여러 개의 값을 담을 수 있는 대표적인 자료형이다.
  • 먼저 배열 공간의 개수를 할당하거나 초기화 시에 데이터를 저장해두면 데이터의 개수만큼 배열의 크기가 결정된다.
  • 개수를 정해 놓고 사용해야 하며, 중간에 개수를 추가하거나 제거할 수 없다.
  • 배열은 다른 데이터 타입과 마찬가지로 변수에 저장해서 사용할 수 있으며 다음과 같은 형태로 선언한다.
var 변수 = Array(개수)
  • 배열 객체는 Int, Long, Char 등과 같은 타입 뒤에 Array를 붙여서 만든다.
var students = IntArray(10)
var longArray = LongArray(10)
var CharArray = CharArray(10)
var FloatArray = FloatArray(10)
var DoubleArray = DoubleArray(10)
  • 위 코드 중 첫 번째 줄은 변수 students에 Int(정수형) 공간을 10개 할당하라는 의미이다.

    • students라는 이름으로 정수형 데이터를 담을 수 있는 10개의 공간을 가진 배열이 만들어지고, 각 공간에는 아직 무슨 값이 들어 있는지 모른다.

    • 우리는 보통 첫 번째라고 하면 1이라고 생각하지만 컴퓨터는 0을 첫 번째로 인식하도록 설계되어 있다.

    • 그래서 10개의 공간을 가지는 위와 같은 배열의 인덱스(Index)는 0부터 시작해서 9에서 끝난다.

      • 인덱스(Index): 배열은 여러 공간을 가지고 있으며 각 몇 번째의 공간을 가리킬 때 사용한다.

        • 예를 들어 배열의 3번째 공간을 가리키기 위해서는 인덱스(Index) 2를 사용한다.

        • 배열뿐만 아니라 컴퓨터 언어에서 위치를 나타내는 인덱스(Index)는 0부터 시작한다.

        • 다양한 견해가 있지만 컴퓨터의 시작이 0과 1로 만들어진 하드웨어이고 수의 표현을 2진수(Binary)로 하기 때문에 셈의 시작을 1부터 했을 때와 0부터 했을 때, 그 값이 커질수록 표현 범위에 큰 차이가 난다는 것에 기인했다는 견해가 가장 설득력이 있다.

문자 배열에 빈 공간 할당하기

  • String은 Int, Double 등과 같은 기본 타입이 아니기 때문에 StringArray는 없지만 다음과 같이 사용할 수 있다.
var stringArray = Array(10, { item -> "" } )
  • 수식이 조금 특이해 보일 수 있지만 일단은 괄호 안의 첫 번째 숫자인 10만 변경해서 사용하면 그 숫자만큼 빈 문자열로 된 배열 공간을 할당한다.

값으로 배열 공간 할당하기

  • arrayOf 함수를 사용해서 String 값을 직접 할당할 수도 있다.
var dayArray = arrayOf("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")

배열에 값 입력하기

  • 배열을 선언한 변수명 옆에 대괄호 [ ] 를 사용하고, 대괄호 안에 값을 저장할 위치의 인덱스 번호를 작성합니다. 그리고 등호(=)를 사용해서 값을 입력할 수 있다.
배열명[인덱스] = 
  • set 함수를 사용할 수 있다. 배열이 제공하는 set 함수에 인덱스와 값을 파라미터로 넘겨주면 된다.
배열명.set(인덱스, )
  • 다음은 첫 번째부터 열 번째까지의 인덱스 값을 바꾸는 예제 코드이다.
students[0] = 90
students.set(1, 91)
...
sutdents.set(8, 98)
students[9] = 99

여기서 잠깐!

  • 배열의 범위를 벗어난 인덱스에 값을 넣을 경우 어떻게 될까?

    • 10개의 공간이 할당된 배열에서 열한 번째에 해당하는 10번 인덱스에 값을 넣으려고 하면 범위를 넘어섰다는 Exception이 발생하고 프로그램이 종료된다.
var intArray = IntArray(10)
intArray[10] = 100 // Exception 발생. intArray의 마지막 인덱스는 9입니다.

--> ArrayIndexOutOfBoundsException 발생

배열에 있는 값 꺼내기

  • 저장할 때와 마찬가지로 대괄호 안에 인덱스를 입력해서 값을 가져올 수 있으며 get()을 이용할 수도 있다.
배열명[인덱스]
배열명.get(인덱스)
  • 배열 intArray의 일곱 번째 값을 seventhValue 변수에 저장한다.
var seventhValue = intArray[6]
  • 배열 intArray의 열 번째 값을 get 함수를 사용해서 tenthValue 변수에 저장한다.
var tenthValue = intArray.get(9)
  • 다음은 Array 프로젝트의 전체 코드이다.
package net.flow9.thisiskotlin.basicsyntax

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 1. 기본타입 배열 선언하기 - 각 기본타입별로 10개의 빈 공간이 할당된다
        var students = IntArray(10)
        var longArray = LongArray(10)
        var CharArray = CharArray(10)
        var FloatArray = FloatArray(10)
        var DoubleArray = DoubleArray(10)
        // arrayOf를 사용하면 선언과 동시에 값을 입력할 수 있다
        var intArray = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        // intArray 변수에는 1 부터 10까지의 값이 각각의 배열공간에 저장되어 있다

        // 2. 문자열타입 배열 선언하기
        var stringArray = Array(10, { item -> "" })
        // arrayOf 함수로 값을 직접 입력해서 배열을 생성할 수 있다
        var dayArray = arrayOf("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")

        // 3. 앞에서 선언한한 studens 변수에 값 넣기
        // 가. 대괄호를 사용하는 방법
        students[0] = 90
        students[1] = 91
        students[2] = 92
        students[3] = 93
        students[4] = 94
        // 나. set 함수를 사용하는 방법
        students.set(5, 95)
        students.set(6, 96)
        students.set(7, 97)
        students.set(8, 98)
        students.set(9, 99)

        // 4. 값 변경해 보기
        intArray[6] = 137    // 6번 인덱스인 7번째 값 7이 137로 변경된다
        intArray.set(9, 200) // 9번 인덱스인 10번째 값 8이 200으로 변경된다

        // 5. 배열 값 사용하기
        var seventhValue = intArray[6]
        Log.d("Array", "8번째 intArray의 값은 ${seventhValue}입니다")
        var tenthValue = intArray.get(9)
        Log.d("Array", "10번째 intArray의 값은 ${tenthValue}입니다")

        Log.d("Array", "1번째 dayArray 값은 ${dayArray[0]}입니다")
        Log.d("Array", "6번째 dayArray 값은 ${dayArray.get(5)}입니다")
    }
}

/** [로그캣 출력 내용]
8번째 intArray의 값은 137입니다
10번째 intArray의 값은 200입니다
1번째 dayArray 값은 MON입니다
6번째 dayArray 값은 SAT입니다
*/

2) 컬렉션

  • 컬렉션(Collection)은 다른 이름으로는 동적 배열이라고도 한다.

  • 배열과는 다르게 공간의 크기를 처음 크기로 고정하지 않고 임의의 개수를 담을 수 있기 때문이다.

  • 컬렉션은 크게 세 가지로 리스트(List), 맵(Map), 셋(Set)이 있으며 각각은 다음과 같은 용도로 사용할 수 있다.

3) 리스트(List)

  • 리스트는 저장되는 데이터에 인덱스를 부여한 컬렉션이며 중복된 값을 입력할 수 있다.

  • Kotlin에서 동적으로 리스트를 사용하기 위해서는 리스트 자료형 앞에 뮤터블(Mutable)이라는 접두어(Prefix를 붙여야 한다.

  • 예를 들면 mutableList, mutableMap, mutableSet이 있다.

  • 배열과 같이 ‘데이터 타입Of’ 형태로 사용할 수 있다.

var list = mutableListOf("MON", "TUE", "WED")

여기서 잠깐!

  • 뮤터블이란?

    • 뮤터블(Mutable)이란 변할 수 있다라는 의미를 가지고 있다.

    • Kotlin은 컬렉션 데이터 타입을 설계할 때 모두 이뮤터블(Immutable: 변할 수 없는)로 설계하였다.

    • 기본 컬렉션인 리스트(List), 맵(Map), 셋(Set)은 모두 한 번 입력된 값을 바꿀 수 없다.

    • 그래서 컬렉션의 원래 용도인 동적 배열로 사용하기 위해서는 뮤터블로 만들어진 데이터 타입을 사용해야 한다.

리스트 생성하기: mutableListOf

  • 다음과 같이 작성하면 변수에 “MON”, “TUE”, “WED” 3개의 값을 가진 크기가 3인 동적 배열 리스트가 생성된다.
var mutableList = mutableListOf("MON", "TUE", "WED)

리스트에 값 추가하기: add

  • add 함수를 사용해서 값을 추가할 수 있다.

  • 값이 추가되면서 동적으로 리스트의 공간이 자동으로 증가한다.

mutableList.add("THU")
  • add 함수를 사용하면 입력될 위치인 인덱스를 따로 지정해주지 않아도 입력되는 순서대로 인덱스가 지정된다.

  • “MON”: [0], “TUE”: [1], “WED”: [2] –> “MON”: [0], “TUE”: [1], “WED”: [2], “THU”: [3]

리스트에 입력된 값 사용하기: get

  • get 함수로 리스트에서 값을 꺼낼 수 있다.

  • 입력과는 다르게 사용할 때는 인덱스를 지정해서 몇 번째 값을 꺼낼 것인지 명시해야 한다.

  • 다음은 두 번째 값을 변수에 저장하는 예시이다.

var variable = mutableList.get(1)

리스트값 수정하기: set

  • set 함수를 사용해서 특정 인덱스의 값을 수정할 수 있다.

  • 다음은 두 번째 값을 수정하는 예시이다.

mutalbeList.set(1, "수정할 값")

리스트에 입력된 값 제거하기: removeAt

  • removeAt 함수로 리스트에 입력된 값의 인덱스를 지정해서 삭제할 수 있다.

  • 다음은 두 번째 값을 삭제하는 예시이다.

mutableList.removeAt(1)
  • 두 번째 값을 삭제하면 세 번째 값부터 인덱스가 하나씩 감소하면서 빈자리의 인덱스로 이동한다.

  • “MON”: [0], “TUE”: [1], “WED”: [2] –> “MON”: [0], “WED”: [1]

    • “TUE”가 삭제되면서 3번 째 값인 “WED”의 인덱스는 2에서 1로 바뀌게 된다.

빈 리스트 사용하기

  • 아무것도 없는 빈 리스트를 생성하면 앞으로 입력되는 값의 데이터 타입을 알 수 없기 때문에 값의 타입을 추론할 수 없다.

    • 그러므로 빈 컬렉션의 경우 앞에서처럼 ‘데이터 타입Of’만으로는 생성되지 않고 데이터 타입을 직접적으로 알려주는 방법을 사용해야 한다.
  • 다음과 같이 사용할 수 있다.

var 변수명 = mutableListOf<컬렉션에 입력될 값의 타입>()
var stringList = mutableListOf<String>()
  • 다음 예제와 같이 문자열로 된 빈 리스트를 생성하고 조작할 수 있다.
// 생성
var stringList = mutableListOf<String>()

// 입력
stringList.add("월")
stringList.add("화")

// 사용
Log.d("Collection", "stringList에 입력된 두 번째 값은 ${stringList.get(1)}입니다.")

// 수정
stringList.set(1, "수정된 값")

// 삭제
stringList.removeAt(1) // 두 번째 값이 삭제 됩니다.

여기서 잠깐!

  • 제네릭(Generic)

    • 리스트 컬렉션을 생성하면서 < > 괄호를 사용했는데, 이 괄호의 정식 명칭은 제네릭이다.

    • 제네릭은 컬렉션이 사용하는 값의 타입을 지정하기 위한 도구이다.

    • Kotlin에서 컬렉션은 제네릭을 사용하지 않으면 사용할 수 없다.

    • 단, 값으로 초기화 할 때는 입력되는 값으로 타입을 추론할 수 있기 때문에 제네릭을 쓰지 않아도 생성할 수 있다.

컬렉션 개수 가져오기: size

  • size 프로퍼티를 사용하면 컬렉션의 개수를 가져올 수 있다. (mutableList.size)

  • 위에서 set, get 등은 ‘함수’라고 하고, size는 ‘프로퍼티’라는 용어를 사용했는데, 이 둘을 구분하는 방법은 괄호의 유무이다.

    • 괄호가 있으면 함수

    • 괄호가 없으면 프로퍼티

  • 다음은 컬렉션 List를 사용한 예제이다. 안드로이드 스튜디오에서 해당 코드를 작성해서 로그를 확인해보자!

package net.flow9.thisiskotlin.basicsyntax

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 1. 값으로 컬렉션 생성하기
        var mutableList = mutableListOf("MON", "TUE", "WED")
        // 값 추가하기
        mutableList.add("THU")
        // 값 꺼내기
        Log.d("Collection", "mutableList의 첫 번째 값은 ${mutableList.get(0)}입니다")
        Log.d("Collection", "mutableList의 두 번째 값은 ${mutableList.get(1)}입니다")

        // 2. 빈 컬렉션 생성하기기
        var stringList = mutableListOf<String>() // 문자열로 된 빈 컬렉션 생성
        // 값 추가하기
        stringList.add("월")
        stringList.add("화")
        stringList.add("수")
        // 값 변경하기
        stringList.set(1, "날짜 변경")
        // 사용
        Log.d("Collection", "stringList에 입력된 두 번째 값은 ${stringList.get(1)}입니다")
        // 삭제
        stringList.removeAt(1) // 두 번째 값이 삭제된다.
        Log.d("Collection", "stringList에 입력된 두 번째 값은 ${stringList.get(1)}입니다")
    }
}

/** [로그캣 출력 내용]
mutableList의 첫 번째 값은 MON입니다
mutableList의 두 번째 값은 TUE입니다
stringList에 입력된 두 번째 값은 날짜 변경입니다
stringList에 입력된 두 번째 값은 수입니다
*/

4) 셋(Set)

  • 셋은 중복을 허용하지 않는 리스트라고 할 수 있다.

  • 리스트와 유사한 구조이지만 인덱스로 조회할 수 없고, get 함수도 지원하지 않는다.

  • String 타입의 값을 입력받기 위해 다음과 같이 선언할 수 있다.

var set = mutableListOf<String>()

빈 셋으로 초기화하고 값 입력하기

  • 셋은 중복을 허용하지 않기 때문에 다음 코드를 보시면, 네 번째 줄에서 입력한 “JAN”은 입력되지 않는다.
var set = mutableSetOf<String>()
set.add("JAN")
set.add("FEB")
set.add("MAR")
set.add("JAN") // 동일한 값은 입력되지 않습니다.

셋의 값 인덱싱?

  • 셋은 인덱스로 조회하는 함수가 없기 때문에 특정 위치의 값을 사용할 수 없습니다.

셋의 값 삭제하기

  • 셋은 값이 중복되지 않기 때문에 다음과 같이 값을 직접 입력하여 삭제하여야 합니다.
set.remove("FEB")
  • 다음은 컬렉션 Set을 사용한 예제이다. 안드로이드 스튜디오에서 해당 코드를 작성해서 로그를 확인해보자!
package net.flow9.thisiskotlin.basicsyntax

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 1. 셋 생성하고 값 추가하기
        var set = mutableSetOf<String>()
        set.add("JAN")
        set.add("FEB")
        set.add("MAR")
        set.add("JAN") // 동일한 값은 입력되지 않는다.
        // 2. 전체 데이터 출력해보기
        Log.d("Collection", "Set 전체출력 = ${set}")
        // 3. 특정 값 삭제하기
        set.remove("FEB")
        Log.d("Collection", "Set 전체출력 = ${set}")
    }
}

/** [로그캣 출력 내용]
Set 전체출력 = [JAN, FEB, MAR]
Set 전체출력 = [JAN, MAR]
*/

5) 맵(Map)

  • 맵은 키(Key)와 값(Value)의 쌍으로 입력되는 컬렉션이다.

  • 맵의 키는 리스트의 인덱스와 비슷한데 맵에서는 키를 직접 입력해야 한다.

  • 제네릭으로 키와 값의 데이터 타입을 지정해서 맵을 생성한다.

  • 다음은 키와 값의 타입을 모두 String으로 사용하기 위한 생성 예제이다.

var map = mutableMapOf<String, String>()
  • 인덱스에 해당하는 키를 직접 지정해서 사용해야 한다.

빈 맵으로 생성하고 값 추가하기

  • 값을 추가하기 위해 제공되는 맵에서 제공되는 put 함수에 키와 값을 입력하면 된다.
var map = mutableMapOf<String, String>()
map.put("key1", "value2")
map.put("key2", "value2")
map.put("key3", "value3")
  • 키와 값을 추가할 때마다 리스트처럼 맵의 공간이 늘어난다.

맵 사용하기

  • get 함수에 키를 직접 입력해서 값을 꺼낼 수 있다.
Log.d("CollectionMap", "map에 입력된 key1의 값은 ${map.get("key1")}입니다.")

// [로그캣 출력 내용]
// map에 입력된 key1의 값은 value2입니다.

맵 수정하기

  • put 함수를 사용할 때 동일한 키를 가진 값이 있으면 키는 유지된 채로 그 값만 수정한다.
map.put("key2", "수정")
  • 기존 “key2”의 값인 “value2”는 “수정”으로 바뀌게 된다.

맵 삭제하기

  • remove 함수에 키를 입력해서 값을 삭제할 수 있다.

  • 리스트와는 다르게 인덱스에 해당하는 키의 값이 변경되지 않고 그대로 유지된다.

map.remove("key2")
  • 다음은 컬렉션 Map을 사용한 예제이다. 안드로이드 스튜디오에서 해당 코드를 작성해서 로그를 확인해보자!
package net.flow9.thisiskotlin.basicsyntax

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 1. 맵 생성하기
        var map = mutableMapOf<String, String>()
        // 2. 값 넣기
        map.put("키1", "값2")
        map.put("키2", "값2")
        map.put("키3", "값3")
        // 3. 값 사용하기
        var variable = map.get("키2")
        Log.d("Collection", "키2의 값은 ${variable}입니다")
        // 4. 값 수정하기
        map.put("키2", "두 번째 값 수정")
        Log.d("Collection", "키2의 값은 ${map.get("2")}입니다")
        // 5. 값 삭제하기
        map.remove("키2")
        // 5.1 없는 값을 불러오면 null값이 출력된다
        Log.d("Collection", "키2의 값은 ${map.get("2")}입니다")
    }
}

/** [로그캣 출력 내용]
키2의 값은 값2입니다
키2의 값은 두 번째 값 수정입니다
키2의 값은 null입니다
*/

여기서 잠깐!

  • 컬렉션 값의 단위 = 엘리먼트

    • 컬렉션에 입력되는 값은 각각을 엘리먼트(Element)라고 한다.

    • 값이라고 해도 되지만 맵을 지칭할 때 맵의 값(엘리먼트 자체)을 가리키는 건지 엘리먼트의 값(실제 값)을 가리키는 건지, 2개의 용어가 충돌할 수 있기 때문에 엘리먼트라고 이해하고 있는 것이 좋다.

    • 엘리먼트는 맵의 입력 단위인 키와 값을 합친 것을 말하는데 이것은 리스트와 셋에서도 동일한 용어로 사용된다.

    • 즉 리스트의 값 또한 엘리먼트라고 부른다.

      • 리스트 엘리먼트 = 리스트의 (값)

      • 맵 엘리먼트 = 맵의 (키와 값)

6) 이뮤터블 컬렉션(Immutable Collection)

  • Kotlin은 일반 배열처럼 크기를 변경할 수 없으면서 값 또한 변경할 수 없는 이뮤터블 컬렉션을 지원한다.

  • 이뮤터블 컬렉션은 다음과 같이 기존 컬렉션에서 mutable 이라는 접두어가 제거된 형태로 사용된다.

var list = listOf("1", "2")
  • 불변형 컬렉션은 한 번 입력된 값을 변경할 수 없기 때문에 add나 set 함수는 지원하지 않고 최초 입력된 값만을 ‘사용’할 수 있다.

  • 배열과 다른 점은 크기뿐만 아니라 값의 변경 또한 불가능하다는 것이다.

  • 즉 불변형 컬렉션은 수정, 추가, 제거 모두 안된다.

var immutableList = listOf("JAN", "FEB", "MAR") // 생성
Log.d("Collection", "리스트의 두 번째 값은 ${immtuableList.get(1)}입니다.) // 사용

// [로그캣 출력 내용]
// 리스트의 두 번째 값은 FEB입니다.
  • 그렇다면 이뮤터블 컬렉션은 언제 사용할 수 있을까?

    • 일반 변수 var와 읽기 전용 변수 val의 관계에세 그 사용법을 유추할 수 있는데, 기준이 되는 어떤 값으 모음을 하나의 변수에 저장할 필요가 있거나 또는 여러 개의 값을 중간에 수정하지 않고 사용할 필요가 있을 때 이뮤터블 컬렉션을 사용한다.

    • 대표적인 예로 요일 데이터가 있습니다!

      • 다음처럼 7개의 요일을 이뮤터블 리스트로 선언하면 중간에 바뀌지 않기 때문에 계속 같은 값을 유지하면서 사용할 수 있다.
var dayList = listOf("월", "화", "수", "목", "금", "토", "일)

7)복습

  • 배열(Array)

    • 하나의 변수에 여러 개의 정해진 값을 담을 수 있게 해주는 데이터 타입이다.

    • 처음 정해진 값의 개수는 바꿀 수 없다.

  • 컬렉션(Collection)

    • 여러 개의 값을 담을 수 있는 배열은 값의 개수가 증가하면 사용할 수 없는 단점이 있기 때문에 동적으로 크기를 변경할 수 있도록 만들어진 동적 배열이다.

    • 동적 배열에는 크게 리스트(List), 셋(Set), 맵(Map) 세 가지의 데이터 타입이 있으며 이것들을 모두 통칭해서 부르는 용어가 컬렉션이다.

  • 뮤터블(Mutable)

    • 입력된 값을 변경할 수 있는 것을 말한다.

    • 대표적으로 var로 선언된 변수는 모두 뮤터블이다.

  • 리스트(List)

    • 컬렉션의 한 종류로 인덱스를 사용하는 데이터 타입이다.

    • 인덱스가 있기 때문에 중복된 값을 넣을 수도 있다.

  • 셋(Set)

    • 리스트에서 인덱스가 빠진 데이터 타입이다.

    • 값을 중복해서 넣을 수 없다.

  • 맵(Map)

    • 키(Key)와 값(Value)을 쌍으로 갖는 데이터 타입이다.

    • 맵의 키는 리스트의 인덱스처럼 사용할 수 있다.


© 2023. All rights reserved.

AgileCatch