코틀린
Kotlin,
통합 개발환경인 인텔리 J로 유명한 제트브레인에서 내놓은 프로그래밍 언어. 자바 가상머신 위에서 동작하며 자바와 100% 호환된다는 것이 제트브레인 측의 설명이다. 즉 자바가 돌아가는 환경이라면 코틀린도 돌아간다. 그 대표적인 예가 안드로이드로, 지금도 코틀린을 사용해서 안드로이드 프로그래밍을 하는 것이 가능하며[1] 안드로이드 스튜디오 3.0부터는 아예 자바와 함께 코틀린도 지원한다. 물론 자바 라이브러리도 제약 없이 갖다 쓸 수 있다. 자바 때문에 오라클과 계속해서 분쟁을 겪고 있는 구글로서는 하나의 대안으로 부상하고 있는데, 한때 스위프트로 돌아선다는 얘기도 있었지만 그럴 가능성은 거의 사라졌고, 일단 자바와 100% 호환되는 코틀린을 지원함으로써 개발자들을 이쪽으로 유도한 후 자바 가상머신을 대체해서 LLVM과 같은 방식으로 코틀린을 네이티브 지원하고 자바는 자연스럽게 놓지 않겠느냐는 전망이 대두되고 있다.
특징
간결함
자바에 비해서 훨씬 간결하게 코드를 짤 수 있으며, 현대 프로그래밍의 여러 개념들을 적극 수용했다. 실제로 똑같은 일을 하는 프로그램을 자바와 코틀린으로 짜 보면 코틀린의 코드 양이 훨씬 적다. 예를 들면 아래의 코드는 단 한 줄로 name, email, company 세 개의 필드를 가지고 이에 대한 getters와 setters는 물론 equals(), hashCode(), toString() 그리고 copy()까지 모두 지원하는 POJO 클래스를 정의한다.
data class Customer(val name: String, val email: String, val company: String)
이걸 자바로 짜려면 몇 줄이나 나올까? 말도 하지 말자. 그런데 자세히 보면 변수 혹은 매개변수를 정의하는 방법이 자바와 많이 다르다. val이라는 키워드가 먼저 나오고, 변수 이름이 나온 다음 콜론을 찍고 변수의 타입을 쓴다. 변수 타입 → 이름 순서인 자바[2]와는 반대다. [3] 다만 약간 헷갈릴 수 있는 부분이 있는데, 매개변수의 이름에 val이나 var를 사용하면 이를 프로퍼티로 간주하여 클래스 안에 변수는 물론 getter와 setter도 만들어주는 반면, val이나 var가 없으면 그냥 생성자(constructor)에 쓸 매개변수로 취급한다.
또한 과도하게 엄격한 예외 처리를 강요하는 자바와는 달리 모든 예외 처리를 강제하지 않는다. 모든 예외를 다 프로그래밍으로 처리함으로써 오류 문제에 단단한 코드를 만들려고 했던 게 자바지만 지나친 엄격함에 시달리다 못해 그냥 안 받아도 되는 RuntimeException 같은 걸로 다 때워버리든가 해버려서 오히려 프로그램을 부실하게 만드는 자바의 문제점을 해소한 셈이다.
자바와는 달리 줄 끝에 세미콜론을 쓰지 않는 것도 차이. 요즘 인기 있는 언어들인 파이썬, Go(응?), 스위프트도 역시 줄끝에 세미콜론을 쓰지 않는다.
안전함
코틀린이 강조하는 또 하나의 특징은 안전함이다. 코틀린은 null 값이 들어가도 되는 변수(nullable)와 그렇지 않은 변수를 구분한다. 기본은 nullable이 아니며 nullable로 지정하려면 변수의 타입 뒤에 ? 기호를 붙여 줘야 한다. 이로 인해 null 값을 잘못 사용해서 생길 수 있는 널 참조 문제[4]를 컴파일 단계에서 더 많이 잡아준다.
var output: String
output = null // Compilation error
val name: String? = null // Nullable type
println(name.length()) // Compilation error
현대적
최근에 유행하는 고차함수나 클로저, 람다식 같은 개념들을 지원한다. 자바도 버전 8부터는 클로저나 람다식을 지원하지만 사용하려면 좀 복잡한데, 코틀린으로 하면 훨씬 간단하다.
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
고차함수의 예. lock 함수의 두 번째 매개변수는 body: () -> T
로 되어 있는데, 이는 매개변수가 없고 T 유형의 반환값을 가지는 함수를 매개변수로 받는다는 뜻이며, 함수 안에서는 이를 body
로 참조해서 실행시킬 수 있다.
기본 문법
일단 기본적인 문법은 다음과 같다.
변수 선언
변수를 선언하려면 var, 또는 val 키워드를 사용한다. val은 한번 선언하고 초기값을 부여하면 변경할 수 없는, 즉 읽기만 가능한 변수로 자바의 final과 같은 기능을 한다. 반면 var는 값을 변경할 수 있는 변수다. 가장 기본이 되는 형식은 다음과 같다.
// val [변수 이름]:[변수 타입] = [대입할 값]
// var [변수 이름]:[변수 타입] = [대입할 값]
var num: Int
val num: Int = 1
하지만 대입값에 따라서 추측하는 기능이 있어서 타입을 안 쓸 수도 있다.
var name = "John Doe" // name은 자동으로 String 타입이 된다.
name = 1 // Error!
var unknown // Error!
주의할 것은, 파이썬처럼 name에 다른 타입의 값을 집어넣을 수 없다. 코틀린은 자바와 마찬가지로 정적 타이핑 언어다. 선언 시점에서 초기 대입값에 따라 변수 타입이 결정되므로 다른 타입의 값을 넣으려고 하면 컴파일 단계에서 오류가 난다. 또한 변수를 선언만 하고 초기 대입값이 없으면 타입을 추정할 수 없으므로 이 역시 에러가 난다. 변수를 선언할 때 값을 대입하지 않을 거라면 타입을 반드시 지정해 줘야 한다.
기본적으로 코틀린의 변수에는 null을 대입할 수 없다. 어떤 변수에 null을 대입할 수 있게 하려면, 즉 nullable로 선언하려면 반드시 타입 뒤에 ? 표시를 붙여줘야 한다.
var he: String = "John Doe" // non-nullable
var she: String? = "Jane Doe" // nullable
he = null // Error!
she = null // Okay.
물론 기본값이 nullable이 아니라는 것은 될 수 있으면 nullable을 쓰지 말라는 것이다. 자바와 호환성 때문에 안 쓸 수 없다든가 하는 어쩔 수 없는 때에만 쓰도록 하자.
함수 선언
함수를 선언하려면 fun 키워드를 사용한다. 함수는 즐겁다. 변수 선언 때처럼 반환값 타입을 함수 매개변수 정의 뒤 : 다음에 써야 한다.
fun sum(a: Int, b: Int): Int {
return a + b
}
// 위 함수를 다음과 같이 축약할 수도 있다.
fun sum(a: Int, b: Int) = a + b
코틀린은 함수 선언에서만이 아니라 여기 저기서 중괄호 쓸 일이 많이 줄어든다.
변수와 마찬가지로 함수도 null 값을 반환값으로 쓰려면 함수 정의의 반환값 타입 뒤에 ?를 붙여줘야 한다.
클래스와 객체
클래스 정의하기
클래스 정의는 자바와 비슷하다.
class Sample {
}
그런데 상속 관련 문법은 extends를 안 쓰고 : 기호를 쓰기 때문에 오히려 C++나 C#와 비슷해 보인다.
open class Base(num: Int)
class Derived(num: Int) : Base() {
}
코틀린에서 모든 클래스는 기본값이 자바의 final이다. 즉 상속 불가다. 상속을 허용하려면 open 키워드를 써 줘야한다. 즉, 자바는 final로 지정하지 않으면 어떤 클래스는 상속 가능이지만 코틀린은 반대로 open으로 지정하지 않으며 어떤 클래스든 상속 불가다.
object
자바와 구별되는 것으로는 클래스가 아니라 객체를 정의해 버리는 것도 가능하다. 이게 무슨 뜻이냐 하면, 싱글톤 클래스, 다시 말해서 이 클래스를 이용한 객체(인스턴스)가 딱 하나만 존재하는 클래스를 정의할 수 있는 것.
object Singleton {
var count = 0;
fun addCount(delta: Int = 1) {
count += delta;
}
}
object 이름이 꼭 Singleton일 필요는 없다. 이렇게 정의한 싱글톤 클래스의 객체는 바로 클래스 이름과 함께 쓰면 된다.
Singleton.addCount()
println("count is ${Singleton.count}")
companion object
코틀린에는 static이 없다. 즉 같은 클래스의 인스턴스들끼리 공유하는 변수나 함수를 만들 수 없다. 대신 companion object로 같은 일을 할 수 있다.
class Example {
companion object {
val name = "Example class"
}
}
println("The class name is: ${Example.name}")
확장
기존 클래스에 새로운 메서드를 넣고 싶다면 자바에서는 클래스를 상속 받아서 추가해야 했다. 코틀린은 그럴 필요 없이 그냥 기존 클래스를 확장해서 새로운 메서드를 추가할 수 있는 방법을 제공한다. 또한 변수나 함수가 꼭 클래스 안에 있지 않아도 된다.
package com.example
val NAME = "John Doe"
fun myName() : String {
println("$NAME$)
}
이렇게 해 놓고,
com.example.myName()
다른 패키지에서 쓰려면 이렇게 패키지 이름을 붙여서 참조하면 된다.
각주
- ↑ 안드로이드 스튜디오에서 코틀린 플러그인을 설치해서 쓸 수 있다.
- ↑ 자바만이 아니라 대부분 프로그래밍 언어, 그 중에서도 C를 계승한 언어들이 그렇다. 반면 Go는 코틀린처럼 변수의 타입을 나중에 쓴다. 이쪽을 지지하는 주장은 보통 사람은 '변수 name은 String 유형'와 같은 순서로 생각하는 게 자유롭다는 것.
- ↑ 코틀린의 변수 선언과 가장 비슷한 언어는 놀랍게도 지금 별로 쓰이지 않고 있는 파스칼이다! 코틀린의 변수 선언 방식은
val a: Int = 10000
인데 파스칼도var a : integer = 10000;
. - ↑ 이른바 The Billion Dollar Mistake라고 부른다. 토니 호어가 알골 언어를 설계하는 과정에서 좀 쉽게 가자는 마음에 널 참조를 허용했는데 이 때문에 수십 년 동안 일어난 문제가 'billion dollar' 수준으로 손해가 될 만큼 엄청나다는 것을 뜻하는 말.