BottleH Blog

Kotlin in Action - 8장 기본 타입, 컬렉션, 배열

    Tags

  • Kotlin
Kotlin in Action - 8장 기본 타입, 컬렉션, 배열 thumbnail

📖 8.1 원시 타입과 기본 타입

🔖 8.1.1 정수, 부동소수점 수, 문자, 불리언 값을 원시 타입으로 표현

val i: Int = 1 val list: List<Int> = listOf(1, 2, 3)
  • 코틀린은 원시 타입과 래퍼 타입을 구분하지 않는다.
  • 매번 객체로 표현하는 것이 아니라 실행 시점에 숫자 타입은 가능한 한 가장 효율적인 방식으로 표현된다.

🔖 8.1.2 양수를 표현하기 위해 모든 비트 범위 사용: 부호 없는 숫자 타입

타입 비트 크기 표현 범위
UByte 8비트 0 ~ 255
UShort 16비트 0 ~ 65,535
UInt 32비트 0 ~ 4,294,967,295
ULong 64비트 0 ~ 18,446,744,073,709,551,615
  • 코틀린에는 4가지 부호없는 타입이 있다.
  • 일반적인 원시 타입을 확장해 부호 없는 타입을 제공한다.

🔖 8.1.3 널이 될 수 있는 기본 타입: Int?, Boolean? 등

  • null 참조를 자바의 참조 타입의 변수에만 대입할 수 있기 때문에 널이 될 수 있는 코틀린 타입은 자바 원시 타입으로 표현할 수 없다.
data class Person(val name: String, val age: Int? = null) { fun isOlderThan(other: Person): Boolean? { if (age == null || other.age == null) { return null } return age > other.age } }
  • 두 값이 널이 아닌지 검사해야 한다.
  • 컴파일러는 널 검사를 마친 다음에야 두 값을 일반적인 값처럼 다루도록 허용한다.

🔖 8.1.4 수 변환

  • 코틀린과 자바의 가장 큰 차이점 중 하나는 수를 변환하는 방식이다.
  • 코틀린은 한 타입의 수를 다른 타입의 수로 자동 변환하지 않는다.
val i = 1 val i: Long = b.toLong() // 자동 변환 X
  • 모든 원시 타입에 대해 양방향 변환 함수가 모두 제공
val x = 1 val list = listOf(1L, 2L, 3L) x in list // false
  • 암시적 변환을 허용하지 않는다.
  • 숫자 리터럴을 상요할 때는 변환 함수를 호출할 필요가 없다.
    • ex) 1L, 0.2f
    • 컴파일러가 필요한 변환을 자동으로 넣어준다.
