5. 데이터 타입(깡샘의 코틀린)

Seongho·2022년 1월 21일
0

Kotlin

목록 보기
6/6

5.1 기초 데이터 타입

5.1.1 숫자 타입

  • Double
  • Float
  • Long
  • Int
  • Short
  • Byte

코틀린의 숫자 타입은 위와 같다. 코틀린은 다른 언어와 달리 int, double 등의 기초 타입을 사용하지 않고, 모든 것을 객체로 표현한다. wrapper니 뭐니 생각할 필요 없이 위 숫자 타입을 사용하자.

💡 코틀린은 숫자 타입에 대입되는 데이터에 밑줄을 추가하여 읽기 좋게 표현할 수 있다. 값 자체에는 전혀 영향이 없다.
val oneMillion: Int = 1_000_000

5.1.2 논리, 문자와 문자열 타입

  • || - 논리합
  • && - 논리곱
  • ! - 부정
  • Char - 문자 타입의 데이터는 작은따옴표로 묶어서 표현
  • String - Char의 집합, 인덱스 접근 가능, 큰따옴표로 묶어서 표현
    • "string", """string""" 여러 줄은 후자로 표현 가능. 전자를 여러 줄로 쓰려면 \n 을 사용해야 함.

5.1.3 Any 타입

Any는 코틀린 클래스의 최상위 클래스이다. 타입을 선언할 때 Any를 이용하는 것은 어떤 타입의 데이터도 대입할 수 있다는 것이며, 특정 변수에 대입되는 타입을 예측할 수 없을 때 유용하게 사용할 수 있다.

<fun getLength(obj: Any): Int {
    if (obj is String) {
        return obj.length
    }
    return 0
}

fun main(args: Array<String>) {
    println(getLength("Hello"))
    println(getLength(10))
}

[실행결과]
5
0

아래처럼 when을 사용할 수도 있다.

fun cases(obj: Any): String {
    when(obj) {
        1 -> return "One"
        "Hello" -> return "Greeting"
        is Long -> return "Long"
        !is String -> return "Not a string"
        else -> return "unknown"
    }
}

fun main(args: Array<String>) {
    println(cases(1))
    println(cases(10))
}

[실행결과]
One
Not a string

5.1.4 null 허용 타입

null이 대입될 가능성이 있는 곳에는 명시적으로 nullable을 표현해야 한다.

val a: Int = null   <= null 대입 시 에러 발생
val b: Int? = null  <= null을 대입해도 에러 발생하지 않음
fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

Any 타입도 예외 없이, null을 대입하기 위해서는 ? 기호를 명시해야 한다.

5.1.5 Any, Any? 타입

Any 타입을 Any?에 대입할 수는 있지만 Any? 타입을 Any에 대입할 수는 없다.

당연하다.

5.1.6 Unit과 Nothing

특수 상황을 표현하기 위한 타입

Unit은 함수의 반환 구문이 없다는 것을 표현하기 위해 사용한다. 자바의 void와 비슷하다.

fun myFun1() {}
fun myFun2(): Unit {}

위 두 함수는 Unit을 명시적으로 썼느냐 안썼느냐의 차이이다.

Nothing은 의미 있는 데이터가 없다는 것을 명시적으로 표현하고자 사용한다.

Unit과 Nothing의 차이가 뭔지, 왜 필요한지 알려면 Generic을 알아야 한다고 함.

5.1.7 타입 확인과 캐스팅(is)

fun getStringLength(ojb: Any): Int? {
    val strData: String = obj <= 캐스팅 에러, obj가 어떤 타입인지 모름
    if (obj is String) {      <= 타입을 확인하고, obj가 String이면 String으로 캐스팅됨
        return obj.length
    }
    return null
}

위는 스마트 캐스팅의 예시이다.

코틀린은 기초 타입(Int, Double, Float, ...)끼리의 자동 형 변환을 제공하지 않는다. 기초 타입끼리 형 변환을 하려면 toXXX 형태로 써야 한다.

var a1: Int = 10
var a2: Double = a1  <= 에러
var a1: Int = 10
var a2: Double = a1.toDouble()  <= 성공

