선생님, 개발을 잘하고 싶어요.

[kotlin] 안드로이드 개발에 필요한 최소의 코틀린 강좌 (part1) 본문

강좌/kotlin 강좌

[kotlin] 안드로이드 개발에 필요한 최소의 코틀린 강좌 (part1)

알고싶은 승민 2019. 9. 2. 17:53

도입

 안녕하세요. 안드로이드 공식 언어로 코틀린이 채택된지도 꽤 오랜 시간이 흘렀는데요. 코틀린을 가지고 개발을 하며 코틀린이 c++, java, c#과 어떤 부분에서 다르고, 어떤 부분에서 비슷한지, 왜 코틀린을 쓰면 편한지를 [안드로이드 개발에 필요한 최소의 코틀린]이라는 시리즈로 다뤄보려고 합니다.

 

 이 포스트는 C++, Java, C#언어를 사용해 본 경험이 있는 사람들을 대상으로 작성되었으며, 실제로 코드를 작성하며 포스트를 보면 이해가 잘 되실 거라고 생각합니다. 순차적으로 읽어나가셔도 이해가 잘 안 되는 부분이 많으실 텐데, 실제 프로젝트 진행 중에 참고해서 보시면 도움되실 거예요.

 

코드 작성은 코틀린 플레이그라운드상에서 진행하시거나, 실제 안드로이드 프로젝트를 생성 후 작성해주시면 됩니다.

 

그리고 해당 포스트는 코틀린 공식 홈페이지와, 커니의 코틀린을 참조해서 만들었다는 점을 말씀드립니다. (+ 필자의 경험 :) )

다루는 것

  • 코틀린 문법
    • 선언 (변수, 값 선언) [part1]
    • 자료형 (Nullable) [part1]
    • 리스트 (Mutable) [part1]
    • 흐름 제어 (when, for)
    • 함수 정
    • 코틀린만의 클래스 정의 (data class, object)
    • 상속
    • 람다식
  • 알아두면 인생이 편해지는 코틀린 유틸리티
    • String Templates
    • 리스트 유틸리티 (스트림 함수)
    • 범위 지정 함수 (let, apply, with, run, also)
  • 코틀린은 왜? (사견이 많이 들어간 칼럼입니다.)
    • 왜 코틀린은 불변성에 집중했을까?
    • 왜 코틀린은 nullable에 집중했을까?

선언

공식 참고 링크 : https://kotlinlang.org/docs/reference/properties.html

 

 코틀린은 값의 변경 가능 여부를 중요하게 생각합니다. 그래서 변수를 선언할 때 변경 가능한 변수 선언, 변경 불가능한 변수 선언 크게 두 가지의 키워드를 사용해서 선언합니다.

val : 변경 불가능한 값, 상수 (immutable , value)
var : 변경 가능한 값, 변수 (mutable, variable)

 감이 잘 안 오실 겁니다. java, c#, c++에서도 상수를 선언하는 방법 (java의 final, c#의 const, readonly, c++의 const) 이 존재하지만 코틀린은 이를 극적으로 단순하게 만들어 상수의 중요성을 강조했습니다. 

 

 실제 코드를 보면서 확인해 볼까요?

val result: Int = 3

print(result) // 3
result = 4 // 컴파일 에러! val 변수는 대입 연산이 불가능하다!

var mutableResult: Int = 3

print(mutableResult) // 3
mutableResult = 4 // var 변수는 대입 연산이 가능하다!
print(mutableResult) // 4

var someVariable: Int // 컴파일 에러! 초기화를 해주지 않으면 에러다.

 val로 선언한 result는 변경 불가능한 상수이기 때문에 대입 연산자를 작성하면 빌드 조차 안되게 됩니다. 

실제로 결괏값을 변경하고 싶다면 아래처럼 var로 선언을 해주면 대입 연산자가 가능하게 됩니다.

 

또한 코틀린은 변수의 안정성에 크게 신경을 썼기 때문에 변수든 상수든 무조건 초기화와 함께 작성해주어야 합니다. 그렇지 않으면 컴파일 조차 되지 않죠.

 

