티스토리 뷰
Kotln, Modern Programming Lauage
Kotlin이 뭐죠?
Kotlin 은 자바의 가상머신 (JVM) 위에서 돌아가는 프로그래밍 언어입니다. Jetbrain이라는 IntelliJ, Android Studio와 같은 IDE를 만드는 회사에서 자바가 하기 힘든 기능들을 지원하기 위해서 만들어진 언어입니다. Jetbrain 또한 기존의 시스템이 자바에 많이 의존하고 있었고 자바의 한계, NullPointer라던가 변수 등을 조작함으로써 데이터를 관리하는 Mutability 등을 극복하기 위해 만들어졌습니다. 실제로 자바의 언어적 한계로 인해 생산성도 떨어지게 되고 예기치않은 에러도 발생하기 쉬웠기 때문에 Jetbrain은 새로운 언어를 만들기 시작했습니다.
Kotlin은 2012년 경에 프로젝트가 시작되었고, Kotlin이라는 이름은 개발팀이 러시아 도시인 Kotlin에 위치한데서 유래된 것입니다. Kotlin은 안드로이드 개발자들이 본격적으로 사용하기 시작했으면, 현재까지도 안드로이드 개발에 주로 많이 사용되고 있습니다. 2018년 현재 약 절반 이상의 개발자들이 안드로이드 개발에 Kotlin을 사용하고 있습니다. (참고, https://pusher.com/state-of-kotlin) 저 또한 회사에서 새로운 코드는 무조건 Kotlin을 사용하고 있습니다.
왜 Kotlin인가?
Kotlin 의 장점은 자바가 제대로 해줄 수 없는 부분을 살펴보면 쉽게 이해할 수 있습니다. 아래로 언급하는 부분들은 개별적인 포스팅을 통해 자세하게 다루도록 하고 여기서는 개괄적인 부분만 살펴보도록 하겠습니다.
JVM Compatable
코틀린으로 작성된 코드는 컴파일 되면 결국 자바 바이트 코드가 됩니다. 즉, 기존에 자바에서도 코틀린 코드를 사용할 수 있고, 반대로 코틀린에서도 자바코드를 사용할 수 있습니다. 기존에 자바로 작성된 레거시 코드가 있다면 새로운 기능은 코틀린만을 사용할 수 있습니다. 이게 현재 제가 회사에서 하고 있는 방식입니다.
Nullability/Null safety
String s = null
s.length //Error
위의 자바 코드에서 s가 널일 때 필드에 접근하면 NullPointerException 이 발생하게 됩니다. 반면 코틀린에서는 아래와 같이 널이 허용되는 타입과 허용되지 않는 타입이 따로 존재하면, 널이 허용되는 타입 (Nullable type)을 사용하게 될 경우 ? (Optional operator)를 통해서 접근해야만 하며 만약 s 가 널이면 length 필드는 접근하지 않게 해줍니다. 이 때문에 자바에서 깜빡하고 널체크를 않넣어서 에러가 나곤 하던 부분이 자연스럽게 해결되도록 도와줍니다. 개발자가 해야할 것은 non-nullable 타입을 쓸 것인지 nullable type 을 쓸 것인지만 선택하면 됩니다.
//Non-nullable type
val s: String = "hello"
s.length //5
//Nullable type
var s: String? = null
s.length //compile error
s?.length //null
first-class citizen function
코트린은 메소드 대신 function 을 사용합니다. 이말은 코틀린의 function은 메소드와는 달리 항상 리턴값이 있다는 말입니다. 기본 리턴 타입은 Unit 타입이며 자바의 void와 유사합니다. 코틀린에서는 자바의 lambda 처럼 function을 파라미터롤 넘길 수 있으며, 다시 다른 function을 리턴할 수도 있습니다.
fun add(a: int, b: Int): Int {
return a + b
}
//one line function
fun add(a:Int, b:Int): Int = a + b
//기본 파라미터 함수
fun add(a: Int = 0, b: Int = 0) = a + b //add()는 add(0,0)을 호출, add (a = 1), add (b=1), add(b=1, a=1) 도 가능.
//void function
fun greet(): Unit {
println("Hello, Kotlin")
}
Lambas
코틀린의 함수는 first-class citizen 이고 함수를 파라미터를 사용할 수 있다고 했습니다. 이걸 프로그래밍 용어로 Higher-Order Function이라고 부릅니다. 코틀린의 콜렉션 패키지에 있는 확장 함수(Extension Function)을 예로 살펴보겠습니다.
fun <t, r=""> Collection<t>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
위의 코드를 보면, combine
파라미터는 function type 으로 acc: R, nextElement: T
를 파라미터로 받아서 initial
과 같은 R
타입을 리턴하는 함수입니다. 이 함수가 하는 일은 Collection<t>
의 아이템들을 돌면서 처리한 연산값을 acc
에 값을 쌓아서 마지막에 R
타입 하나만 리턴하는 함수입니다. 아래는 fold
함수를 이용한 예제들입니다.
val items = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 1부터 10까지 더한다.
val sumOneToTen: Int = 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
})
println(sumOneToTen) //55
// Parameter types in a lambda are optional if they can be inferred:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
// Function references can also be used for higher-order function calls:
val product = items.fold(1, Int::times)
지금 이해가 가지 않더라도 걱정할 필요없습니다. 나중에 다시 자세하게 언급할 거니까요.
Immutability
코틀린의 특징 중 하나는 모던 프로그램이 패러다임인 함수형 언어(functional language) 의 특징 중 하나인 immutability 를 대체로 잘 지원합니다. immutability는 변수의 값을 조작함으로써 상태값을 갖도록 하는 기존 프로그래밍의 방법으로, 멀티쓰레드에 안전하지 않고, 런타임에 예기치 않는 버그를 유발하는 요소 중의 하나입니다.
public class Person {
private String name;
private Country country;
public Person(String name, Country country) {
this.name = name;
this.country = country;
}
//getters & setters 생략
}
public class Country {
private String name;
public Country(String name) {
this.name = name;
}
//getter & setter 생략
}
심플한 자바 클래스들입니다. 기존의 프로그래밍 방식에서 위의 클래스들을 사용할 때 생길 수 있는 문제를 하나 살펴봅시다.
Country korea = new Country("Korea")
Person korean = new Person("Hong Kil Dong", country);
System.out.println(korean.country); //Korea
korea.setName("China"); //country = new Country("China")도 동일한 부작용 발생.
System.out.println(korean.country); //China
위의 코드에서 한국인을 중국인으로 바꿔버리는 실수를 할 수 있게 됨을 볼 수 있습니다. 실제로 필드에서는 이런 일이 자주 발생하곤합니다. 왜냐하면 아무 제약없이 korea 인스턴스의 세터에 접근해서 값을 변경할 수 있기 때문입니다. 물론, 자바에서도 이 문제를 어느정도는 해결할 수 있습니다만, 코틀린보다 추가적인 작업이 더 필요하기 때문에 좀 번거롭습니다. 코틀린은 자연스럽게 이런 문제를 해결하도록 도와줍니다. 위의 문제를 코틀린을 이용해 해결하면,
class Person(val name: String, val country: Country)
class Country(val name: String)
val korea: Country = Country("Korea")
val korean: Person = Person("Hong Kil Dong", korea)
country.name = "China" //compile error!
korean.country = Country("China") //compile error!
코틀린에서는 val 이라는 키워드를 이용해서 읽기전용(readonly) 타입을 만듭니다. 따라서 country.name 을 변경하거나 person.country을 다른 인스턴스로 대체할 수 없습니다. 둘의 차이점이 분명하게 보이시나요? 그렇지 않더라도 걱정하실 필요 없습니다. 앞으로 차근차근 하나씩 되짚어보게 될 거니까요. Data Class 제가 좋아하는 코틀린의 장점이자 자바로는 바로 안되는 것 중의 하나가 데이터 클래스입니다. 말 그대로 데이터를 표현하기 위한 클래스입니다. 클래스 앞에 data 키워드를 붙이면 data class가 됩니다. 쉽죠? 왜 데이터 클래스가 좋으냐 하면, 이 클래스는 data키워드만 앞에 붙이는 것으로 getters, setters, equals, hasCode, copy, toString 등의 기능을 자동으로 구현해 줍니다. 자바로는 일일이 코딩을 해야 합니다. 추가로 destruction 이라는 자바에는 없는 유용한 기능도 사용할 수 있습니다. 위의 Person 을 데이터 클래스로 만들고 데이터 클래스들의 기능을 이용해 보겠습니다.
data class Person(val name: String, val age: Int)
val teenage: Person = Person("Hong Kil Dong", age = 13)
println(kilDong) //teenage의 내용을 보기좋게 출력. Person(name=Hong Kil Dong, age=13)
val adult: Person = teenage.copy(age = 21) //age필드만 다른 값을 주고 teenage 인스턴스를 복사.
val (name, age) = adult //destruction
println("name: $name, age: $age") // name: Hong Kil Dong, age: 21
val anotherAdult: Person = Person("Hong Kil Dong", 21)
println(adult == anotherAdult) //true
Extensions
자바에서 지원되지않는 기능 중의 하나는 확장기능(Extension)입니다. Extension은 라이브러등에 있는 클래스 등에 추가적인 기능을 넣고 싶은데 final
로 되어 있어서 상속이 불가능한 경우 등과 같이, 내부 구조의 변경이 불가능할 경우 외부에서 함수를 추가할 수 있는 편리한 기능입니다. 예를 들면 Integer
클래스에 짝수인지 검사하는 함수를 추가하고 싶을 경우 자바에서는 Utility class에 static method를 사용할 것입니다. 코틀린에서는 다음과 같이 할 수 있습니다.
Int.even() = this % 2 == 0
1.even() //false
2.even() //true
기존 Integer 클래스에 even()이란 함수를 추가하여 인스턴스로부터 바로 호출할 수 있습니다. 이건 코틀린 컴파일러가 제공하는 일종의 트릭인데, 컴파일된 자바코드를 확인해보면 내부적으로 static
메소드를 사용하는 것을 알 수 있습니다. Extension 함수는 자바코드에서 사용하고자 한다면 JVM 어노테이션을 사용해서 유틸리티 메소드 처럼 사용할 수 있습니다.
향상된 Collection APIs
코틀린에서는 자바 8부터 지원되는 Stream API
보다 더 강력한 콜렉션 함수들을 제공합니다. 이 함수들은 대부분 확장함수형태로 존재하기 때문에 기존에 자바코드를 조작하지 않으므로 안전합니다. 콜렉션 함수는 수가 너무 많기 때문에 대표적인 map
함수만 살펴보고 나중에 자세하게 다루도록 하겠습니다.
val squredOneToTen = (1..5).toList()
.map{ num -> num * num }
println(squredOneToTen) //[1,4,9,16,25]
Coroutines
Coroutines는 코틀린 1.3 부터 지원되기 시작한 기능으로 비동기작업을 (asynchoronous or non-blocking job)을 동기화 작업을 하는 것처럼 코딩할 수 있는 기능을 말합니다. 코트린의 coroutines는 상당이 가벼운 쓰레드이기 때문에 수백개를 한꺼번에 생성할 수도 있습니다. 사실 이 부분이 코틀린의 특징 중 1, 2 번째로 갈 수 있는 좋은 기능인데, 제가 아직 coroutine 을 공부하지 않은 관계로 뒷쪽에 위치시켰습니다. 간단히 coroutine이 어떻게 생겼는지만 확인하고, 나중에 공부하는대로 업데이트 하겠습니다.
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
이 외에도 수많은 유용한 기능들이 존재하지만 한번에 너무 많은 분량을 다루는 것보다는 개별적으로 하나씩 자세하게 살펴보도록 하겠습니다. 코틀린에는 Function Programming에서 많은 개념을 가져왔기 때문에 Object Oriented Programming과 Function Programming이 공존하는 Multi-paradigm 언어입니다. 따라서 역시나 제대로 사용하지 않을 경우에는 코틀린에서 제공하는 수많은 유익한 기능에도 불구하고 기존 자바코드보다 나아진 코드를 작성하지 못할 수도 있습니다. 앞으로의 포스팅에서는 이런 부분을 염두에 두면서 코틀린을 익혀 나가도록 합시다.
'Kotlin' 카테고리의 다른 글
SharedPreferences Property Delegate (0) | 2024.05.01 |
---|---|
Kotlin's Result sealed class (0) | 2022.02.18 |