fun main() { println(Int.MAX_VALUE + 1) // 음수 최솟값 println(Int.MIN_VALUE - 1) // 양수 최댓값 }
  • 코틀린 산술 연산자에서도 숫자 연산 시 오버플로나 언더플로가 발생할 수 있다.
  • 검사하느라 추가비용이 들지 않는다.

🔖 8.1.5 Any와 Any?: 코틀린 타입 계층의 뿌리

  • 자바와 달리 Any가 원시 타입을 포함한 모든 타입의 조상 타입이다.
val answer: Any = 42
  • 원시 타입 값을 Any 타입의 변수에 대입하면 자동으로 값을 객체로 감싼다.(박싱)
  • Any가 널이 될 수 없는 타입임에 유의

🔖 8.1.6 Unit 타입: 코틀린의 void

fun f(): Unit {} fun f() {}
  • Unit을 반환하지만 타입을 지정할 필요는 없다.
  • return을 명시할 필요가 없다.
    • 컴파일러가 암시적으로 return Unit을 넣어준다.
  • 함수형 프로그래밍에서 전통적으로 Unit은 단 하나의 인스턴스만 갖는 타입을 의미
  • java의 void와 인스턴스의 유무가 가장 큰 차이

🔖 8.1.7 Nothing 타입: 이 함수는 결코 반환되지 않는다

fun fail(message: String): Nothing { throw IllegalArgumentException(message) }
  • Nothing 타입은 아무 값도 포함하지 않는다.
  • 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 쓸 수 있다.
val address = company.address ?: fail("No address")
  • Nothing을 반환하는 함수를 엘비스 연산자의 오른쪽에 사용해서 전제조건을 검사할 수 있다.

📖 8.2 컬렉션과 배열

🔖 8.2.1 널이 될 수 있는 값의 컬렉션과 널이 될 수 있는 컬렉션

fun readNumbers(text: String): List<Int?> { val result = mutableListOf<Int?>() for (line in text.lineSequence()) { val numberOrNull = line.toIntOrNull() result.add(numberOrNull) } return result }
  • List<Int?>
    • List 자체는 항상 Null이 아니다.
    • 각 원소는 Null이 될 수 있다.
  • List<Int>?
    • List는 Null이 될 수 있다.
    • 각 원소는 Null이 될 수 없다.
fun readNumbers2(text: String): List<Int?> = text.lineSequence().map { it.toIntOrNull() }.toList()
  • 함수형 프로그래밍으로 간단하게 표현할 수 있다.
  • 널이 될 수 있는 값으로 이뤄지고, 널이 될 수 있는 리스트를 정의해야 한다면 List<Int?>?로 표현할 수 있다.
fun addValidNumbers(numbers: List<Int?>) { var sumOfValidNumbers = 0 var invalidNumbers = 0 for (number in numbers) { if (number != null) { sumOfValidNumbers += number } else { invalidNumbers++ } } println("Sum of valid numbers: $sumOfValidNumbers") println("Invalid numbers: $invalidNumbers") } fun main() { val input = """ 1 abc 42 """.trimIndent() val numbers = readNumbers(input) addValidNumbers(numbers) // Sum of valid numbers: 43 // Invalid numbers: 1 }
  • filterNotNull을 사용해 간단하게 만들 수 있다.
fun addValidNumbers(numbers: List<Int?>) { val validNumbers = numbers.filterNotNull() println("Sum of valid numbers: ${validNumbers.sum()}") println("Invalid numbers: ${numbers.size - validNumbers.size}") }

🔖 8.2.2 읽기 전용과 변경 가능한 컬렉션

코틀린의 컬렉션 인터페이스는 읽기 전용(read-only) 과 변경 가능(mutable) 두 가지 버전이 있다.

fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) { for (item in source) { target.add(item) } } fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) { for (item in source) { target.add(item) } } fun main() { val source: Collection<Int> = arrayListOf(3, 5, 7) val target: Collection<Int> = arrayListOf(1) copyElements(source, target) // 컴파일 에러 발생 }
  • 읽기 전용 컬렉션이 항상 thread safe하지는 않는다.
    • 내부에서 변경할 수도 있음!
    • 즉, 불변은 아니다.

🔖 8.2.3 코틀린 컬렉션과 자바 컬렉션은 밀접히 연관됨

모든 코틀린 컬렉션은 그에 상응하는 자바 컬렉션 인터페이스의 인스턴스이다.

  • 코틀린의 읽기 전용과 변경 가능 인터페이스의 기본 구조는 java.util 패키지에 있는 자바 컬렉션 인터페이스의 구조와 같다.
  • 읽기 전용 인터페이스에는 컬렉션을 변경할 수 있는 모든 요소가 빠져있다.
컬렉션 타입 읽기 전용 타입 변경 가능 타입
List listOf, List mutableListOf, MutableList
Set setOf mutableSetOf, MutableSet
Map mapOf mutableMapOf, MutableMap
public class CollectionUtils { public static List<String> upperCaseAll(List<String> items) { for (int i = 0; i < items.size(); i++) { items.set(i, items.get(i).toUpperCase()); } return items; } } fun printInUpperCase(list: List<String>) { println(CollectionUtils.upperCaseAll(list)) println(list.first()) } fun main() { val list = listOf("a", "b", "c") printInUpperCase(list) }
  • 컬렉션을 변경하는 자바 메서드에게 읽기 전용 Collection을 넘겨도 코틀린 컴파일러가 이를 막을 수 없다.
  • 컬렉션을 자바 코드에게 넘길 때는 특별히 주의를 기울여야 한다.

🔖 8.2.4 자바에서 선언한 컬렉션은 코틀린에서 플랫폼 타입으로 보임

  • 플랫폼 타입의 경우 코틀린 쪽에는 널 관련 정보가 없다.
  • 자바 쪽에서 선언한 컬렉션 타입의 변수를 코틀린에서는 플랫폼 타입으로 본다.
  • 컬렉션 타입이 시그니처에 들어간 자바 메서드 구현을 오버라이드하려는 경우 읽기 전용 컬렉션과 변경 가능 컬렉션의 차이가 문제가 된다.
    • 컬렉션이 null이 될 수 있는가?
    • 컬렉션의 원소가 null이 될 수 있는가?
    • 여러번이 작성할 메서드가 컬렉션을 변경할 수 있는가?
public interface FileContentProcessor { void processContents(File path, byte[] binaryContents, List<String> textContents); }
  • 이를 코틀린으로 구현하면 아래와 같다.
class FileIndexer : FileContentProcessor { override fun processContents(path: File, binaryContents: ByteArray?, textContents: MutableList<String>?) { TODO("Not yet implemented") } } interface DataParser<T> { void parseData( String input, List<T> output, List<String> errors ); }
  • 이를 코틀린으로 구현하면 아래와 같다.
class PersonParser : DataParser<Person> { override fun parseData(input: String, output: MutableList<Person>, errors: MutableList<String?>) { TODO("Not yet implemented") } }
  • java 인터페이스나 클래스가 어떤 맥락에서 사용되는지 정확히 알아야 한다.

🔖 8.2.5 성능과 상호운용을 위해 객체의 배열이나 원시 타입의 배열을 만들기

fun main(args: Array<String>) { for (i in args.indices) { println("Argument $i is: ${args[i]}") } }
  • 코틀린 배열은 타입 파라미터를 받는 클래스다.
fun main() { val letters = Array<String>(26) { i -> ('a' + i).toString() } println(letters.joinToString("")) }
  • 타입인자를 생략해도 컴파일러가 알아서 원소 타입을 추론해준다.
fun main() { val letters = Array(26) { i -> ('a' + i).toString() } println(letters.joinToString("")) }
  • 데이터가 이미 컬렉션에 들어 있다면 컬렉션을 배열로 변환해야 한다.
fun main() { val strings = listOf("a", "b", "c") println("%s/%s/%s".format(*strings.toTypedArray())) // 스프레드 연산자 사용 }
  • 코틀린은 원시 타입의 배열을 표현하는 별도 클래스를 각 원시 타입마다 하나씩 제공한다.
val fiveZeros = IntArray(5) val fiveZerosToo = intArrayOf(0, 0, 0, 0, 0) fun main() { val squares = IntArray(5) { i -> (i + 1) * (i + 1) } println(squares.joinToString()) }
  • 배열을 만드는 방법은 다양하다.
Written by@BottleH
Back-End Developer

GitHub