이렇게 해서 코틀린은 해당 변수가 선언된 이후부터 변수에 값이 비어있는 상황을 신경 쓰지 않고 개발을 진행할 수 있게 됩니다.

 

반면, 변수를 애플리케이션 실행 중에야 초기화할 수 있는 경우는 어떨까요? 그럴 때를 대비해서 코틀린은 lateinit var 키워드를 마련해 두었습니다.

lateinit var : 변경 가능한 변수, 초기화는 런타임에 하겠다고 명시

 즉 lateinit var를 사용하면 초기화를 런타임으로 미루어 초기값을 설정하지 않아도 개발이 가능합니다만, 초기화하지 않고 해당 변수에 접근하면 UninitializedPropertyAccessException라는 런타임 에러가 발생하게 됩니다. java에서는 NullpointerException이 발생하는 것과 다르게 좀 더 명시적인 런타임 에러를 가지게 되는 것이죠.

 

실제 안드로이드 코드에서는 어떤 식으로 사용될까요?

class MainActivity : AppCompatActivity() {

    private lateinit var someArgument: SomeClass // 초기화 코드는 필요없다! 어차피 나중에 대입 할 거잖아?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // someArgument의 초기화는 런타임에 진행되어야 한다.
        someArgument = SomeClass(intent.getString("hello"))
    }
}

따라서 해당 객체의 초기화를 뒤로 미루고 싶은 경우도 코틀린은 문법적으로 지원해주고 있습니다. (가끔씩 안드로이드 개발에서 필요한 경우가 생기실 겁니다. 그때그때 사용하시며 느낌을 잡아놓으시면 좋겠네요.)


자료형 (Nullable)

공식 참고 링크 : https://kotlinlang.org/docs/reference/null-safety.html

 

 또한 코틀린은 해당 변수가 null일수 있는지 아닌지를 중요하게 생각합니다. 프로그래머의 부주의로 null객체의 함수나 멤버에 접근할 때 발생하는 NullPointerException은 언제나 프로그래머들의 골칫거리죠. 

 모든 변수가 null일 수 있는 언어는 프로그래머가 해당 변수가 null인지 아닌지 모두 예외처리를 해주어야 하는 경우가 발생하는 겁니다. 부주의하게 실수를 한다면, 프로그램이 바로 죽어버리는 경우가 발생하죠. 그리고 이러한 경우 프로그램이 왜 죽었는지 발견하기도 매우 어렵습니다.

 

 코틀린은 이를? 키워드를 통해 깔끔하게 해결했습니다. Null일 수 있는 자료형은 자료형 뒤에?를 붙이자는 아이디어죠. 다음과 같습니다.

var a: Int = 3 // a는 절대로 null 일 수 없다.

a = null // 컴파일 에러, a는 null일 수 없다.

var b: Int? = 4 // Int?는 Int일 수도 있고, null일 수도 있다는 의미이다.

b = null 
print(b) // null

 

 이는 멤버 변수에 접근이나 멤버 함수에 접근을 매우 안전하게 도와줍니다. 다음의 예를 봅시다.

var a: SomeClass? = SomeClass()

a.someFunction() // 컴파일 에러, a가 null일 수 있고, null객체의 멤버에 접근하는 것은 불가능 하기 때문에

a?.someFunction() // a가 null이 아니면 someFunction을 실행하고, null이면 무시한다.

------------------

// 이 조건문은
if (a != null) {
    a.someFunction()
}

// 이 표현식과 완벽히 동일하다.
a?.someFunction()

 따라서 nullable 변수를 정의해도 프로그래머는 아주 편하고 강력하게 null check후 함수를 실행할지 말지 결정할 수 있다는 장점이 있습니다.

 

 반면에 조금 위험하지만, nullable객체가 프로그램 흐름상 절대 null이 아니라고 확신할 수 있을 때는!! 키워드를 사용해서 null이 아니라고 단정 시켜 주시면 됩니다만, 개인적으로 저는 추천하지 않는 방법입니다. 제가 나중에 알려드릴 범위지정 함수를 사용해서 더 kotlin like 하게 개발할 수 있습니다. 

var a: SomeClass? = SomeClass()

// 컴파일 에러, a가 null일 수 있고, null객체의 멤버에 접근하는 것은 불가능 하기 때문에
a.someFunction() 