타입별 toXXX가 다 있음!

아래와 같은 상황에서는 형 변환이 자동으로 이루어짐

val l = 1L + 3  // Long + Int => Long
💡 as 연산자를 이용한 클래스간 캐스팅은 기초 데이터끼리 캐스팅 할 때에는 사용하면 안 된다. 에러 발생함. 나중에 상하관계가 있는 클래스간에 캐스팅을 할 때 as를 사용할 것이다.

5.2 컬렉션 타입

5.2.1 배열

코틀린에서 배열은 Array로 표현, Array는 get, set, size 등의 함수를 포함하는 클래스이다.

Array를 만드는 가장 쉬운 방법은 arrayOf() 함수를 이용하는 것이다.

fun main(args: Array<String>) {
    var array = arrayOf(1, "kkang", true)
    array[0] = 10
    array[2] = "world"
    println("${array[0]} .. ${array[1]} .. ${array[2]}")
    println("size: ${array.size} .. ${array.get(0)} .. ${array.get(1)} .. ${array.get(2)}")
}

[실행결과]
10 .. kkang .. world
size: 3 .. 10 .. kkang .. world

arrayOf로 만든 array에는 기본적으로 다양한 타입을 대입할 수 있는데, 특정 타입의 데이터만 대입할 수 있게 한정할 수 있다.

var arrayInt = arrayOf<Int>(10, 20, 30)
println("${arrayInt.get(0)} .. ${arrayInt.get(1)} .. ${arrayInt.get(2)}")

[실행결과]
10 .. 20 .. 30
💡 제네릭이란, 소스에서 <> 를 이용하여 타입을 명시하는 것을 말한다. 형식 타입이라고도 부르며, 선언하는 곳이 아닌 이용하는 곳에서 타입을 지정하기 위한 기법이다. 
arrayOf() 함수를 만들면서 타입을 지정하는게 아니라, arrayOf() 함수를 이용할 때 데이터 타입을 Int로 지정하는 등..

무슨 소리지?

기초 데이터 타입만을 원소로 가지는 array를 xxxArrayOf() 함수로 만들 수 있다.

var arrayInt = intArrayOf(10, 20, 30)
var arrayDouble = doubleArrayOf(10.0, 20.0, 30.0)

Array 클래스를 이용하여 직접 배열을 만들 수도 있다.

var array3 = Array(3, {i -> i*10})
println("${array3[0]} .. ${array3[1]} .. ${array3[2]]")

[실행결과]
0 .. 10 .. 20

여기서 {i -> i*10} 표현은 풀어쓰면 다음과 같다.

fun some(i: Int): Int {
    return i * 10
}

하여튼 이렇게 작성하면 arrayOf가 아니라 Array() 를 사용해서 배열을 만들 수 있는데, 그냥 그렇다고 알아두자.

마찬가지로,

var array4 = Array<Int>(3, {i -> i * 10})
var array5 = intArray(3, {i -> i * 10})

이런 것도 가능하다.

그럼 모든 값이 0인 Int Array는?

var array6 = Array<Int>(3, {i -> 0})

이렇게 하면 되지 않을까? 파이썬에서는

array6 = [0] * 3
array6 = [0 for _ in range(3)]

이렇게 했었는딩..

빈 배열 만들기

var array2 = arrayOfNulls<Any>(3) // 길이가 3인 null로 채워진 배열
array2[0] = 10
array2[1] = "hello"
array2[2] = true
println("${array2[0]} .. ${array2[1]} .. ${array2[2]}")

[실행 결과]
10 .. hello .. true

이렇게 arrayOfNulls 아니면,

var emptyArray = Array<String>(3, {""})
emptyArray[0] = "hello"
emptyArray[1] = "world"
emptyArray[2] = "kkang"
println("${emptyArray[0]} .. ${emptyArray[1]} .. ${emptyArray[2]}")

[실행 결과]
hello .. world .. kkang

Array 클래스를 이용하면서 생성자의 두 번째 인수에 배열의 초깃값을 빈 상태("")로 대입하여 만들 수 있다.

Array 외에는?

