Kotlin – 시작하기

공식 문서 요약/번역/정리 중

기본 문법

패키지 정의하기

패키지 정의는 소스 코드 맨 윗줄에 적는다. 패키지 경로와 소스 코드의 파일 경로는 꼭 일치하지 않아도 된다

package my.demo

import java.util.*

//...

함수 정의 하기

// Int 타입의 a, b 변수를 인자로 받아서 Int 타입을 리턴하는 함수의 정의
fun sum(a: Int, b:Int): Int {
    return a + b
}

// 리턴 타입의 추론
fun sum(a:Int, b:Int) = a + b

// Void == Unit, Unit은 생략할 수 있다
fun printSumb(a: Int, b: Int):Unit {
    print("sum of $a and $b is ${a+b}")
}

지역 변수의 선언

// val -> Immutable, var -> Mutable
val a: Int = 1
var b = 2       // Int 타입 추론
val c: Int      // val로 선언했는데 바로 초기화하지 않음 -> 오류

String template

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

Conditional expression

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

null

명시적으로 null 허용이라고 마킹을 해줘야 null을 assign할 수 있다

//문자열 str에 integer값이 포함되어 있지 않으면 null을 리턴하는 함수
fun parseInt(str: String): Int? {
    ...
}

함수에서 어떤 값도 리턴하지 않으면 null을 리턴한다

타입 체크와 자동 캐스팅

is 연산자 -> 특정 타입의 인스턴스인가?

immutable의 지역 변수가 어떤 타입인지 체크를 했다면 명시적으로 캐스팅할 필요가 없다

fun getStringLength(obj: Any): Int? {
    if(obj is String) {
        // 앞에서 String 타입임을 체크했기 때문에 
        // obj를 명시적으로 String으로 cast할 필요가 없다
        return obj.length;      
    }
    // if 블럭을 벗어났기 때문에 이 지점에서 obj의 타입은 Any다
    return obj
}

// 혹은...

fun getStringLength(obj: Any):Int? {
    if(obj !is String) return null

    // obj가 자동으로 String 타입으로 casting된다
    return obj.length
}

// 심지어는...
fun getStringLength(obj: Any):Int? {
    // 역시나 if 블럭에서는 obj가 String으로 자동 캐스팅된다
    if(obj is String && obj.length > 0) {
        return obj.length
    }
    return null
}

for loop

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

// 또는

val items = listOf("apple", "banana", "kiwi")
for(index in items.indices) {
    println("item at $index is ${item[index]}")
}

while loop

// while loop
val items = listOf("apple", "banana", "kiwi")
var index = 0
while(index < item.size) {
    println("item at $index is ${item[index]}")
    index++
}

when expression

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

ranges

//어떤 숫자가 특정 범위에 포함되어 있는지 체크
val x = 10
val y = 9
if(x in 1..y+1) {           // x가 1 ~ y+1 사이의 숫자이면
    println("fit in range")
}

// 숫자가 범위에 포함이 안되면
val list = listOf("a", "b", "c")
if(-1 !in 0..list.lastIndex) {
    println("-1 is out of range")
}
if(list.size !in list.indices) {
    println("list size is out of valid list indices range too")
}

// for loop using range
for(x in 1..5) {
    print(x)        // print 1,2,3,4,5
}

// step
for(x in 1..10 step 2) {
    println(x)      // print 1,3,5,7,9
}

collections

// iteration
for(item in items) {
    println(item)
}

// collection에 아이템이 있는지 체크
when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

// lambda function을 이용한 collection의 map과 filter
// it 라는 변수로 접근 가능
fruits
    .filter { it.startsWith("a") }
    .sortedBy { it }
    .map { it.toUpperCase() }
    .forEach { println(it) }

Idioms

DTO 생성 (data class)

data class Customer(val name: String, val email: String)

data 클래스는 다음과 같은 기능을 제공한다

  • 모든 프로퍼티에 대한 getters (var로 선언된 변수의 경우 setter도 제공)
  • equals()
  • hashCode()
  • toString()
  • copy()
  • 모든 프로퍼티에 대해 component1(), component2()

함수 파라미터의 기본 값

    fun foo(a: Int = 0, b: String = "") {
        ...
    }

리스트 필터

    val positives = list.filter { x -> x > 0}

    // 혹은 더 간단히

    val positives = list.filter { it > 0 }

문자열 인터폴레이션

    val name = "..."
    println("Name $name")

인스턴스 체크

    when(x) {
        is Foo -> ...
        is Bar -> ...
        else -> ...
    }

맵/리스트의 요소 탐색

    for((k, v) in map) {
        println("$k -> $v")
    }

Range

    for(i in 1..100) {...}          // closed range: 100 포함
    for(i in 1 until 100) {...}     // half open: 100 불포함
    for(x in 2..10 step 2) {...}
    for(x in 10 downTo 1) {...}
    if(x in 1..10) {...}

읽기 전용 리스트

    val list = listOf("a", "b", "c")

읽기 전용 맵

    val amp = mapOf("a" to 1, "b" to 2, "c" to 3)

맵에 접근

    println(map['key'])
    map['key'] = value

지연 초기화

    val p: String by lazy {
        ...초기화 연산
    }

확장 함수

    fun String.spaceToCamelCase() {
        ...
    }

    "Convert this to camelcase".sapceToCamelCase()

싱글톤

    object Resource {
        val name = "Name"
    }

if not null

    val files = File("Test").listFiles()
    println(files?.size)

