1. Introduction

1.1 Hello, World!

 

코틀린에서는 function을 다음과 같이 선언할 수 있다.

fun start(): String = "OK"

String 을 반환형으로 사용하는, 파라미터를 받지 않는 start 함수다.

 

https://kotlinlang.org/docs/reference/basic-syntax.html#defining-functions

 

Basic Syntax - Kotlin Programming Language

 

kotlinlang.org

링크가 걸려있다. function 을 여러 방법으로 선언할 수 있다.

 

1.2 Java to Kotlin conversion

자바 코드를 자동으로 코틀린 코드로 바꿔주는 것을 보여주는 예제다.

그냥 JavaCode를 복사해서 붙여넣으면 끝이다. Java to Kotlin Converter의 힘을 감상하기만 하면 끝.

 

1.3 Named arguments

함수를 직접 써보는 예제다. 코틀린은 함수에 파라미터를 넘길 때, 파라미터 이름을 지정해서 넘길 수 있다.

이름을 지정 안하면 순서대로 넘어간다.

 

fun joinOptions(options: Collection<String>) = options.joinToString(", ", "[", "]")

 

코틀린의 Collection은 기본적으로 자바와 같다고 보면 된다. 여기서 더 편리한 기능 확장이 추가돼 있다.

콜렉션이 넘어오면, 인스턴스로 함수를 호출한다.  위와 같이 호출 할 수도 있고,

 

fun joinOptions(options: Collection<String>) = options.joinToString(separator = ", ",
prefix = "[",
postfix = "]")

 

이런 식으로도 호출 할 수 있다.

 

또한 함수를 선언 시에 Default Argument 를 사용할 수 있다.

 

fun 함수이름(a: Int = 1, b: Int = 2) = a + b

이런 식으로 해 놓으면 파라미터에 아무것도 넘어가지 않을 때, 기본 값을 사용한다.

 

"코틀린은 함수 선언시에 기본 값을 지정할 수 있다. 또한 함수 호출 시에 파라미터 이름과 함께 넘길 수 있다."

 

1.4 Default Arguemnts

위에 설명한, 기본 값 지정에 대한 문제이다. 나처럼 풀면 이렇게 된다.

 

fun foo(name: String, number: Int, toUpperCase: Boolean) =
(if (toUpperCase) name.toUpperCase() else name) + number


fun foo(name: String, number: Int) = foo(name, number, false)

fun foo(name: String) = foo(name, 42)

fun foo(name: String, toUpperCase: Boolean) = foo(name, 42, toUpperCase)

 

fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

하지만 코틀린은 "기본 값을 지정" 할 수 있다.

다음과 같이 간단히 작성 할 수 있다.

 

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false) =
(if (toUpperCase) name.toUpperCase() else name) + number


fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

 

1.5 Lambdas

람다는 자바에서도 8 버전에서부터 지원하는 것으로 알고 있다.

코틀린 또한 당연히 람다를 지원한다.

 

함수를 파라미터로 받는 함수를, Higher order Function이라고 한다.

이런 함수에, 파라미터를 넘겨줄 때 람다를 쓰면 아주 깨끗한 코드를 만들 수 있다. (사람마다 코드의 미적 기준은 다를 수 있다.)

 

아래가 그 예시이다.

 

// Lambdas are code blocks enclosed in curly braces.
items.fold(0, { 
    // When a lambda has parameters, they go first, followed by '->'
    acc: Int, i: Int -> 
        print("acc = $acc, i = $i, ") 
    val result = acc + i
    println("result = $result")
    // The last expression in a lambda is considered the return value:
    result
})

 

 

함수 타입은 다음과 같이 나타낸다.

(R, T) -> R

 

코틀린은 변수를 쓸 때  var a: Int = 1  이런 식으로 쓸 수 있다. 

함수 타입이 (R, T) -> R 과 같은 방식이란 말은

 

var sampleFunction: (Int, Int) -> String = {
a: Int, b: Int -> "the result is $a + $b"
}

print(sampleFunction(1, 2))

결과: the result is 3

 

요런 식으로 쓸 수 있다는 말이다.

 

코틀린은 특이한 문법이 있다.

가장 마지막에 들어가는 "함수 인자" 는 parenthesis 밖으로 뺄 수 있다. (paraenthesis = ( or ) )

 

이게 무슨 개소린가 싶기도 한데...

 

fun higherOrderFunctionDefault(a: Int, b: Int, f: (Int, Int) -> Int): Int {
return f(a, b)
}

var result = higherOrderFunctionDefault(1,2, {a: Int, b: Int -> a + b})

print(result) -> 결과는 3이 나온다.

 

위와 같은 경우에.

 

var result = higherOrderFunctionDefault(1,2, {a: Int, b: Int -> a + b})

이 부분을

 

var result = higherOrderFunctionDefault(1,2){a: Int, b: Int -> a + b}

 

이렇게 쓸 수 있다는 말이다. 심지어 IntelliJ Edu 에서는 뒷 부분을 더 선호한다. ??? 

 

