Kotlin은 프로그래밍 언어입니다. 전 세계에서 Android 개발자가 널리 사용합니다. 이 주제는 활용도를 높이기 위한 Kotlin 단기 집중과정입니다.
변수 선언
Kotlin은 두 키워드(val
및 var
)를 사용하여 변수를 선언합니다.
- 값이 변경되지 않는 변수에
val
을 사용합니다.val
을 사용하여 선언된 변수에 값을 재할당할 수 없습니다. - 값이 변경될 수 있는 변수에
var
을 사용합니다.
아래 예에서 count
는 10
의 초기 값이 할당되는 Int
유형의 변수입니다.
var count: Int = 10
Int
는 정수를 나타내는 유형이며 Kotlin에서 표현될 수 있는 많은 숫자 유형 중 하나입니다. 다른 언어와 마찬가지로 수치 데이터에 따라 Byte
, Short
, Long
, Float
, Double
을 사용할 수도 있습니다.
var
키워드는 필요에 따라 count
에 값을 재할당할 수 있음을 의미합니다. 예를 들어 count
값을 10
에서 15
로 변경할 수 있습니다.
var count: Int = 10
count = 15
하지만 일부 값은 변경되지 않습니다. languageName
이라는 String
을 고려합니다. languageName
에서 'Kotlin'의 값이 항상 유지되도록 하려면 val
키워드를 사용하여 languageName
을 선언합니다.
val languageName: String = "Kotlin"
이러한 키워드를 사용하면 변경 가능한 항목을 명시할 수 있습니다. 필요에 따라 키워드를 유용하게 사용하세요. 변수 참조를 재할당할 수 있어야 하는 경우 var
로 선언합니다. 그렇지 않은 경우 val
을 사용합니다.
유형 추론
이전 예에서 languageName
에 초기 값을 할당할 때 Kotlin 컴파일러는 할당된 값의 유형을 기반으로 유형을 추론할 수 있습니다.
"Kotlin"
값이 String
유형이므로 컴파일러는 languageName
또한 String
이라고 추론합니다. Kotlin은 정적으로 입력되는 언어입니다. 즉, 컴파일 시간에 유형이 결정되고 절대 변경되지 않습니다.
다음 예에서 languageName
이 String
으로 추론되므로 String
클래스의 일부가 아닌 함수를 호출할 수 없습니다.
val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()
// Fails to compile
languageName.inc()
toUpperCase()
는 String
유형의 변수에서만 호출할 수 있는 함수입니다. Kotlin 컴파일러가 languageName
을 String
으로 추론했으므로 toUpperCase()
를 안전하게 호출할 수 있습니다. 하지만 inc()
는 Int
연산자 함수이므로 String
에서 호출할 수 없습니다. Kotlin의 유형 추론 방식은 간결성과 유형 안전성을 모두 보장합니다.
Null 안전
일부 언어에서는 초기 값을 명시적으로 제공하지 않고 참조 유형 변수를 선언할 수 있습니다. 이러한 경우 변수에는 일반적으로 null 값이 포함됩니다. Kotlin 변수는 기본적으로 null 값을 보유할 수 없습니다. 즉, 다음 스니펫은 유효하지 않습니다.
// Fails to compile
val languageName: String = null
null 값을 포함하는 변수는 nullable 유형이어야 합니다. 아래 예와 같이 ?
를 변수 유형의 접미사로 지정하여 변수를 nullable로 지정할 수 있습니다.
val languageName: String? = null
String?
유형을 사용하여 String
값 또는 null
을 languageName
에 할당할 수 있습니다.
nullable 변수는 신중하게 처리해야 합니다. 아니면 심각한 NullPointerException
이 발생할 위험이 있습니다. 예를 들어, 자바에서 null 값에 관해 메서드를 호출하려고 하면 프로그램이 비정상 종료됩니다.
Kotlin은 nullable 변수로 안전하게 작업하기 위한 많은 메커니즘을 제공합니다. 자세한 내용은 Android의 일반 Kotlin 패턴: Null 허용 여부를 참조하세요.
조건부
Kotlin은 조건부 논리를 구현하기 위한 몇 가지 메커니즘을 제공합니다. 가장 일반적인 것은 if-else 문입니다. if
키워드 옆의 괄호 안에 포함된 표현식이 true
로 평가되는 경우, 해당 분기 내 코드(즉, 중괄호 안에 포함된 바로 다음 코드)가 실행됩니다. 그렇지 않은 경우 else
분기 내 코드가 실행됩니다.
if (count == 42) {
println("I have the answer.")
} else {
println("The answer eludes me.")
}
else if
를 사용하여 여러 조건을 나타낼 수 있습니다. 즉, 다음 예와 같이 단일 조건문 내에서 보다 상세하고 복잡한 논리를 표현할 수 있습니다.
if (count == 42) {
println("I have the answer.")
} else if (count > 35) {
println("The answer is close.")
} else {
println("The answer eludes me.")
}
조건문은 스테이트풀(Stateful) 논리를 나타내는 데 유용하지만 작성 시 반복될 수 있습니다. 위 예에서는 각 분기에 String
을 인쇄합니다. 이 반복을 피하기 위해 Kotlin은 조건식을 제공합니다. 마지막 예는 다음과 같이 다시 작성될 수 있습니다.
val answerString: String = if (count == 42) {
"I have the answer."
} else if (count > 35) {
"The answer is close."
} else {
"The answer eludes me."
}
println(answerString)
암시적으로 각 조건부 분기는 마지막 줄에 표현식의 결과를 반환하므로 return
키워드를 사용할 필요가 없습니다. 세 분기의 결과는 모두 String
유형이므로 if-else 표현식의 결과도 String
유형입니다. 이 예에서 answerString
에는 if-else 표현식의 결과에서 초기 값이 할당됩니다. 유형 추론을 사용하여 answerString
에 명시적 유형 선언을 생략할 수 있지만 명확히 하기 위해 유형 선언을 포함하는 것이 좋습니다.
아래 예와 같이 조건문의 복잡도가 증가하면 if-else 표현식을 when 표현식으로 교체할 것을 고려할 수 있습니다.
val answerString = when {
count == 42 -> "I have the answer."
count > 35 -> "The answer is close."
else -> "The answer eludes me."
}
println(answerString)
when
표현식의 각 분기는 조건, 화살표(->
) 및 결과로 표시됩니다. 화살표의 왼쪽 조건이 true로 평가되면 오른쪽에 있는 표현식의 결과가 반환됩니다. 한 분기에서 다음 분기로 실행되지 않습니다.
when
표현식 예의 코드는 이전 예의 코드와 기능적으로 동일하지만 쉽게 읽을 수 있습니다.
Kotlin의 조건부는 이 언어의 강력한 기능 중 하나인 스마트 변환을 강조합니다. 안전 호출 연산자 또는 null이 아닌 어설션 연산자를 사용하여 nullable 값을 처리하는 대신 아래 예와 같이 조건문을 사용하여 변수에 null 값 참조가 있는지 확인할 수 있습니다.
val languageName: String? = null
if (languageName != null) {
// No need to write languageName?.toUpperCase()
println(languageName.toUpperCase())
}
조건부 분기 내에서 languageName
은 nullable이 아닌 것으로 간주될 수 있습니다.
Kotlin에서는 분기 실행 조건에 따라 languageName
은 null 값을 보유할 수 없으므로 분기 내에서 languageName
을 nullable로 처리할 필요가 없습니다. 이 스마트 변환은 null 검사, 유형 검사 또는 컨트랙트를 충족하는 모든 조건에 적용됩니다.
함수
하나 이상의 표현식을 함수로 그룹화할 수 있습니다. 결과가 필요할 때마다 동일한 일련의 표현식을 반복하는 대신 함수에 표현식을 포함한 다음 함수를 호출할 수 있습니다.
함수를 선언하려면 fun
키워드 뒤에 함수 이름이 오도록 사용합니다.
그런 다음 함수에 사용되는 입력 유형(있는 경우)을 정의하고 함수에서 반환하는 출력 유형을 선언합니다. 함수의 본문에서는 함수를 호출할 때 호출되는 표현식을 정의합니다.
이전 예를 기반으로 완성된 Kotlin 함수는 다음과 같습니다.
fun generateAnswerString(): String {
val answerString = if (count == 42) {
"I have the answer."
} else {
"The answer eludes me"
}
return answerString
}
위 예에서 함수의 이름은 generateAnswerString
입니다. 입력 값은 받지 않으며, String
유형의 결과를 출력합니다. 함수를 호출하려면 함수의 이름 뒤에 호출 연산자(()
)를 사용합니다. 아래 예에서 answerString
변수는 generateAnswerString()
의 결과에 따라 초기화됩니다.
val answerString = generateAnswerString()
아래 예와 같이 함수에서는 인수를 입력으로 사용할 수 있습니다.
fun generateAnswerString(countThreshold: Int): String {
val answerString = if (count > countThreshold) {
"I have the answer."
} else {
"The answer eludes me."
}
return answerString
}
함수를 선언할 때 인수의 개수와 유형을 지정할 수 있습니다. 위 예에서 generateAnswerString()
은 Int
유형의 countThreshold
인수 한 개를 사용합니다. 함수 내에서 이름을 사용하여 인수를 참조할 수 있습니다.
이 함수를 호출할 때 함수 호출 괄호 안에 인수를 포함해야 합니다.
val answerString = generateAnswerString(42)
함수 선언 단순화
generateAnswerString()
은 매우 간단한 함수입니다. 이 함수는 변수를 선언한 다음 즉시 반환합니다. 아래 예와 같이 단일 표현식의 결과가 함수에서 반환되는 경우, 함수에 포함된 if-else 표현식의 결과를 직접 반환하여 로컬 변수 선언을 건너뛸 수 있습니다.
fun generateAnswerString(countThreshold: Int): String {
return if (count > countThreshold) {
"I have the answer."
} else {
"The answer eludes me."
}
}
반환 키워드를 할당 연산자로 바꿀 수도 있습니다.
fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
"I have the answer"
} else {
"The answer eludes me"
}
익명 함수
모든 함수에 이름이 필요하지는 않습니다. 일부 함수는 입력과 출력에 의해 더 직접적으로 식별됩니다. 이러한 함수를 익명 함수라고 합니다. 이 참조를 사용하여 나중에 익명 함수를 호출하면 익명 함수 참조를 유지할 수 있습니다. 다른 참조 유형과 마찬가지로 애플리케이션에서 참조를 전달할 수도 있습니다.
val stringLengthFunc: (String) -> Int = { input ->
input.length
}
이름이 지정된 함수와 마찬가지로 익명 함수는 표현식을 제한 없이 포함할 수 있습니다. 함수의 반환 값은 최종 표현식의 결과입니다.
위 예에서 stringLengthFunc
는 String
을 입력으로 사용하고 String
입력 길이를 Int
유형의 출력으로 반환하는 익명 함수 참조를 포함합니다. 따라서 함수의 유형은 (String) -> Int
로 표시됩니다. 하지만 이 코드는 함수를 호출하지 않습니다.
함수의 결과를 가져오려면 이름이 지정된 함수처럼 호출해야 합니다. 아래 예와 같이 stringLengthFunc
를 호출할 때 String
을 공급해야 합니다.
val stringLengthFunc: (String) -> Int = { input ->
input.length
}
val stringLength: Int = stringLengthFunc("Android")
고차 함수
함수는 다른 함수를 인수로 취할 수 있습니다. 다른 함수를 인수로 사용하는 함수를 고차 함수라고 합니다. 이 패턴은 자바에서 콜백 인터페이스를 사용할 때와 동일한 방식으로 구성요소 간에 통신하는 데 유용합니다.
다음은 고차 함수의 예입니다.
fun stringMapper(str: String, mapper: (String) -> Int): Int {
// Invoke function
return mapper(str)
}
stringMapper()
함수는 전달된 String
에서 Int
값을 파생하는 함수와 함께 String
를 가져옵니다.
아래 예와 같이 다른 입력 매개변수를 충족하는 함수, 즉 String
을 입력으로 사용하고 Int
를 출력하는 함수와 String
을 전달하여 stringMapper()
를 호출할 수 있습니다.
stringMapper("Android", { input ->
input.length
})
아래 예와 같이 익명 함수가 함수에 정의된 마지막 매개변수인 경우 함수를 호출하는 데 사용된 괄호 밖에서 함수를 전달할 수 있습니다.
stringMapper("Android") { input ->
input.length
}
익명 함수는 Kotlin 표준 라이브러리 전체에서 찾을 수 있습니다. 자세한 내용은 고차 함수 및 람다를 참조하세요.
클래스
지금까지 언급된 모든 유형은 Kotlin 프로그래밍 언어에 내장되어 있습니다. 아래 예와 같이 맞춤 유형을 추가하려는 경우 class
키워드를 사용하여 클래스를 정의할 수 있습니다.
class Car
속성
클래스는 속성을 사용하여 상태를 나타냅니다. 속성은 getter, setter 및 backing 필드를 포함할 수 있는 클래스 수준 변수입니다.
아래 예와 같이 자동차를 운전하려면 바퀴가 필요하므로 Wheel
객체 목록을 Car
의 속성으로 추가할 수 있습니다.
class Car {
val wheels = listOf<Wheel>()
}
wheels
는 public val
입니다. 즉, Car
클래스 외부에서 wheels
에 액세스할 수 있지만 재할당할 수는 없습니다. Car
의 인스턴스를 가져오려면 먼저 생성자를 호출해야 합니다. 그러면 액세스 가능한 모든 속성에 액세스할 수 있습니다.
val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car
바퀴를 맞춤설정하려면 클래스 속성을 초기화하는 방법을 지정하는 맞춤 생성자를 정의하면 됩니다.
class Car(val wheels: List<Wheel>)
위 예에서 클래스 생성자는 List<Wheel>
을 생성자 인수로 취하고 인수를 사용하여 wheels
속성을 초기화합니다.
클래스 함수 및 캡슐화
클래스는 함수를 사용하여 동작을 모델링합니다. 함수는 상태를 수정할 수 있으므로 노출하려는 데이터만 노출할 수 있습니다. 이 액세스 제어는 캡슐화라는 더 큰 객체 지향 개념의 일부입니다.
다음 예에서 doorLock
속성은 Car
클래스 외부의 모든 항목에서 비공개로 유지됩니다. 아래 예와 같이 자동차를 잠금 해제하려면 유효한 키를 전달하는 unlockDoor()
함수를 호출해야 합니다.
class Car(val wheels: List<Wheel>) {
private val doorLock: DoorLock = ...
fun unlockDoor(key: Key): Boolean {
// Return true if key is valid for door lock, false otherwise
}
}
속성을 참조하는 방법을 맞춤설정하려면 맞춤 getter 및 setter를 제공하면 됩니다. 예를 들어 속성의 setter에 액세스하는 것을 제한하면서 속성의 getter를 노출하려면 setter를 private
으로 지정합니다.
class Car(val wheels: List<Wheel>) {
private val doorLock: DoorLock = ...
var gallonsOfFuelInTank: Int = 15
private set
fun unlockDoor(key: Key): Boolean {
// Return true if key is valid for door lock, false otherwise
}
}
속성과 함수를 조합하여 모든 유형의 객체를 모델링하는 클래스를 만들 수 있습니다.
상호운용성
Kotlin의 가장 중요한 기능 중 하나는 자바와의 유연한 상호운용성입니다. Kotlin 코드는 JVM 바이트 코드로 컴파일되기 때문에 Kotlin 코드는 자바 코드로 직접 호출될 수 있으며 그 반대의 경우도 마찬가지입니다. 즉, 기존 자바 라이브러리를 Kotlin에서 직접 활용할 수 있습니다. 또한 대부분의 Android API는 자바로 작성되어 Kotlin에서 바로 호출할 수 있습니다.
다음 단계
Kotlin은 지원이 확대되고 성장세를 이어가고 있는 유연하고 실용적인 언어입니다. 아직 사용해보지 않으셨다면 시도해 보시기 바랍니다. 다음 단계는 공식 Kotlin 문서를 참고하세요. 신청 방법에 대한 가이드와 Android 앱의 일반적인 Kotlin 패턴입니다.