2019-10-06 TIL

2019-10-06

Kotlin 시작

구글에서 kotlin 강의를 검색하면 Inflearn의 무료강좌가 공식문서 마냥 맨 위에 뜬다. kotlin의 공식문서를 볼까 했지만 일단 보면서 내용 정리 및 기록을 해본다.

Basic Syntax

패키지 정의

package my.demo // 패키지 정의는 파일 최상단에. directory와 패키지를 일치시키지 않아도 됨

import java.util.* // import는 자바와 같다고 한다
// ..

함수 정의

fun sum(a: Int, b: Int): Int {
  return a + b
}
fun sum(a: Int, b: Int) = a + b
// Expression으로 선언 가능. 이 경우 리턴 타입을 생략 가능하며 타입을 추론해줌
fun printKotlin(): Unit {
  println("hello Kotlin")
}
// Unit 은 자바의 void와 같은 역할. 생략가능한데 이 경우 묵시적으로 Unit을 리턴함이 정의됨. JS같네.

지역 변수 정의

val : 읽기 전용 변수. 자바의 final과 비슷하다함

val a: Int = 1  // 즉시 할당
val b = 2       // Int 타입 추론
val c: Int      // 컴파일 오류. 초기화 필요
c = 3           // 컴파일 오류. 읽기전용

var : mutable 변수

var x = 5
x += 1

주석

JS와 동일한데 다른점은 block comment의 중첩이 가능

/* block comment
  /* nested block comment 가능*/
  허헛
*/

문자열 템플릿

String Interpolation

var a = 1
val s1 = "a is $a"

a = 2
val s2 = "${s1.replace("is", "was")}, but now is $a"

JS와 유사하지만 단순 변수 대입할땐 중괄호가 필요없네.

조건문

fun maxOf(a: Int, b: Int): Int {
  if (a > b) {
    return a
  } else {
    return b
  }
}
// 조건식으로 사용 가능. 간결.
fun maxOf(a: Int, b: Int) = if (a > b) a else b

근데 가독성 측면에서 block 표기를 선호하는 경우도 있기 때문에 이런식의 간결화는 호불호 내지는 장단점이 있다고 본다.

nullable

타입스크립트, flow에서 접해본 개념.

fun parseInt(str: String): Int? {
  // 정수가 아닌 경우 null 리턴
}

nullable 타입의 변수에 접근할 때는 반드시 null 체크를 해야 함 컴파일 오류로 이어짐

