Kotlin Syntax, Null Safety and more…
이번 장에서는 코틀린의 기본 구문(Syntax)을 이해하고 fragment를 열기 위한 코드를 MainActivity.kt에 추가하는 것에 중점을 두고 진행됩니다.
코틀린 파일인 MainActivity를 이용해 구문을 공부해봅시다. 코틀린 코드를 보는 게 처음이라 좀 두려울 수 있지만 아마 곧 코틀린을 좋아하게 될 거라 확신합니다.
코틀린이란 언어를 다양한 방법으로 정의할 수 있습니다만 다음 문장이 코틀린의 가장 중요한 특징이 포함되어 있기에 제일 좋아합니다.
코틀린은 자바와 상호 호환되는데 중점을 둔
간결하고 안전한 정적 타입 프로그래밍 언어이다.
이 정의를 갖고 생성된 코드를 살펴보도록 합시다. 자바와 코틀린으로 된 MainActivity 파일들은 다음과 같습니다.
(브런치에서 코드 하일라이팅이나 gist 임베딩을 지원하지 않아 부득이하게 그림파일로 넣었습니다.)
https://gist.github.com/juanchosaravia/e91fd9c27b232e86c096#file-kotlin-tutorial-mainactivity-java
https://gist.github.com/juanchosaravia/38ecab272894b83c158a#file-kotlin-tutorial-mainactivity-kt
자바는 19줄이지만 코틀린은 12줄에 불과한 것을 보셨나요? 이건 자바의 불필요한 부분을 제거했기 때문에 그렇습니다. 간결한 코드는 읽고 쓰는데 시간을 줄여주고 곧 생산성의 증가로 이어집니다.
"extends"와 "implement"라는 단어는 모두 콜론 (:)으로 대체되었습니다. 여기서는 AppCompatActivity(Java class인데도!)를 extend 합니다.
class MainActivity : AppCompatActivity()
기존의 "public void methodName()" 구조가 아닌 "fun" 키워드를 이용해 funtions을 정의하고 반환 타입은 끝에 추가해 줍니다. 근데 "onCreate" 메서드의 리턴 타입은 어디에 있을까요?
override fun onCreate(savedInstanceState: Bundle?) {
값을 리턴하고 싶지 않은 경우 자바에서는 "void"를 사용합니다만 코틀린에서는 "Unit"을 같은 방식으로 사용합니다. 리턴 타입을 생략함으로써 컴파일러에게 리턴 타입이 없다는 것을 알려줄 수 있습니다. 필요하다면 다음과 같은 방법으로 추가해줄 수도 있습니다.
override fun onCreate(savedInstanceState: Bundle?) : Unit {
또한 parameter를 나타낼 때 다른 순서를 가집니다. 가장 먼저 변수의 이름이, 그리고 타입이 옵니다.
코틀린에서는 문장 끝에 세미콜론(;)을 붙여줄 필요가 없습니다. 하고 싶으면 해도 됩니다.(하지 마세요)
변수(variable)를 정의하려면 "var" 키워드를 이용합니다. 변수의 타입은 할당되는 값(context)에 의해 추론되며 같은 방법으로 상수(constant)는 val 키워드를 이용합니다.
val price = 100 // Int
price = 30 // 상수에 값을 대입하려 했기 때문에 컴파일 오류 발생.
var total = price * 3 // Int
val name = "Juancho" // String
타입을 명시적으로 지정해줄 수도 있습니다.
val lastname : String = "Keddit" // 명시적인 타입 선언
var size : Double = 30.0
var time : Float = 15f
"double"을 사용하지 않고 "Double"을 사용하는 것처럼 primitive 타입을 사용하지 않았다는 걸 볼 수 있었을 것입니다. 코틀린의 모든 것은 객체(object)이기 때문에 그렇습니다. 성능 향상을 위해 컴파일러는 내부적으로 이런 객체 중 일부는 primitive 타입으로 변환시킵니다.
코틀린에서 자바의 필드를 접근(access)하는 것처럼 property를 접근할 수 있습니다. Activity에서 getResources() 메서드를 호출하는 것 대신 직접 접근하면 됩니다.
resources.getString(R.string.id)
// vs
getResources().getString(R.string.id) // 이것도 가능함.
여전히 getResoucre() 메서드를 호출할 수 있습니다만 안드로이드 스튜디오가 바꿀 수 있다고 제안할 겁니다.
이건 filed에 직접적으로 접근할 수 있다는 의미가 아니라 단지 getResource() 메서드를 편리하게 호출할 수 있는 것입니다.
이번에 설명할 내용은 코틀린의 훌륭한 점 중 하나인데 사용자가 아래의 방법처럼 따로 명시하여 선언하지 않는 한 코틀린의 모든 것은 null이 될 수 없습니다. 즉, 값이 없을 수도 있을 수도 있다는 걸 "?" 물음표를 붙이는 방법으로 나타낼 수 있습니다.
(Null이 될 수 있는 객체는 앞으로 nullable 객체라 편의상 표현하겠습니다)
말로만 하지 말고 한 번 다음의 예제를 봐봅시다.
val a : String = null // 컴파일 오류!
var b : Int // 마찬가지로 오류! 반드시 초기화돼야 합니다.
val ok : String? = null // 우왕 굳 :)
컴파일러는 null이 될 수 있는 객체가 있는지 체크하여 "NullPointerException"이나 잘 알려진 "Billion-dollar mistake"와 같은 실수를 방지할 수 있게 합니다.
nullable 객체와 상호작용 하는 건 정말 쉽습니다. nullable 객체에 "?" 물음표를 붙임으로써 값이 존재한다면 얻을 것이고 없다면 무시되어 안전하게 프로그램을 계속 실행할 수 있습니다.
val context : Context? = null
val res = context?.getResources() // res는 null이 되고 crash 나지 않음.
nullable 객체를 가지고 쭉 이용하길 원한다면 아마 다음과 같이 쓸 것입니다.
val context : Context? = null
val res = context?.getResources()
val appName = res?.getString(R.string.app_name)
val shortName = appName?.substring(0, 2)
이건 좀 구린데요, 대신에 smart cast를 이용하면 좀 더 낫습니다. context가 null인지 if문으로 체크하고 체크한 if 블록 내에서는 context는 nullable하지 않은 객체로 취급됩니다.
val context : Context? = null
if (context != null) {
val res = context.getResources() // 더 이상 '?'를 붙일 필요가 없음
val appName = res.getString(R.string.app_name)
val shortName = appName.substring(0, 2)
}
"?:" 연산자를 elvis 연산자라 부르고 객체가 null일 경우 대체 값을 얻기 위해 사용됩니다. 이 연산자는 null 체크를 수행하기 위한 축약형 표현이라 보면 됩니다. 이 예제에서 message는 null일 수 있기 때문에 타입은 "String?"입니다. 이 경우 message의 값이 null이 아니라면 전달할 것이고 아니라면 elvis 연산자 뒤의 값을 이용하여 전달할 것입니다.
try {
// code...
} catch (e: Throwable) {
Log.e("TAG", e.message ?: "Error message")
}
null safety에 대해 좀 더 자세한 내용을 알고 싶다면 다음의 링크를 참고합시다.
https://kotlinlang.org/docs/reference/null-safety.html
정적 타입이란 의미는 코틀린이 컴파일 타임에 타입을 체크하여 컴파일러가 코드에 정의된 모든 것들의 타입을 알 수 있어야 한다는 것입니다. 변수에 값을 잘 할당될 수 있도록 우리가 사용하고 있는 안드로이드 스튜디오(젯브레인 ㄱㅅ)라는 강력한 IDE의 도움을 받을 것입니다.
뿐만 아니라 변수(혹은 상수)를 생성할 때 타입을 지정해줄 필요가 없습니다. 다음 toolbar constant를 보면 타입은 context로부터 추론되는데 이건 코틀린의 또 다른 훌륭한 점 중 하나입니다.
val toolbar = findViewById(R.id.toolbar) as Toolbar
타입 추론은 언어의 신뢰성(컴파일러가 프로그램의 정확성을 검증)과 유지보수성(Maintainability, 스스로 설명하는 코드), 도구 지원(전에 언급했듯이 정적 타입은 리팩터링 때 신뢰성 및 코드 자동완성 등을 가능하게 함) 및 성능(대부분의 경우 메서드가 호출될 필요가 있는지 런타임 시에 파악할 필요가 없음)에 있어서 큰 장점입니다.
코틀린 파일에서 자바 코드를 쓰거나 혹은 그 반대의 경우가 가능하다는 건 코틀린의 큰 특징 중 하나입니다. Keddit 앱에서 AppCompatActivity를 extend, onCreate 메서드의 Bundle 객체를 사용하는 것, 앱을 만들 때 Android SDK를 사용하는 것에서 그 예를 찾아볼 수 있습니다 :). 또한 이미 자바 라이브러리인 Retrofit이나 Picasso 등을 사용하고 있습니다.
자바와 호환된다는 사실은 코틀린은 자체적인 collection 라이브러리 없이 자바의 standard library class에 의존하며 이 class들을 새로운 함수로 확장해서 쓰고 있다는 것을 보면 알 수 있습니다. 이 말은 Java와 코틀린 사이에 객체를 변환할 필요가 없다는 것입니다.
다음 장에서 살펴볼 fragment 열기를 위해 MainActivity에 몇 가지 메서드를 추가했고 사용하지 않는 메뉴 세팅에 관련된 코드들을 제거했습니다.
아래 경로의 코드에서 이번에 수행한 작업들을 살펴볼 수 있고 새로운 내용들을 이해해보도록 합시다.
제가 Fragment manager를 어떻게 사용했는지 보면,
val ft = supportFragmentManager.beginTransaction();
// and not getSupportFragmentManager()
changeFragement() 메서드를 사용했는데 이걸 이용하면 액티비티 안에 fragment를 열 때 페이드 애니메이션과 함께 열리도록 할 수 있습니다.
https://github.com/juanchosaravia/KedditBySteps/tree/v0.2
이번 장은 코틀린의 특징 중 아주 작은 부분일 뿐입니다. 앞으로 keddit 앱을 개발하며 좀 더 많은 걸 배울 것입니다. 코틀린에 대해 좀 더 알고 싶다면 http://try.kotlinlang.org/ 링크를 확인해보세요. 아주 유용할 겁니다. 다음 장에서 만나염!
Twitter: https://twitter.com/juanchosaravia