[Kotlin Tutorial] Kotlin 의 Type system #2
참조 : Kotlin in action
6.2. Primitive and other basic types
6.2.1. Primitive types: Int, Boolean, and more
-
Kotlin 은 primitive type 과 wrapper type 을 구분하지 않는다.
-
그렇다면 Int 가 object 라면 Kotlin 은 모든 primitive type 을 실제로 object 로 만드는가?
당연히 그렇게 안 했다.
compiler 가 대부분의 Int type 을 Java 의 primitive type 으로 변형시킨다.
generic, collection 등은 원래 Java 의 Integer 형태만 담을 수 있으므로 이 경우는 Int 가 Object 로 남는다.
즉 runtime 관점에서 Java 의 primitive int 가 될 수 있는 녀석은 모두 그 형태라고 보면 된다.
-
Integer types - Byte, Short, Int, Long
Floating-point number types - Float, Double
Character type - Char
Boolean type - Boolean
6.2.2. Nullable primitive types: Int?, Boolean?, and more
-
Kotlin 의 nullable type 은 Java 의 primitive type 이 될 수 없다. Java 에서는 reference 만 null 을 받을 수 있기 때문.
그래서 primitive type 들의 nullable version 을 사용하면 그것들은 wrapper type 으로 compile 된다.
-
Collection 이나 Generic 도 마찬가지로 wrapper 를 사용한다.
6.2.3. Number conversions
-
Kotlin 은 Java 와 달리 implicit type conversion 을 지원하지 않는다.
val myInt = 1
val myLong: Long = myInt // compile error
val.myLong : Long = myInt.toLong()
-
Boolean 을 제외한 모든 primitive type 은 toByte(), toShort(), toChar() 등의 conversion function 이 정의되어 있다.
smaller to larger, larger to smaller 보두 지원한다.
-
val x = 1
val list = listOf(1L, 2L, 3L)
x in list // false
x.toLong() in list // true
-
Long 은 L 을 suffix 로 붙여줘야 한다.
일반적인 floating-point number 는 Double
Float 은 f 를 suffix 로 붙여줘야 한다.
Hexadecimal 은 똑같이 0x 나 0X 를 prefix 로 붙여준다.
binary literal 은 0b 나 0B 를 prefix 로 붙여준다.
character literal 은 java 와 동일
-
val b: Byte = 1
val l = b + 1L
fun foo(l:Long) = println(l)
foo(42) // 42 를 42L 로 치부한다.
이 녀석들은 또 compile error 없이 잘 된다.. ( 왜… 왜 그런거야..? ㅠ )
arithmetic operator 의 number-range overflow는 Java 와 동일하다.
-
String 에 대해서도 toInt, toByte, toBoolean 등의 primitive type conversion 함수들이 제공된다.
치환할 수 없는 경우 Java 와 마찬가지로 NumberFormatException 이 발생한다.
( 1.1 부터는 toIntOrNull 함수가 추가되어 이 녀석을 쓰면 더 좋다 )
6.2.4. “Any” and “Any?”: the root types
-
Java 의 Object 처럼 Any 는 Kotlin 의 nonnull type 의 supertype 이다.
Java 의 Object 는 primitive type 을 못 담는 것에 비해 Kotlin 의 Any 는 primitive type 모두를 담을 수 있다.
val answer: Any = 42 // auto boxing
-
Any 는 null 을 가질 수 없다.
null 까지 담으려면 Any? 를 써야 한다.
-
Java 에서의 param 으로 쓰이는 또는 return 되는 Object 는 Any 로 보인다. (사실 platform type 의 Any)
Any 는 Compile 되면 Java 의 Object 가 된다.
-
Kotlin 의 toString, equals, hashCode 는 Any 에 있는 method 들이다.
Object 에 있는 wait, notify 는 Any 에 정의되어 있지 않다.
쓰려면 Object 로 manual conversion 해서 써야 한다.
6.2.5. The unit type: Kotlin’s “void”
-
Kotlin 의 Unit type 은 Java 의 void 와 같다.
fun f(): Unit { … } // 안 써도 그만~
-
그럼 void 를 안 쓰고 왜 Unit type 이란 걸 만들었냐?
Unit type 은 제대로된 type 의 하나이다.
Generic parameter 를 return 하는 함수를 override 할 때 유용하다.
interface Processor<T> {
fun process(): T
}
class NoResultProcessor: Processor<Unit> {
override fun process() {
...
}
}
Java 에서는 이것이(Return 타입이 있고 없고를 이런 문법방식으로 해결하는 것) 어렵기 때문에 다른 interface 를 쓰던가 ( 예를 들면 Callable and Runnable ), 아니면 Void type 을 써야 하는데, Void 를 사용할 경우 return null; 을 무조건 해줘야 한다. ( AsyncTask 생각해보세요 여러분 )
-
그럼 왜 Void 대신 Unit 을 사용하느냐?
보통 기존의 functional language 에서Unit 은 “only one instance” 를 이야기한다.
그리고 Kotlin 에는 “Nothing” 이라는 type 도 있다. 그래서 void 와 헷갈린다.
그래서 Unit 을 사용하기로 결정!
6.2.6. The Nothing type: “This function never returns”
-
test library 에서 exception 등을 던지는, 무조건 fail 하는 function 이라던지,
함수 내부에서 infinite loop 를 돌아서 절대 return 하지 않는 경우가 있다.
이렇게 절대 성공적으로 function 이 끝나지 않는 경우에 Nothing 을 return type 으로 두어 가독성을 주기로 했단다.
fun fail(message: String): Nothing{
throw IllegalStateException(message)
}
val address = company.address ?: fail(“No address”)
println(address.city)
위의 코드에서 address 는 nullable 로 주지 않아도 된다.
왜냐하면 fail 이 Nothing type 이기 때문에 address 는 nonnull 일 수밖에 없음을 compiler 가 알 수 있기 때문이다.
6.3. Collections and Arrays
6.3.1. Nullability and collections
-
collection 에서도 null 을 element 로 가질 수 있는지 control 할 수 있다.
val result = ArrayList<Int?>()
-
Kotlin 에서는 null 을 수용할 수 있는 collection 에서 nonnull 인것들만 filter 해내는 filterNotNull function 이 있다.
6.3.2. Read-only and mutable collections
-
Kotlin 에서는 Collection 과 Mutable-Collection interface 를 나누어 두었다.
Collection 은 read only feature 만 가지고 있고, MutableCollection 은 Collection 을 extends 하며 modify 속성이 추가되었다.
-
val 과 var 같이 대부분의 경우 Collection interface 를 사용하는 것이 좋고, 필요한 경우에만 MutableCollection 을 사용하는 것이 좋다.
-
fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>){
...
}
val source : Collection<Int> = arrayListOf(3, 5, 7)
val target: MutableCollection<Int> = arrayListOf(1)
copyElements(source, target)
copyElements(source, source) // compile error
실제 arrayListOf 는 MutableCollection 이지만, val 의 type 을 Collection 으로 받으면 Collection type 으로 여겨 compile error 가 난다.
-
read-only collection 으로 지정되었다고 해도, 실제 실체는 MutableCollection 일 수 있기 때문에 ConcurrentModificationException 은 발생할 수 있다.
이 점을 주의해야 한다. ( thread-safe 는 좀 다른 관점으로 봐야 한다. )
6.3.3. Kotlin collections and Java
-
Kotlin collection 과 Java collection 은 완벽히 상호 호환되는 같은 녀석이다.
wrapping 이나 conversion 등이 전혀 필요없다.
그러나 Kotlin 만의 특징이 있다면, read-only 와 mutable 의 두 가지 형태의 interface 로 나뉜다는 데 있다.
( list, set, map 등 모든 collection )
-
Collection Type |
Read-only | Mutable |
List |
listOf |
mutableListOf, arrayListOf |
Set |
setOf |
mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf |
Map |
mapOf |
mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf |
Kotlin 1.0 에서 setOf 나 mapOf 는 실제로는 mutable version 을 return 하고 있지만, 추후 Kotlin 버전에서는 언제 진짜 immutable 이 return 될지 모른다.
(unmodifiable 은 overhead 가 있어 호출하고 있지 않다고 한다.)
-
Kotlin 에서 Collection 을 다루는 Java 코드를 호출할 떄는 read-only 를 넘겨도 modify 를 할 수 있다.
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;
}
}
마찬가지로 nonnull collection 인데도 Java 안에서는 null 을 추가할 수 있다.
그래서 이 부분들에 대해서는 (Java function 에 Collection 을 넘기는 경우) 주의해야 한다.
6.3.4. Collections as platform types
-
Collection 에도 platform type 은 적용된다.
platform type 의 collection 은 기본적으로 unknown mutability collection 이라고 보면 된다.
-
Java 의 함수를 override 할 때는 다음을 고려해야 한다.
1. collection 이 nullable 인가
2. elements 들이 nullable 인가
3. collection 을 modify 할 것인가
/* Java */
interface FileContentProcessor {
void processContents(File path, byte[] binaryContents, List<String> textContents);
}
interface DataParser<T>{
void parseData(String input, List<T> output, List<String> errors);
}
/* Kotlin */
class FileIndexer : FileContentProcessor{
override fun processContents(path:File, binaryContents:ByteArray?, textContents:List<String>?){
...
}
}
class PersonParser : DataParser<Person>{
override fun parseData(input:String, output:MutableList<Person>, errors:MutableList<String?>){
...
}
}
6.3.5. Arrays of objects and primitive types
-
기본적으로 collection 을 쓰는 것이 array 를 쓰는 것보다 선호되지만, Java API 들에서 아직 array 를 쓰는 곳들이 있다.
그리고 vararg parameter 일 때도 쓰인다.
Kotlin 에서 array 는 type parameter 가 있는 class 이다.
-
다음을 통해 array 들을 만들 수 있다.
arrayOf(1,2,3)
arrayOfNulls(5) // 5개의 element 를 null 로 갖는 array
Array<String>(26) { i -> (‘a’ + i).toString() } // alphabet 을 갖는 array, i 는 index 가 들어온다
-
Collection 을 array 로 전환하려면 toTypedArray 를 호출해주면 된다.
val strings = listOf(“a”, “b”, “c”)
println(“%s/%s/%s”.format(*strings.toTypedArray()))
// * 는 array 를 spread 해준다고 해서 spread operator 이다. vararg parameter 가 들어가는 곳에 array 를 대입할 때 쓰인다.
-
array 에 들어가는 type 들은 모두 object type 이다.
그래서 primitive type 의 array 를 만들려면 특별한 class 를 써야 한다.
IntArray, ByteArray, CharArray, BooleanArray, 등등..
( Kotlin 의 Array<Int> 는 Java 의 Integer[] 에 mapping 된다 )
-
primitive type array 를 만드는 방법은 아래와 같다.
IntArray(5) // 5개의 element 는 default value 로 채워짐
intArrayOf(0,0,0,0,0)
IntArray(5) { i -> (i+1) * (i+1) }
-
Boxing 된 array 는 toIntArray 와 같은 함수로 primitive type array 로 만들 수 있다.
-
array 들도 collection 과 같은 extension function 들을 가지고 있다.
그래서 filter, map, 등을 array 에도 쓸 수 있다.
primitive array 도 이것들이 적용되는데, 주의할 점은 이들의 return 값은 list 이다. ( array 가 아니다 !! )
-
fun main(args: Array<String>){
args.forEachIndexed { index, element ->
println(“Argument $index is: $element”)
}
}
6.4. Summary
-
Kotlin 은 compile time 에 NPE 를 찾기 위해 nullable type 이 지원된다.
-
Kotlin 은 safe call ( ?. ), Elvis operator( ?: ), not-null assertions( !! ), nullable type 을 다루기 위한 let function 등이 지원된다.
-
as? operator 는 cast 에 대해 유연하게 처리할 수 있게 해준다.
-
Java 로부터 오는 Type 은 Kotlin 에서는 platform type 으로 매핑된다.
개발자가 알아서 nullable 혹은 non-null 로 매핑해야 한다.
-
Int 와 같은 녀석들은 일반적인 class 처럼 보이고, 작동하지만 실제 compile 이 되면 대부분 Java primitive type 으로 치환된다.
-
“Any” type 은 Java 의 Object 처럼 모든 것의 supertype 이다.
Unit 은 void 와 비슷한 녀석이다.
-
“Nothing” type 은 정상 종료되지 않는 function (exception 만 던지거나, infinite loop 등) 의 return type 으로 사용될 수 있다.
-
Kotlin 에서는 Java 의 standard collection class 를 그대로 사용하며, 여기서 extension function 으로 기능 강화만 시켰다.
그리고 read-only 와 mutable collection 을 나누어, compile time 의 에러 체크를 강화하였다.
-
Java 의 함수를 override 할 때는 nullability 와 mutability 를 잘 고려해야 한다.
특히나 collection 일 때는 더 복잡하다.
-
Kotlin 의 Array class 는 generic class 로 보인다. 그러나 Java 의 array 로 compile 된다.
-
primitive array type 은 IntArray 등으로 표현된다.
댓글