오늘은 코틀린을 마지막으로 배워보자. 오늘 이 시간에는 뭘 딱 정해서 배우는 것이 아니라 이것저것 빠진 것이나 기타 여러가지들을 배워보자.

Operator Overloading

말 그대로 연산자 오버라이딩이다. 우리가 흔히 아는 연산자가 맞다. 그 연산자를 다시 재정의 할 수 있다. 우리는 코드로 보는게 더 좋지 않는가? 코드를 보자.

class Account(val age: Int) {
    operator fun plus(account: Account): Account {
        return Account(age + account.age)
    }

    operator fun minus(account: Account): Account {
        return Account(age - account.age)
    }

    //...
    override fun toString(): String {
        return "$age years old"
    }
}

위와 같은 코틀린 코드가 있다고 가정하자. 단지 예제일 뿐이니. operator라는 키워드를 함수에 적용하고 plus(), minus()를 정의하였다. 그럼 사용법은 다음과 같다.

println(Account(10) + Account(20))
println(Account(20) - Account(10))

object 그대로 연산을 할 수 있다. 함수를 호출 하는 것이 아니라 연산자를 사용하여 숫자, 문자 마냥 object을 연산 할 수 있다. 물론 아무 함수나 되는 것은 아니다. 정해져 있는 함수만 가능하다. 이외에도 *(곱), /(나누기), %(나머지), ++(증감), --(감소) 등 여러가지가 더 있다. 꽤 많은 연산자들을 재정의 할 수 있다.
물론 plus 함수를 직접 사용해도 상관은 없다.

println(Account(10).plus(Account(20)))

c++ 에도 연산자 오버라이딩이 있던걸로 기억하는데 쓰읍.. 노기억. 아무튼 연산자를 object에 사용할 수 있다.

invoke

코틀린에는 invoke 라는 특이한 함수가 존재 한다. 이것은 스칼라에도 있다. 스칼라와 잠시 비교해보자.
일단 스칼라코드를 보자.

object Foo {
  def apply() = println("hello world")
  def apply(str: String) = println(s"hello $str")
}

스칼라도 object라는 키워드가 존재 한다. 우리는 코틀린에서 object를 잠시 배웠었다. 코틀린과 비슷하게 이것 또한 싱글톤이다.
보통 함수를 호출할 때는 함수의 이름을 사용해서 호출해야 된다. 예를들어 다음과 같다.

object Foo {
  def hello() {
    println("hello")
  }
}

위의 코드에서 hello() 함수를 사용하려면 우리는 다음과 같이 작성해서 사용해야 된다.

Foo.hello()

hello() 함수를 사용하려면 hello() 메서드를 명시적으로 호출해 줘야 hello() 메서드에 접근할 수 있다. 하지만 스칼라의 apply()란 메서드를 사용할 때에는 그럴 필요 없다.

Foo()
Foo("world")

위의 코드는 명시적으로 apply() 라는 함수를 호출 하지 않아도 자동으로 호출 되어진다. 특이한 함수이다. 스칼라와 마찬가지로 코틀린에도 apply() 와 같은 메서드가 존재한다. 위의 제목에서 보다시피 invoke() 라는 함수이다. 한번 만들어 보자.

object Foo {
    operator fun invoke() = println("hello world")
    operator fun invoke(message: String) = println("hello $message")
}

스칼라에서는 그냥 apply() 메서드를 일반 메서드 처럼 만들어도 되지만 코틀린에서는 operator 키워드를 사용해야 한다. 왜 그냥 되도록 만들지 않았지? 흠..
위의 코드도 동일하게 함수명을 명시적으로 호출하지 않아도 된다.

Foo()
Foo("world")

스칼라와 동일하다. 물론 스칼라와 코트린 모두 함수호출을 명시적으로 해줘도 상관은 없다.

//코틀린
Foo.invoke()

//스칼라 
Foo.apply()

잠시 스칼라와 몇가지만 비교해보자.

Data class vs Case class

코틀린에 data class라는 것이 존재한다. 우리는 data class를 아주 잠깐 배웠었다. 스칼라에도 data class와 비슷한 case class라는게 존재한다.
아래는 코틀린의 data class 이다

data class Person(val id: Long, val name: String)

다음은 스칼라 코드이다.

case class Person(id: Long, name: String)

문법 역시 비슷하다. 하지만 스칼라에서는 valvar 키워드를 명시적으로 써주지 않아도 되며 기본값으로 val로 되어 있다. 한마디로 아무 선언을 하지 않는다면 setter는 생성되지 않는다. 스칼라 역시 hashCode, equals, copy, getter, setter, toString 등등 자동으로 생성해 준다.

아래는 코틀린으로 data class를 호출할 때이다.

val person = Person(1, "wonwoo")
println(person)

//Person(id=1, name=wonwoo)