fun printProduct(arg1: String, arg2: String) {
  val x: Int? = parseInt(arg1)
  val y: Int? = parseInt(arg2)

  if (x != null && y !== null) {
    println(x * y)
  } else {
    println("either '$arg1' or '$arg2' is not a number")

그렇다고 한다. if문으로 처리하는게 뭔가좀 번거로워 보이는데.. 간편한 문법을 지원하지 않으려나?

자동 타입 변환

타입 체크만 해도 자동으로 타입 변환 됨

fun getStringLength(obj: Any): Int? {
  if (obj is String) {
    // obj 가 자동으로 String 타입으로 변환됨
    return obj.length
  }

  return null
}

when expression

JS는 아직 제안단계(Stage 1) pattern matching

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

ranges

val x = 3
if (x in 1..10) {
  println("fits in range")
}
for (x in 1..5) {
  print(x)
}

collections

컬렉션도 in으로 loop 가능

val items = listOf("apple", "banana", "kiwi")
for (item in items) {
  println(item)
}

in으로 컬렉션에 포함되는지 체크 가능

val items = setOf("apple", "banana", "kiwi")
when {
  "orange" in items -> println("juicy")
  "apple" in items  -> println("apple is fine too")
}

람다식을 이용해서 컬렉션에 filter, map 등 연산 가능

val fruits = listOf("banana", "avocado", "apple", "kiwi")
fruits
      .filter { it.startsWith("a") }
      .sortedBy { it }
      .map { it.toUpperCase() }
      .forEach { println(it) }

(람다식이 뭔가 했더니 JS에서 화살표함수와 유사한 형태의 문법이고.. OOP 언어인 자바에서 함수형 지원을 위해 도입한 것으로 보인다. 마치 JS가 객체지향 패러다임을 지원하려고 prototype으로 흉내를 내듯.. 여기 적은 예제는 자바에 람다식과 함께 도입된 stream API를 함께 사용했다. parameter가 it 하나뿐이면 it ->을 생략할 수 있다. 참고)

Basic Types

  • 자바는 primitive, reference 타입
  • kotlin은 모든것이 객체. 모든것에 멤버 함수, 프로퍼티 호출 가능.

Number

자바와 비슷하지만..

  • kotlin에서 number는 클래스. java의 primitive type에 직접 접근할 수 없다.
  • java에서 숫자형이던 char가 kotlin에선 클래스.
Kotlin Java bit
Double double 64
Float float 32
Long long 64
Int int 32
Short short 16
- char 16
Byte byte 8

Literal

kotlin은 8진수를 지원하지 않음

  • 10진수: 123 (Int, Short)
  • Long: 123L
  • Double: 123.5, 123.5e10
  • Float: 123.5f
  • 2진수: 0b00001011
  • 8진수: 미지원 (Java: int I = 017;)
  • 16진수: 0X0F

Underscores in numeric literals (since 1.1)

언더스코어로 자리수 끊어 가독성을 높일 수 있다.

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_2344L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

Representation

  • Java 플랫폼에서 숫자형은 JVM primitive type으로 저장됨
  • Nullable이나 제네릭의 경우에는 박싱됨
  • 박싱된 경우 identity를 유지하지 않음
// JVM primitive
val a: Int = 100
print(a === a) // Prints 'true'

// Boxed
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println("==: ${boxedA == anotherBoxedA}") // Prints 'true'
println("===: ${boxedA === anotherBoxedA}") // Prints 'false'

참고: ===는 양변이 같은 객체인지 비교 가능

생각해보면 당연하다. 한 int 변수에 숫자가 아닌 null이 들어올 수 있으려면 int라는 타입만으로는 표현이 불가하다. nullable하려면 변수가 다른 타입이 되어야 하니 객체가 되는거다.

Explicit Conversions

작은 타입은 큰 타입의 하위 타입이 아님. 암시적으로는 숫자 타입 변환이 안됨. 명시적으로 변환해줘야 함

val a: Int = 1 // A boxed Int
// val b: Long = a // 오류

val b: Long = a.toLong()
// println(a == b) // 오류: Operator '==' cannot be applied to 'Int' and 'Long'
val i: Int = b.toInt() // OK
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

Characters

Char는 숫자로 취급되지 않음

fun check(c: Char) {
  if (c == 1) {/*...*/} // Error
}

fun check(c: Char) {
  if (c == `a`) {/*...*/} // OK
}

print('0'.toInt()) // print 48 (코드값)

배열

  • kotlin에선 Array는 클래스
  • get, set ([] 연산자 오버로딩됨)
  • size 등 유용한 멤버 함수 포함

    var array: Array<String> = arrayOf("코틀린", "강좌")
    println(array.get(0)) // 0번째 인덱스 꺼냄
    println(array[0]) // 오버로딩 돼있어서 이렇게도 사용가능
    println(array.size)

JS같다!

배열 생성

두가지 방법

  • Array의 팩토리 함수 이용
  • arrayOf() 등의 라이브러리 함수 이용
val b = Array(5, {i -> i.toString() }) // 범위와 람다식 이용

val a = arrayOf("0", "1", "2", "3", "4")

특별한 Array 클래스

  • Primitive 타입의 박싱 오버헤드를 없애기 위한 배열
  • IntArray, ShortArray, ..
  • Array를 상속한 클래스들은 아니지만, Array와 같은 메서드와 프로퍼티를 가짐
  • size 등 유용한 멤버 함수 포함
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = 7
println(x.get(0)) // print 7
println(x[0])     // print 7
println(x.size)   // print 3

Array<Double> 배열에 제네릭을 사용하면 primitive type이 아니라 reference type이 사용된다..고 함. 메모리 문제인지, 말그대로 원시값/참조값 차이가 생기는게 문제인건지 모르겠지만 이런 현상을 없애기 위해 사용한다고 한다. 따라서 nullable하지 않은 내용이라면 IntArray 등의 클래스를 사용해 배열을 선언하는 것이 권장된다.

흠. JS에도 typed array가 있긴 하지만 같은개념인진 모르겠다. (typed array를 모름)

String

  • 문자열은 String 클래스로 표현
  • String은 characters로 구성됨
  • s[i]와 같은 방식으로 접근 가능 (immutable이라 변경 불가)
var x: String = "Kotlin"
println(x.get(0))
println(x[0])
println(x.length)

for (c in x) {
  println(c) // 한글자씩 가져오기 가능
}

문자열 리터럴

  • escaped string (“Kotlin”)

    • 전통적인 방식. Java String과 거의 비슷
    • backslash 사용해서 escaping 처리
  • raw string ("""Kotlin""")

    • escaping 처리 필요 없음
    • 개행이나 어떠한 문자든 포함 가능
val s = "Hello, world!\n"

val s = """
"'이것은 코틀린의
 raw String
 입니다.'"
"""

JS의 백틱이랑 비슷하지만.. 백틱은 이스케이프 문자가 먹힌다.


불행인지 다행인지 JS에서 보던 것들을 코틀린에서도 볼 수 있다. 쪼개져있던 언어들이 한 형태로 수렴중인 느낌. 이럴수록 나같은 주니어는 하던 언어를 더 깊이 파고 다른 언어를 접하는게 효율적이겠단 생각이 깊어진다.


Minchang Kim
Minchang Kim
웹/앱 개발자 김민창입니다! 좋은 하루 되세요!