// a가 null이 아니면 someFunction을 실행하고, null이면 무시한다.
a?.someFunction() 

// a는 null이 아니라고 컴파일러에게 알려준다. 컴파일은 되지만, 해당 코드는 런타임에 에러를 내보낼 수 있다.
a!!.someFunction() 

리스트 (변경 가능성 관점)

공식 참고 링크 : https://kotlinlang.org/docs/reference/collections-overview.html

 

 코틀린은 리스트에서도 리스트가 변경 가능한지 아닌지를 중요하게 생각합니다. 여기서 리스트의 변경 가능성이란 다음과 같습니다.

리스트에 쓰기(추가, 제거)를 할 수 있으면, 변경 가능한 리스트이다.

 이를 위해 코틀린은 기존 java에서와 다르게, Collection 자료형을 분리합니다. 

 

MutableCollection <T>와 Collection <T>가 바로 그것이죠.

 

Collection <T>는 변경이 불가능한 묶음입니다. 즉, add, remove 같은 연산이 정의되어 있지 않습니다.

반면 MutableCollection <T>는 변경이 가능한 묶음입니다. 즉, 추가와 삭제 연산이 모두 정의가 돼있죠.

 

 그리고 코틀린은 각 Collection을 만드는 편의 함수를 제공합니다. 예를 들어 listOf()와 mutableListOf()가 있습니다. 지금까지 말한 내용을 예시를 통해서 한 번 같이 확인해보죠.

val aList: List<Int> = listOf(1,2,3) // listOf로 불변하는 배열을 생성했다. 불변하는 배열은 List<T>형으로 정의된다.

print(aList[2]) // 3, index에 해당하는 아이템을 출력한다.
aList.add(3) // 컴파일 에러! 불변하는 배열은 추가하는 연산이 없다.
aList.remove(2) // 컴파일 에러! 불변하는 배열은 삭제 연산이 없다.

val bList: MutableList<Int> = mutableListOf(1,2,3) // mutableListOf()로 변경 가능한 배열을 생성했다. 변경 가능한 배열은 MutableList<T>형으로 정의된다.

print(bList[2]) // 3, index에 해당하는 아이템을 출력한다.
bList.add(4) // 
print(bList) // [1, 2, 3, 4]
bList.remove(2) //
print(bList) // [1, 3, 4]

따라서 리스트에 쓰기 연산 (add, remove)를 사용하시려면 mutable 자료형을 꼭 선언해 주셔야 한다는 말입니다.

 

코틀린 Collection 계층도

각각 List, Set, Map에 대해 Mutable과 그렇지 않은 것이 pair로 준비되어 있습니다.

 


주저리

 이번 시간에는 코틀린이 변수를 대하는 태도에 대해서 중점적으로 다루었습니다. 다른 언어와 가장 큰 차이점이 바로 이러한 부분이라고 할 수 있겠네요.

 

  • 코틀린은 변수의 변경 가능성을 중요하게 생각하고 만들어진 언어다.
  • 코틀린은 변수의 Null 가능성을 중요하게 생각하고 만들어진 언어다.

이 두 가지를 당장 이해하실 필요는 없습니다. 다만 프로그래밍하실 때, 변수를 선언하실 때 해당 변수의 자료형을 선택하는 과정에서 위의 두 가지 키워드를 생각하고 선언해야 한다는 어려움이 있습니다만

 

 두 가지 모두 프로그래머가 부주의 하게 넘겼을 때 런타임 에러로 이어질 수 있는 치명적인 것들인 점을 생각해본다면, 프로그램 실행하기 전에 프로그래머에게 주의하라고 가이드를 주는 친절한 언어라고 생각됩니다. 그래서 이제부터 개발을 진행하실 때는 변수의 속성에 대해서 좀 더 생각해보시면서 개발하면 어떨까요?

 

다음은 흐름 제어와 클래스, 함수 정의에 관한 부분들을 다뤄보도록 하겠습니다.

 


 

[part1] 선언, 자료형, 리스트 (we're here)

[part2] 흐름 제어, 함수 정의, 코틀린만의 클래스

[part3] 클래스 심화, 확장

[part4] 확장 함수

Comments