📖 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())
}
- 배열을 만드는 방법은 다양하다.