다음으로는 스칼라의 case class를 호출할 때이다.

val person = Person(1, "wonwoo")
println(person)

//Person(1,wonwoo)

문법역시 거의 비슷하다. 이건 똑같다. 필자가 스칼라 코드로 만들고 그냥 복붙해서 코틀린에 코드에 넣었는데 아무 이상없이 잘 된다.

When vs Pattern Matching

코틀린의 when도 우리는 살짝 배웠었다. 물론 When절도 괜찮다. 자바의 switch case문은 저리가라 할만큼 괜찮다. 하지만 스칼라의 Pattern Matching이 좀 더 강력한 것 같다.
아래의 코드는 코틀린의 when절이다. person의 id가 1이면 id와 name을 출력해주고 그렇지 않다면 nothing을 출력한다.

when {
    person.id == 1L -> println("id: 1, name: ${person.name}")
    else -> println("nothing")
}

위의 코드도 나쁘지 않다. 이런걸 할 수 있는 자체가 나쁘지 않다.
하지만 스칼라의 case class를 사용하면 바로 비교도 가능하고 그 안의 원소를 간단하게 비교도 할 수 있다.

person match {
  case Person(1, name) => println(s"id: 1, age : $name")
  case _ => println("nothing")
}

위의 코드는 person object에서 id가 1이면 id와 name을 출력한다. name은 상관없다. name은 아무거나 와도 된다. 하지만 id는 무조건 1이 와야 해당 문자를 출력한다. 원소를 굳이 person에서 꺼내지 않고 바로 사용해도 되서 더욱 간편한듯 싶다.
스칼라도 좋지만 코틀린도 그렇게 나쁘지 않다. 자바에 비교하면 훨씬 좋아 보인다.

Lambda Parameter

며칠 전에 배웠던 Lambda Parameter도 비교해보자. 코틀린에서 단일 파라미터를 좀 더 간편하게 쓸 수 있는 it이라는게 존재 하였다. 예를들어 1~5까지의 자연수중에 2보다 큰수를 2배하는 코드를 코틀린 코드로 작성한다고 가정하자. 그럼 아래와 같이 작성 할 수 있다.

val list = mutableListOf(1, 2, 3, 4, 5)
println(list.filter { i -> i > 2 }.map { i -> i * 2 })

원래는 이런 코드가 들어가겠지만 우리는 저번에 배웠던 it라는 키워드?를 사용해서 좀 더 깔끔하게 만들 수 있었다.

val list = mutableListOf(1, 2, 3, 4, 5)
println(list.filter { it > 2 }.map { it * 2 })

코틀린의 it 같이 스칼라에도 it가 동일한 키워드?가 존재 한다. 아래는 스칼라 코드이다.

val list = List(1,2,3,4,5)
println(list.filter(i => i > 2).map(i => i * 2))

원래는 위의 코드처럼 사용해도 되겠지만 코틀린의 it과 비슷한 개념으로 _가 존재 한다. 위의 코드를 _ 형태로 바꿔보자.

val list = List(1,2,3,4,5)
println(list.filter(_ > 2).map(_ * 2))

코틀린과 문법자체는 비슷하다. 키워드만 다를뿐이지 사용법은 동일하다.

우리는 이렇게 코틀린에 대해서 살펴봤다. 아직 익숙하지 않기에 좀 더 공부를 헤야 할 것 같다. 하지만 좀 더 공부를 할 지 하지 않을지는 미지수다. 그래도 잠깐이나마 자바를 벗어나 다른언어도 살펴보니 좋은 기능이 많은 것 같다. 예전에도 잠깐 스칼라를 공부했었는데 조금 공부하다가 그만둬서 기억이 가물가물하고 그때는 함수형 언어가 익숙치도 않아 많이 어려웠다. 이제는 코틀린을 공부해봤으니 그때보다는 조금 쉬워졌겠지? 코틀린을 사용하지는 않더라도 함수형 언어에 한발자국 다가간 느낌이다. 요즘 또 GO언어가 급부상하면서 언어 사용률도 몇십계단이나 올라갔던 뉴스?를 본게 기억난다. 다음에는 GO를 잠깐 살펴볼까?ㅎㅎㅎㅎ 흠흠 그러기엔 아직 역량이 부족하다. 아주아주 나중에 기회가 된다면 살펴봐도 나쁘지 않을 것같다. 아직 한국에서 자바로 먹고 살만큼 자바 사용률이 높다. (물론 세계적으로도 높긴 하다.)향후 10년은 거뜬하지 않을까 싶다. 하지만 시대가 어떻게 바뀔지 모르기에 다른언어 한개정도는 완벽하게 하지는 못해도 알아두면 좋을 것 같다.

이상으로 코틀린에 대해서 간단하게나마 알아봤다. 끝!