-
Notifications
You must be signed in to change notification settings - Fork 0
CH06. 코틀린 타입 시스템 (이희찬)
s: String?
코틀린에서의 모든 타입은 null이 될 수 없지만, 타입 이름 뒤에 물음표를 붙이면, null이 될 수 있는 타입이 됩니다.
fun strLenSafe(s: String?) = s.length()
>>> ERROR: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiverof type kotlin.String?
null이 가능한 변수는 변수.메소드() 할 수 없습니다.
val x: String? = null
val y: String = x
>>> ERROR: Type mismatch: inferred type is String? but String was expected
null이 될 수 있는 값을 null이 될 수 없는 변수에 대입할 수 없습니다.
fun strLenSafe(s: String?): Int =
if(s != null) s.length else 0
val x: String? = null
println(strLenSafe(x))
>>> 0
null과 비교하면, 컴파일러는 그 사실을 기억하고 null이 없는 타입의 값처럼 사용할 수 있습니다.
s?.toUpperCase()
>>> if (s != null) s.toUpperCase() else null
.?은 null 검사와 메소드 호출을 한 번의 연산으로 수행합니다. 다시 말해서, 호출하려는 값이 null이 아니면 일반 메소드 호출처럼 작동하지만, 호출하려는 값이 null이면 이 호출은 무시되고 null이 반환됩니다.
fun foo(s: String?) {
val t: String = s ?: ""
}
위처럼 엘비스 연산자를 사용하면, null일 경우에 특정 값으로 바꿔줄 수 있습니다.
fun foo(s: Any?) {
val s as? Person ?: return false
}
위처럼 안전한 연산자는 타입이 일치하지 않으면 null을 반환하기 때문에, “false”를 반환합니다.
이는 null이 아님을 알리고, null이 될 수 없는 타입으로 바꿀 수 있습니다.
fun sendEmailTo(email: String) {
println("Sending email to $email")
}
var email: String? = "[email protected]"
email?.let { sendEmailTo(it) }
>>> Sending email to sendEmailTo
email = null
email?.let { sendEmailTo(it) }
>>> // 아무일도 일어나지 않음.
let 함수는 원하는 식을 평가해서 결과가 null인지 검사한 다음에 결과를 변수에 넣는 작업을 할 수 있습니다.
let 함수 안의 로직이 null일 경우에는 아무 일도 일어나지 않습니다.
isNullOrBlank()를 예시로 들면, isBacnk()는 null이 아닌 문자열 타입의 값에 대해서만 호출할 수 있습니다.
하지만, isNullOrBlank()는 null인 경우 true를 반환하고, null이 아닌 경우 isBlank()를 호출하여 null 값에 대한 적절한 처리를 할 수 있습니다.
fun <T> printHashCode(t: T) {
println(t?.hashCode())
}
위처럼 “T”의 타입은 Any?이기 때문에, 안전한 호출을 써야만 합니다.
List<int> list1 = new ArrayList<>();
>>> X
List<Ineger> list2 = new ArrayList<>();
>>> O
val list: List<Int>
코틀린은 자바와 달리 참조 타입과 원시 타입을 구분하지 않고, 항상 같은 타입을 사용합니다.
또한, null 참조를 자바의 참조 타입의 변수에만 대입할 수 있기 때문에 null이 될 수 있는 코틀린 타입은 자바의 원시 타입으로 표현할 수 없습니다.
자바에서 Object가 클래스의 최상위 타입이듯, 코틀린에서는 Any 타입이 모든 null이 될 수 없는 타입의 조상 타입입니다.
자바에서의 void는 코틀린에서 Unit과 같은 기능을 담당합니다.
하지만 코틀린에서는 반환 값이 없을 경우, 반환 타입을 명시하지 않습니다. 그렇다면 언제 Unit을 명시할까요?
interface Processor<T> {
fun process(): T
}
class NoResultProcessor : Processor<Unit> {
override fun process() {
// 내용
}
}
위 와같이 제네릭 파라미터를 반환하는 함수를 오버라이드하면서 반환 타입으로 Unit을 쓸 때 유용합니다.
위 코드는 Unit을 반환하지만, 타입을 지정할 필요는 없습니다. 또한, 메서드 안에서 return을 명시하지 않아도 됩니다.
Nothing 반환 타입은 한 문장으로 “이 함수는 결코 정상적으로 끝나지 않는다”를 뜻합니다.
fun fail(message: String) : Nothing {
throw IllegalStateException(message)
}
다시말해서, 위처럼 반환을 하지않고 예외를 던질 때 사용합니다.
컬렉션 자체가 null일 경우도 있지만, 컬렉션의 원소 중에 null이 있을 경우가 있습니다.
fun addValidNumbers(numbers: List<Int?>) {
var sumOfValidNumbers = 0
var invalidNumbers = 0
for (number in numbers) {
if (number != null) {
sumOfValidNumbers += number
} else {
invalidNumbers++
}
}
}
fun addValidNumbers(numbers: List<Int?>) {
val validNumbers = numbers.filterNotNull()
}
코틀린은 이를 위처럼 filterNotNull()을 사용하여 해결합니다.
코틀린에서는 자바와 달리 컬렉션을 읽기 전용(Collection)과 변경 가능한(MutableCollection) 컬렉션으로 나누었습니다.
사용하는 컬렉션이 읽기만 할 것인지, 변경도 할 것인지를 알리기 위해 나눴다고 합니다.
fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>)
변경할 일이 없다면 최대한 읽기 전용으로 쓰다가, 위의 메서드를 사용하여 변경 가능한 컬렉션으로 바꾸어 사용하면 됩니다.
위처럼 모든 코틀린 컬렉션은 그에 상응하는 자바 컬렉션 인터페이스의 인스턴스입니다.
자바에도 배열이 있듯이 코틀린도 배열이 있습니다. 객체 배열과 원시 타입 배열이 있습니다.
val letters = Array<String>(26) { i -> ('a' + i).toString() }
위는 Array 생성자를 사용하여 객체 배열 만드는 방법입니다. 이 외에도 arrayOf 메서드를 사용하거나 arrayOfNull 메서드를 사용하여 객체 배열을 만들 수 있습니다.
val squares = IntArray(5) { i -> (i+l) * (i+l) )
val fiveZerosToo = intArrayOf(0, 0, 0, 0, 0)
위는 원시 타입 배열을 생성하는 방법입니다. IntArray 타입 외에도 ByteArray, CharArray 등 여러가지가 있습니다. (컴파일 시에는 int[], byte[], char[], ..로 컴파일 됨)