1.6 Strings

이건 그냥 답 봤다. 도저히 못 찾겠다.

String Template 에 관련된 문제인데... Docs 링크가 걸려있긴 하지만, 전혀 몰랐던 Template 적용 방식이 있었다.

자바 때부터 전해내려온 것인지, Kotlin에만 있는 것인지조차 잘 모르겠다. 이 부분은 좀 약하다.

 

1.7 Data Classes

class 앞에 data 를 붙이면, lomobk 의 @Data 와 비슷한 작동을 한다.

 

equals/hashCode, toString

 

함수를 자동으로 만들어 준다.

 

문제 자체는 간단하지만, 여기서 Kotlin의 클래스 개념을 잡고 넘어가야 한다.

 

오른쪽에 있는 링크를 따라가 차분히 읽어보도록 하자.

많은 것들을 다 적을 순 없고 간단히 특이한 점만 정리해 놓겠다.

 

1. 코틀린의 클래스 안의 필드들은, 반드시 초기화 돼야 한다.

2. main counstructor  라는 놈이 있다.

3. 일단 상속되는 놈이 먼저 생성되는건 자바와 똑같다. 따라서 주의할 점도 똑 같다.

4. 클래스 안에서의 문(Statement) 들은 무조건 순서대로 시작된다. 단 constructor는 예외 케이스가 있다.

   main constructor 가 있다면, 클래스 안쪽에 선언하는 놈들은 다 secondary constructor 인데,

   constructor keyword 를 이용해서 생성자를 추가할 수 있다. 이 때 반드시, main constructor을 this(name) 이런 식으로 호출해 줘야한

   다... 

ex)

class Constructors(var a: Int = 3) {
	constructor(i: Int, b: Int): this(i) {
  		println("Constructor")
	}
	init {
  		println("Init block")
	}
}

따라서 위와 같은 경우 init 이 먼저 시작된다. secondary constructor 보다는 init 이 우선시 된다.

main constructor 가 없다? 없을 수는 없다. 사실 암시적으로 갖고 있는 것이다. 마치 자바의 기본 생성자가 있듯이... 만약 main constructor 가 없다면, 암시적으로 호출하고 있다고 생각해야 한다.

 

그렇다면 순서대로라는 것은 무슨 의미인가?

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

위와 같은 경우 반드시 순서대로 시작된다.  (위의 also 는 kotlin 의 확장 함수 중 하나이다. 자기 자신을 매개변수로 넘기고, 자기 자신을 return 한다.

val person: Person = getPerson().also {
	print(it.name)
	print(it.age)
}

마지막엔 자동으로 자기 자신을 return 한다.

 

5. main constructor 에는 연산을 넣을 수 없다. 따라서 연산을 위해서는 클래스 안에 init 을 사용하도록 해라.

6. class 는 클래스 : 부모클래스() 와 같이 상속. interface  는 클래스: 언터페이스 와 같이 상속한다.

7. 상속 하려면 open 키워드를 붙여야 한다.

 

다른게 무지 많다. 자세히 알아보지도 않고 대충 정리만 해놨다. 무엇보다 1번이 가장 애매한데... 초기화 안하면 컴파일러에서 빽빽돼서 저리 적어 놓았다.

 

1.8 Nullable Type

꽤나 중요한 문제이다.

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
){
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

위가 정답이다. 깔끔하고 멋있다. kotlin 의 ? 의 힘이다.

자료형에 ? 가 붙어있는 것을 볼 수 있는데, null 이 들어갈 수도 있는 자료형을 나타낸다.

인스턴스에 ? 를 붙인뒤, 인스턴스의 자료 혹은 함수에 접근하려고 하면, null 을 자동으로 체크해준다.

만약 접근하려는 놈이 null 일 경우 그냥 null 을 반환한다. 그래서 마지막에 email != null 을 체크하는 것이다.

 

처음에 if 문을 하나만 써서 해결하라는 문구를 보고 나는 다음과 같이 문제를 해결했다.

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
){
    if(client != null
            && message != null
            && mailer != null
            && client.personalInfo != null
            && client.personalInfo.email != null)
        mailer.sendMessage(client.personalInfo.email!!, message!!)
}

 굳이 이 흉한 코드를 올려놓은 이유는, !! 또한 키워드 중 하나이기 때문이다. ? 자료형의 변수에, !! 를 뒤에 붙여주면.

"이 값은 null 일리가 없다!" 라는 것을 의미한다. 따라서 String? 자료형의 변수는 String 으로 변해서 파라미터로 전달되는 효과가 있다.

 

1.9 smart casts

 

제목 그대로 "똑똑한 형변환" 을 의미한다. 일단 kotlin 에서는 if 문과 when 문을 사용해서 변수에 값을 할당할 수 있다는 것을 알고 넘어가자.

var a = 3;

var b = if(a == 3) 4 else 5 

이런 식으로 쓸 수 있다는 말이다. (많이 신기함..)

 