if not null else

    val files = File("Test").listFiles()
    println(files?.size ?: "empty")

not null이면 실행(1)

    val data = ...
    val email = data["email"] ?: throw IllgalStateException("Email is missing!")

not null이면 실행(2)

    val data = ...
    data?.let {
        ... 실행할 코드
    }

when을 이용한 리턴

    fun transform(color: String): Int {
        return when(color) {
            "Red" -> 0
            "Green" -> 1
            "Blue" -> 2
            else -> throw IllegalArgumentException("Invalid color param")
        }
    }

try / catch 표현

    fun test() {
        val result = try {
            count()
        } cath(e: ArithmeticException) {
            throw IllegalArgumentException(e)
        }

        ... working with result
    }

if 표현식

    fun foo(param: Int) {
        val result = if(param == 1) {
            "one"
        } else if(param == 2) {
            "two"
        } else {
            "three"
        }
    }

Unit을 리턴하는 메서드의 빌더 스타일의 사용

    fun arrayOfMinusOnes(size: Int): IntArray {
        return IntArray(size).apply { fill(-1) }
    }

한 줄짜리 함수 표현

    fun theAnswer() = 42

    // 같은 함수
    fun theAnswer():Int = {
        return 42
    }

이 방식은 when 표현식 등과 함께 사용되어 더욱 효과적으로 사용할 수 있다

fun transformer(color: String): Int = when(color) {
    "Red" -> 0
    "Green" -> 1
    "Blue" -> 2
    else -> throw IllegalArgumentException("Invalid color param")
}

하나의 객체에 대해서 복수의 메서드를 호출하기 (with)

class Turtle {
    fun penDown()
    fun penUP()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) {
    penDown()
    for(i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}

Java 7의 try with resources

val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use {
    reader -> println(reader.readText())
}

Generic 함수

//  public final class Gson {
//     ...
//     public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
//     ...

inline fun <reified T: Any> Gson.fromJson(json): T = this.fromJson(json, T::class.java)

Nullable 변수의 이용

val b: Boolean? = ...           // b -> Nullable
if(b == true) {
    ...
} else {
    ... b가 null이거나 false일 때
}

Coding convention

Kotlin의 코딩 스타일에 대한 내용

Naming style

확실하지 않은 것은 기본적으로 자바의 코딩 스타일을 따른다. 예를 들면

  • camelCase 사용 (언더바 사용 지양)
  • 타입(클래스, 인터페이스)는 대문자로 시작한다
  • 메서드와 프로퍼티는 소문자로 시작한다
  • indent는 스페이스 4칸
  • 공개 함수는 Kotlin Doc등의 포맷으로 문서화 해야한다

콜론

타입과 슈퍼 타입을 구분하는 콜론에는 콜론 앞에 스페이스를 포함시키고 인스턴스와 타입을 구분하는 콜론에는 콜론 앞에 스페이스를 넣지 않는다

interface Foo<out T : Any> : Bar {      // T : Any, _: Bar
    fun foo(a: Int): T                  // a: Int
}

람다

람다 함수를 사용할 때 curly brace ({}) 앞 뒤로 스페이스를 사용하고 마찬가지로 람다 함수의 파라미터와 함수 본문을 구분하는 화살표에도 앞뒤로 스페이스를 사용한다. 필요하다면 언제라도 람다 함수는 괄호 밖으로 전달될 수 있어야 한다.

list.filter { it > 10 }.map { element -> element * 2 }

람다 함수가 짧고 중첩된 구조가 아니라면 파라미터 변수를 명확하게 선언하는 것 보다는 it 컨벤션을 따르는 것을 추천한다. 그러나 중첩된 람다 함수 내에서는 파라미터 변수의 이름을 명시적으로 사용해야 한다

클래스 헤더 포맷

인지가 많지 않은 클래스의 선언은 한 줄로 한다

class Person(id: Int, name: String)

긴 클래스 헤더는 포맷팅을 통해 primary constructor의 인자들이 줄바꿈된 상태로 사용해야 한다. 또한 닫는 괄호 역시 줄바꿈해서 입력한다. 만약 상속을 사용한 경우라면 부모 클래스의 생성자나 인터페이스의 이름을 닫는 괄호와 같은 줄에 적는다.

class Person(
    id: Int, 
    name: String,
    surname: String
) : Human(id, name) {
    // ...
}

여러 개의 인터페이스를 구현하는 클래스의 경우는 각 인터페이스 이름을 줄바꿈해서 입력한다

class Person(
    id: Int, 
    name: String,
    surname: String
) : Human(id, name),
    KotlinMaker {
    // ...
}

생성자의 인자들은 한 줄로 사용하거나 줄바꿈해서 사용할 수 있다

Unit

함수가 Unit을 리턴한다면 함수 정의에 리턴 타입을 생략할 수 있다

fun foo() {     // :Unit이 생략됨

}

함수 vs 프로퍼티

몇몇 경우에 인자 없는 함수는 읽기 전용 프로퍼티와 호환 가능하다. 의미는 유사하지만 때에 따른 스타일적인 선호가 존재한다

다음과 같은 경우 함수보다 프로퍼티를 사용하는 것이 선호된다

  • 예외를 던지지 않는 경우
  • O(1) 복잡도를 갖는 경우
  • 계산 비용이 낮을 때 (혹은 첫 번째 실행 결과를 캐시되었을 때)
  • 여러 번 호출해도 동일한 값을 리턴할 때

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중