5.2.2 List, Set, Map

셋 모두 Collection이라는 인터페이스 타입으로 표현되는 클래스이며, 통칭해서 컬렉션 타입의 클래스들이라고 부른다.

List - 순서가 있는 데이터 집합, 데이터의 중복 허용

Set: 순서가 없으며 데이터 중복을 허용하지 않음

Map: 키와 값으로 이루어지는 데이터 집합, 순서가 없으며 키의 중복은 허용하지 않음

익숙한 놈들이다.

중요

코틀린에서 컬렉션 타입의 클래스들은 mutable, immutable 클래스로 구분된다.

List, Set, Map은 불변, size()와 get() 함수만 제공

MutableList, MutableSet, MutableMap은 가변, size(), get(), set() 등을 제공

MutableList는 ArrayList의 하위 클래스이지만 실질적으로 값의 차이는 없다. mutable로 통일해서 쓰면 될듯?

불변과 가변 리스트를 만들기 위한 함수는 listOf(), mutableListOf()로 제공되며, 이 함수들을 이용해 List 객체를 만들어 사용한다.

Map, Set도 다 똑같다.

val immutableList: List<String> = listOf("hello", "world")
val mutableList: MutableList<String> = mutableListOf("hello", "world")
mutableList.add("kkang")
mutableList.set(1, "korea") // index, 내용

꺼내쓰려면..
immutableList.get(0)
immutableList[0]
이런 식으로 사용하면 됨

아니면 ArrayList를 직접 이용해도 되는데,

val arrayList: ArrayList<String> = ArrayList()
arrayList.add("hello")
arrayList.add("world")
arrayList.set(1, "kkang")

이런 식으로 쓴다.

Map은?

val immutableMap1 = mapOf<String, String)(Pair("one", "hello"), Pair("two", "world"))
immutableMap1.get("one")

val immutableMap2 = mapOf<String, String>("one" to "hello", "two" to "kkang")

val mutableMap = mutableMpaOf<String, String>()
mutableMap.put("one", "hello")
mutableMap.put("two", "map")

Set은?

val immutableSet = setOf<String>("hello", "hello", "world")

val mutableSet = mutableSetOf<String>()
mutableSet.add("hello")
mutableSet.add("set")

자바의 List, Set, Map을 그대로 이용해도 된다. 생략

5.2.3 이터레이터(Iterator)

가끔 for문 대신 써도 될듯?

이터레이터는 컬렉션 타입의 데이터를 hasNext()와 next() 함수를 이용해 차례로 얻어서 사용하기 위한 인터페이스이다.

hasNext() 함수는 가져올 수 있는 데이터가 있으면 true, 없으면 false를 반환하는 함수이고, 실제 데이터를 가져올 때 사용하는 함수는 next()이다. List, Map, Set, Array 타입 데이터 모두 이터레이터 타입의 객체로 변형하여 이용할 수 있다.

val list1 = listOf<String>("hello", "list")
val iterator1 = list1.iterator()
while (iterator1.hasNext()) {
    println(iterator1.next())
}
val map = mapOf<String, String>("one", to "hello", "two" to "map")
val iterator2: Iterator<Map.Entry<String, String>> = map.iterator()
while (iterator2.hasNext()) {
    val entry = iterator2.next()
    println("${entry.key} - ${entry.value}")
}
val set = setOf<String>("hello", "set")
val iterator3 = set.iterator()
while (iterator3.hasNext()) {
    println("${iterator3.next()}")
}
val array = arrayOf("hello", "world")
val iterator4 = array.iterator()
while (iterator4.hasNext()) {
    println("${iterator4.nexxt()}")
}
[실행 결과]
hello
list
one - hello
two - map
hello
set
hello
world

코틀린은 배열을 []로 선언하지 않으며 {}를 이용하여 초기화하지도 않는다.

=> 코틀린에서는 모든 것이 객체로 이용되므로 배열도 Array 클래스의 객체로 표현된다. 그러므로 배열을 표현할 때 val array: Array 이용해야 하며 초깃값을 대입할 때도 arrayOf() 등의 함수를 이용해야 한다.

0개의 댓글