위에서는 If문과 When 문이라고 했지만.. Kotlin 에서의 If 는 "Expression" 이다.

이게 뭔 의미냐면, 반환형이 있다는 말이다.

If 표현의 마지막 문장은 자동으로 "반환 값" 이 된다.

 

따라서 Kotlin 에는 ? a : b 표현이 없다. If 로 충분하기 때문이다.

 

when 은 Java에서의 Switch 랑 비슷한데 여러모로 다른 점이 있다. 

fun eval(expr: Expr): Int =
        when (expr) {
            is Num -> expr.value
            is Sum -> eval(expr.left) + eval(expr.right)
            else -> throw IllegalArgumentException("Unknown expression")
        }

위를 보면 알 수 있듯이, when 은 is 로 타입을 확인 할 수 있다. 자바로 따지면 instance of 같은 느낌이다.

왜 "똑똑한 형변환" 일까?

위에서 Expr 이 인터페이스라고 하자. 인터페이스를 구현한 클래스 A : Num , B : Sum 이 있다.

그럼 eval 함수에는 A 도, B 도 들어갈 수 있겠지? 

 

이 때 when 의 is 로 타입을 구분해서, 사용할 수 있다는 말이다.

 

잘 보면 expr.value, expr.left expr.right 라고 저녛 다른 것들을 사용 하는 것을 볼 수 있다.

 

interface 가 when 절의 is 를 통과했을 때, "자동으로" is 에서 비교한 타입으로 형변환 해서 넘어간다.

따라서 is Num 을 통과한 녀석은 Num 자료형으로 자동으로 변경돼서 들어간다.

똑똑하다는 말이다.

 

when의 is 는 타입을 비교할 때 쓴다, 보통은 왼쪽에 "조건식" 이 들어간다.

 

when (num) {

  1 -> "하나"

 2 -> "둘"

else -> "둘 초과"

}

이런 식으로 사용할 수 있다.

 

만약 when 에 아무런 인자도 넘어가지 않는다면, 그냥 "순수한 조건식" 만을 이용해서 사용할 수도 있다.

 

var a = 3

 

when {

  a == 3 -> "a는 3이 맞어"

  else -> "a 는 3이 아니여"

}

이런 식으로 말이다.

물론 위와 같이 쓸 필요는 없다. 위와 같으 2개의 분기로 나눠 질때는 if를 쓰면 된다.

 

3가지 이상의 분기로 나눠질 때 when을 사용하도록 하자.

 

1.9 Extension Function

 

확장 함수를 의미한다. 클래스의 확장 함수를 부여 할 수 있다.

그냥 클래스 안에다가 함수 만드는 것은 확장 함수가 아니다.

 

1.10 Object Expression

코틀린에서는 익명 클래스를 어떻게 사용할 수 있을까?

 

object: 인터페이스{

 override fun 함수이름() {}

}

    Collections.sort(arrayList, object: java.util.Comparator<Int> {
        override fun compare(o1: Int?, o2: Int?): Int {
            if(o1!! > o2!!) return -1 else if(o1 < o2) return 1 else return 0
        }
    })

 

https://kotlinlang.org/docs/reference/object-declarations.html

 

Object Expressions, Object Declarations and Companion Objects - Kotlin Programming Language

 

kotlinlang.org

위의 object 표현식을 활용 하도록 하자.

주의 해야할 점은, 이 anonymous object 의 scope 이다.

anonymous object는 private이나 local 에서만 사용 가능하다.

 

public 으로 노출되면, 이 object는 순식간에 Any? 타입으로 바뀌어 버린다.

(Kotlin 에서의 Any 는 Java에서의 Object와 비슷한 맥락이지만. 절대 같지는 않다.)

 

class C {
    // Private function, so the return type is the anonymous object type
    private fun foo() = object {
        val x: String = "x"
    }

    // Public function, so the return type is Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // Works
        val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
    }
}

 

1.11 SAM Conversion

 

SAM Conversions

Just like Java 8, Kotlin supports SAM conversions. This means that Kotlin function literals can be automatically converted into implementations of Java interfaces with a single non-default method

 

1.12 Extension On Collection

 

문제 자체는 아주 간단하다. 

 

제목 그대로 Collection을 어떻게 확장해서 사용하고 있는지를 알려준다.

 

차트를 보면 3단원에 제대로 Collection을 다룰 예정인듯 하니 기다리도록 하자.

 

 

 

 


 

이렇게 1단원을 끝냈다.

굳이 정리할 필요는 없겠지만, 기본 문법이니 만큼 적어두는 것이 좋다고 생각했다.

 

이 포스트의 내용 만으로는 완벽히 Kotlin을 이해하기 힘들다.

글을 쓰고 있는 사람부터가 이해하고 쓰는게 아니고, 이해하기 위해, 혹은 이해하고 나서 훗날 기억하기 위해 적고 있기  때문이다.

 

화이팅 하자.

블로그 이미지

맛간망고소바

,