Jetpack Compose을 위한 코틀린 문법 정리
본 글은 정확하지 않을 수 있습니다. 참고용으로만 봐주시면 감사하겠습니다.
Jetpack Compose 튜토리얼을 보고 정리한 글입니다.
자바를 어느 정도 아는 상태에서 정리하였습니다. 따라서 자세한 설명보다는 예시 코드 위주로 설명되어 있습니다.
여기서 코틀린 실습하실 수 있습니다.
목차
1. 기본 형식
fun main() { // 메서드 등록시 fun 키워드 사용
println("Hello, world!!!") // 출력문, 세미콜론 생략 가능.
}
- 자바와 다르게 꼭 클래스 외부에 함수나 변수를 선언할 수 있다.
- 함수이름, 변수명에 대한 규칙은 자바와 같이 카멜 표기법 규칙을 따른다. ex) sendMsg()
2. 변수
종류
Kotlin 데이터 유형 | 포함할 수 있는 데이터 종류 | 리터럴 값 예시 |
String | 텍스트 | "Add contact" "Search" "Sign in" |
Int | 정수 | 32 1293490 -59281 |
Double | 십진수 | 2.0 501.0292 -31723.99999 |
Float | 십진수(Double보다 정밀도가 낮음). 숫자 끝에 f 또는 F가 있습니다. | 5.0f -1630.209f 1.2940278F |
Boolean | true 또는 false. 가능한 값이 두 개만 있을 때 이 데이터 유형을 사용합니다. true와 false는 Kotlin의 키워드입니다. | true false |
선언방법
fun main() {
val count: Int = 2 // val 변수이름: 변수타입 = 초기값
println(count)
println("My count is $count") // 문자열 내부에 변수 표시 (문자열 템플릿)
println("My count is ${count + count}") // 응용
println("My count is " + count) // 물론 이 방법도 가능
}
- val를 통해 Kotlin 컴파일러가 이 라인에 변수 선언이 있음을 인식한다.
유형추론
유형추론은 Kotlin 컴파일러가 유형을 명시적으로 코드에 작성하지 않고도 변수가 어떤 데이터 유형인지 추론하거나 결정할 수 있는 경우이다.
fun main() {
val count = 2 // 타입 생략 가능
val count: Int // 초기값을 지정하지 않을 경우에는 생략 불가!
}
변수 업데이트
fun main() {
val cartTotal = 0
cartTotal = 20 // !! Val cannot be reassigned 오류 발생
println("Total: $cartTotal")
}
- val로 선언된 변수는 재할당이 불가능하다. 따라서 val 대신 var을 쓰면 된다.
- val 키워드 : 변수 값이 변경되지 않을 것으로 예상하는 경우 사용합니다. =상수
- var 키워드 : 변수 값이 변경될 수 있을 것으로 예상하는 경우 사용합니다.
3. 함수 1
기본 형식
fun name() : return type {
return statement
}
반환타입
- Unit : 반환 유형을 지정할지 않을 경우 기본값
// 아래 두 개 동일
fun name() : Unit {}
fun name() {}
- ex) String일 경우
fun birthdayGreeting(): String {
val nameGreeting = "Happy Birthday, Rover!"
val ageGreeting = "You are now 5 years old!"
return "$nameGreeting\n$ageGreeting"
}
매개변수
fun main() {
println(birthdayGreeting("Name", 10))
}
// 매개변수가 있는 함수
fun birthdayGreeting(name: String, age: Int): String {
val nameGreeting = "Happy Birthday, $name!"
val ageGreeting = "You are now $age years old!"
return "$nameGreeting\n$ageGreeting"
}
매개변수의 경우 기본적으로 val로만 사용 가능하다.
var로 직접 선언해 준 결과 오류가 발생한다.
- 이름이 지정된 인수
println(birthdayGreeting(name = "Rex", age = 2)) //순서 무관
- 기본값 인수
// 매개변수 name에 "Rover"라는 디폴트값 지정
// 함수 호출시 name값을 인자값으로 지정해주지 않으면 "Rover"값이 들어온다.
fun birthdayGreeting(name: String = "Rover", age: Int): String {
return "Happy Birthday, $name! You are now $age years old!"
}
4. if문
자바와 동일하다.
4.1. when 절 : 조건문이 여러 개일 때 사용, switch문과 비슷
고려할 브랜치(if else)가 3개 이상일 경우 when 문을 사용하는 것이 좋다.
// 기본 구조
when( parameter) {
condition1 -> body1
condition2 -> body2
condition3 -> body3
else ->
}
// 사용 사례
val trafficLightColor = "Yellow"
when (trafficLightColor) {
"Red", "Black" -> println("Stop") // 쉼표로 여러 조건문 달 수 있다.
"Yellow" -> println("Slow")
"Green" -> println("Go")
else -> println("Invalid traffic-light color") // default
}
4.2. in 절 사용으로 여러 조건 적용
fun main() {
val x = 3
when (x) {
2, 3, 5, 7 -> println("hello")
in 1..10 -> println("hello") // 1 <= x <= 10 과 동일하다.
else -> println("hello")
}
}
in 절은 컬렉션, 배열 등에서 요소가 내부에 속해있는지 검사하는 데 사용된다.
4.3 is 키워드를 사용하여 데이터 유형 확인
fun main() {
val x: Any = 20 // Any는 자바의 Object 타입이다.
when (x) {
2, 3, 5, 7 -> println("x")
in 1..10 -> println("x")
is Int -> println("xx") // Int타입으로 걸린다.
else -> println("x.")
}
}
is 키워드는 객체가 지정한 클래스의 인스턴스인지를 확인하는 데 사용된다.
4.4 조건문을 표현식으로 사용
// --- 수정 전 ---
fun main() {
val trafficLightColor = "Black"
if (trafficLightColor == "Red") {
println("Stop")
} else if (trafficLightColor == "Yellow") {
println("Slow")
} else if (trafficLightColor == "Green") {
println("Go")
} else {
println("Invalid traffic-light color")
}
}
// --- 표현식으로 수정 후 ---
fun main() {
val trafficLightColor = "Black"
val message =
if (trafficLightColor == "Red") "Stop"
else if (trafficLightColor == "Yellow") "Slow"
else if (trafficLightColor == "Green") "Go"
else "Invalid traffic-light color"
println(message)
}
// +++ when 절도 비슷하다 +++
fun main() {
val trafficLightColor = "Amber"
val message = when(trafficLightColor) {
"Red" -> "Stop"
"Yellow", "Amber" -> "Proceed with caution."
"Green" -> "Go"
else -> "Invalid traffic-light color"
}
println(message)
}
표현식을 사용하면 코드가 좀 더 간결해진다.
5. null
Kotlin에서 null 안전을 보장하기 위해 null 허용 여부를 의도적으로 처리한다.
null을 허용하는 유형의 변수와 혀용 하지 않는 유형의 변수가 있다.
- null을 허용하지 않는 변수
fun main() {
var favoriteActor: String = "Sandra Oh"
favoriteActor = null // !!오류 발생 Null can not be a value of a non-null type String
}
- null을 허용하는 변수
fun main() {
var favoriteActor: String? = "Sandra Oh" // ? 추가
favoriteActor = null
}
? 연산자를 추가해 주면 null을 허용하는 변수가 된다.
- 하지만 해당 변수의 속성에 접근하면 에러가 발생한다.
fun main() {
var favoriteActor: String? = "Sandra Oh" // ? 추가
print(favoriteActor.length) // !!에러발생
}
런타임 에러를 사전에 방지하여 null값에 접근할 수 없도록 사전에 컴파일 에러를 발생시킨다.
- 그렇다면 속성에는 어떻게 접근할 수 있을까?
// 방법1 ?. 사용
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor?.length) // null일 경우 null 출력
}
// 방법2 !!. 사용
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor!!.length) // null일 경우 NullPointerException 발생
}
// 그 밖에 if문 사용
fun main() {
val favoriteActor: String? = "Sandra Oh"
val lengthOfName = if(favoriteActor != null) {
favoriteActor.length
} else {
0
}
}
그 밖에 Elvis 연산자가 있다
Elvis 연산자는 ?: 로 표현하며, ?. 안전 호출 연산자와 함께 사용하며, 안전 호출자가 null을 반환할 때 기본값을 추가할 수 있다.
fun main() {
val favoriteActor: String? = "Sandra Oh"
val lengthOfName = favoriteActor?.length ?: 0 // null일 경우 0 반환
}
6. 클래스
기본적인 사용 방법은 java와 유사하다.
class SmartDevice {
// 속성
val name = "Android TV"
val category = "Entertainment"
var deviceStatus = "online"
// 멤버 함수
fun turnOn() {
println("Smart device is turned on.")
}
}
fun main() {
val a = SmartDevice() // 인스턴스 생성, new를 안 써도 됨
a.turnOn()
}
getter, setter 메서드
var로 선언된 변수는 컴파일 시점에 자동으로 getter, setter 메서드를 생성한다.
class SmartDevice {
var speakerVolume = 2
// get() = field 컴파일 시점에 자동으로 생성됨
// set(value) { 컴파일 시점에 자동으로 생성됨
// field = value
// }
}
fun main() {
val a = SmartDevice()
println(a.name); // getter 사용
a = 4; // setter 사용
}
- val로 선언된 변수는 setter가 없다.
- getter, setter를 커스텀할 경우에는 위 코드와 같이 형식으로 구현하면 된다.
생성자
Kotlin의 생성자에는 기본 생성자와 보조 생성자 두 가지 기본 유형이 있다.
1. 기본 생성자
클래스에는 클래스 헤더의 일부로 정의된 기본 생성자가 하나만 있을 수 있다.
- 기본 형태
class SmartDevice constructor() { // constructor 생략 가능
...
}
- 매개변수가 있는 형태
// 수정 전 : 생성자가 없는 버전
class SmartDevice {
val name = "Android TV"
val category = "Entertainment"
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
}
// 수정 후 : 기본 생성자가 있는 버전
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
}
// 사용법
fun main() {
val a = SmartDevice("Android TV", "Entertainment")
}
불변 변수만 따로 매개변화된 생성자를 통해 값을 초기화시키는 것을 권장한다.
2. 보조 생성자
한 클래스에 여러 보조 생성자가 있을 수 있다. 매개변수를 포함하거나 포함하지 않고 보조 생성자를 정의할 수 있다.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
// constructor 키워드 사용, this로 위에 기본 생성자 선언해줘야 된다.
constructor(name: String, category: String, statusCode: Int) : this(name, category) {
...
}
...
}
- this 키워드로 기본 생성자를 초기화시켜줘야 한다.
- 기본 생성자가 없다면 this부분을 생략할 수 있다.
상속
- 슈퍼 클래스
// 1. 매개변수가 있는 슈퍼 클래스
open class SmartDevice(val name: String, val category: String) {
...
}
// 2. 매개변수가 없는 슈퍼 클래스
open class SmartDevice {
...
}
- open 키워드를 통해 컴파일러에게 해당 클래스를 다른 클래스가 상속할 수 있게 알려준다.
- 서브 클래스
// 1. 매개변수가 있는 슈퍼 클래스 상속
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory)
// 필요한 경우 슈퍼클래스 괄호에는 슈퍼클래스 생성자에 필요한 매개변수가 포함
}
// 2. 매개변수가 없는 슈퍼 클래스 상속
class SmartTvDevice : SmartDevice()
}
- 서브 클래스에서 슈퍼 클래스 메서드 재정의
// 슈퍼클래스
open class SmartDevice(val name: String, val category: String) {
open fun turnOn() { // 메서드에도 open 키워드 추가
// function body
}
}
// 서브 클래스
class SmartLightDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
override fun turnOn() { // override가 붙음
// function body
}
}
- override 키워드는 서브 클래스의 정의된 메서드를 실행하도록 Kotlin 런타임에 알린다.
- super 키워드를 사용하여 서브 클래스에서 슈퍼 클래스 코드 재사용
java와 동일, super.method() 로 접근 가능
- 서브 클래스에서 슈퍼 클래스 속성 재정의
// 슈퍼클래스
open class SmartDevice(val name: String, val category: String) {
open val deviceType = "unknown" // open 붙여서 재정의 허용
}
// 서브 클래스
class SmartLightDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
override val deviceType = "Smart TV" // override 붙여서 재정의
}
- java에서는 final이 붙으면 상속, 재정의가 불가능했다. 확실하지 않지만 기본적으로 코틀린의 변수는 final 형태인 것으로 추측된다.
7. 공개 상태 수정자
- public(디폴트) : 모든 위치에서 선언에 액세스 할 수 있도록 한다.
- private : 동일한 클래스 또는 소스 파일에서 선언에 액세스 할 수 있도록 한다.
- protected : 서브 클래스에서 선언에 액세스 할 수 있도록 한다.
- internal : 동일한 모듈에서 선언에 엑세스할 수 있도록 한다.
패키지는 기본적으로 관련 클래스를 그룹화하는 디렉터리나 폴더이고, 모듈은 앱의 소스 코드, 리소스 파일, 앱 수준 설정을 위한 컨테이너를 제공합니다. 한 모듈에 여러 패키지가 포함될 수 있습니다.
// 공개 상태 수정자를 사용한 예시 코드
open class SmartDevice protected constructor (val name: String, val category: String) {
...
private var hello = "hello" // 변수 공개 상태 수정자 지정
var deviceStatus = "online"
protected set(value){ // setter에 공개 상태 수정자지정
field = value
}
// protected set 로 줄여서 사용할 수 있다.
protected fun nextChannel() { // 메서드에 공개 상태 수정자 지정
channelNumber++
}
}
open class SmartDevice protected constructor() // 생성자에 공개 상태 수정자 지정
internal open class SmartDevice {} // 클래스의 공개 상태 수정자 지정
- 속성과 메서드의 공개 상태를 엄격하게 유지하는 것이 이상적이므로 최대한 자주 private 수정자를 사용하여 이를 선언하는 것을 권장한다.
- 비공개로 유지할 수 없는 경우 protected 수정자를 권장한다.
- 보호 상태로 유지할 수 없는 경우 internal 수정자를 권장한다.
- 내부에 유지할 수 없는 경우 public 수정자를 권장한다.
수정자 | 동일한 클래스에서 액세스 가능 | 서브클래스에서 액세스 가능 | 동일한 모듈에서 액세스 가능 | 모듈 외부에서 액세스 가능 |
private | ✔ | 𝗫 | 𝗫 | 𝗫 |
protected | ✔ | ✔ | 𝗫 | 𝗫 |
internal | ✔ | ✔ | ✔ | 𝗫 |
public | ✔ | ✔ | ✔ | ✔ |
8. 속성 위임 정의
- 행위를 위임하는 기능을 제공하는 문법이다.
- 속성 위임은 by 키워드를 사용한다.
// 속성 by 기본 형태
var name by 대리자 객체
// ------ 속성 by ------
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class SmartDevice(val name: String, val category: String){}
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
// channelNumber변수는 by로 RangeRegulator을 위임 받는다.
private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 3)
fun upChannel() {
channelNumber++ // 위임한 RangeRegulator클래스의 setValue가 실행됨
// 멤버 변수 값이 변경되면 setter발생 -> setter를 위임한 setValue가 실행
}
}
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> { // var 유형의 위임 클래스
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int { // getter
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { // setter
println("안녕")
if (value in minValue..maxValue) {
fieldData = value
}
}
}
fun main() {
val a: SmartTvDevice = SmartTvDevice("Asd", "asd")
a.upChannel() // "안녕"이 찍힌다.
}
//======================================================================
// ------ 클래스 by ------
interface Worker {
fun work()
fun takeVacation()
}
open class JavaProgrammer : Worker {
override fun work() = println("code with Java")
override fun takeVacation() = println("code at the beach")
}
class Manager() : Worker by JavaProgrammer() // JavaProgrammer구현체를 가지는 효과
// 클래스 정의 수준에서 사용되는 코틀린의 by 키워드의 왼쪽에는 인터페이스가,
// 오른쪽엔 해당 인터페이스를 구현한 클래스가 필요합니다.
fun main() {
val manager = Manager()
manager.work() // 사실상 JavaProgrammer.work() 실행
}
- var 유형의 위임 클래스를 만들기 위해서는 ReadWriteProperty 인터페이스를 구현해야 한다.
- val 유형의 위임 클래스를 만들기 위해서는 ReadOnlyProperty 인터페이스를 구현해야 한다.
코틀린은 클래스 멤버변수 값이 변경될 때 setter메서드가 실행된다.
9. 함수 2
변수에 함수를 저장할 수 있다.
코틀린에서는 함수도 데이터 유형이다. 따라서 변수에 함수를 저장하고 저장된 함수를 다른 함수에 전달하고 그것을 함수에서 반환할 수 있다.
// 에러가 발생하는 코드
fun main() {
val trickFunction = trick
}
fun trick() {
println("No treats")
}
Kotlin 컴파일러에서 trick을 trick() 함수의 이름으로 인식하지만 변수에 함수가 할당되는 대신 함수가 호출될 것으로 예상하기 때문에 오류가 발생한다.
- :: 연산자를 사용하면 해결된다.
// 수정된 코드
fun main() {
val trickFunction = ::trick
}
fun trick() {
println("No treats")
}
- 람다 표현식을 사용하면 ::연산자를 생략해도 된다.
fun main() {
val trickFunction = trick // 함수를 참조하는 변수를 참조함 따라서 ::연산자 필요없어짐
trick() // 람다 함수 사용법
trickFunction() // 함수를 가진 변수 사용법
}
// 람다 함수
val trick = {
println("No treats!")
}
- 람다 표현식에 매개변수나 반환값이 있는 경우
함수의 매개변수 유형이나 반환 유형을 지정하기 위해 유형 추론을 지정해줘야 한다.
// 아래 두 변수는 동일하다.
// 매개변수와 반한타입이 없는 경우 () -> Unit을 생략할 수 있다.
val trick = {
println("No treats!")
}
val treat: () -> Unit = {
println("Have a treat!")
}
------------------------
// Int 반환 예시
val trick: () -> Int = {
println("One treats!")
1 // return 안 씀. 마지막 줄을 return으로 취급한다.
}
// 매개변수 받는 예시
val trick: (Int, String) -> Int = { age, name ->
println("One treats $name!")
age + 10
}
- 함수를 반환 유형으로 사용
fun main() {
val result = trickOrTreat(true)
result(); // No treats!
}
fun trickOrTreat(isTrick: Boolean): () -> Unit { // 반환되는 함수 유형추론
if (isTrick) {
return trick // 함수 반환
} else {
return treat // 함수 반환
}
}
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- 함수를 다른 함수에 인수로 전달
// 매개변수가 int이고 반환타입이 String인 함수를 매개인자로 받을 수 있다.
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
if (isTrick) {
return trick
} else {
println(extraTreat(5))
return treat
}
}
// 사용법
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false, coins)
treatFunction()
}
- null을 허용하는 함수 유형
함수도 null을 허용하고 싶을 경우 ? 연산자를 붙여주면 된다.
// 괄호로 감싸고 ?를 붙여주면 된다.
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {}
// 함수 변수에도 null 허용 가능
val trick: (() -> Unit)? = {
println("tricl");
}
fun main() {
val treatFunction = trick
treatFunction?.invoke() // nullable변수처럼 ?붙이고 직접 메서드로 실행시켜야됨
}
- 약식 문법으로 람다 표현식 작성
매개변수가 하나일 경우 매개변수 이름 대신에 it 키워드를 사용할 수 있다.
// 수정 전, 조건 : 매개변수가 하나일 경우
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
// 수정 후
val coins: (Int) -> String = {
"$it quarters"
}
- 람다 표현식을 함수에 직접 전달
// 수정 전
fun main() {
val coins: (Int) -> String = {
"$it quarters"
}
val treatFunction = trickOrTreat(false, coins)
treatFunction()
}
// 수정 후
fun main() {
val treatFunction = trickOrTreat(false, { "$it quarters" })
treatFunction()
}
// 공용
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
if (isTrick) {
return trick
} else {
println(extraTreat(5))
return treat
}
}
- 후행 람다 문법 사용
함수 매개변수가 함수의 마지막 매개변수일 때 사용 가능하다.
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
treatFunction()
}
- repeat() 함수 사용
- 함수가 함수를 반환하거나 또는 함수를 인수로 취하는 경우 이를 고차 함수 라고 한다. repeat도 고차함수 중 하나이다.
- repeat() 함수는 함수로 for 루프를 표현할 수 있는 간결한 방법이다.
// 형식
repeat(times: Int, action: (Int) -> Unit)
// 사용 예시
repeat(4) {
println("hello")
}
10. 제네릭
전체적으로 자바랑 사용방법은 동일하다.
// 예시 코드
fun main() {
val question1 = Question<String>("Quoth the raven ___", "nevermore", "medium")
val question2 = Question<Boolean>("The sky is green. True or false", false, "easy")
val question3 = Question<Int>("How many days are there between full moons?", 28, "hard")
question1.print()
question2.print()
question3.print()
}
class Question<T>(
val questionText: String,
val answer: T,
val difficulty: String
) {
fun print() {
println("$answer")
}
}
11. 클래스 2
enum 클래스
- enum 클래스는 가능한 값 집합이 제한되어 있는 유형을 만드는 데 사용된다.
- 전체적으로 자바와 동일하다.
enum class Difficulty {
EASY, MEDIUM, HARD
}
// 사용법
Difficulty.EASY
데이터 클래스
- 데이터만 있고 실행 메서드가 없는 클래스를 데이터 클래스로 정의할 수 있다.
- 클래스를 데이터 클래스로 정의하면 Kotlin 컴파일러에서 특정 가정을 하고 일부 메서드를 자동으로 구현할 수 있다. ex) toString(), equeals() 등등
// 변경 전
class Question<T>(
val questionText: String,
val answer: T,
val difficulty: String
)
// 변경 후
data class Question<T>(
val questionText: String,
val answer: T,
val difficulty: Difficulty
)
참고 : 데이터 클래스에는 생성자에 매개변수가 하나 이상 있어야 하며, 모든 생성자 매개변수는 val 또는 var로 표시되어햐 한다. 데이터 클래스는 abstract 또는 open, sealed, inner일 수 없다.
싱클톤 객체 사용
- 클래스에 인스턴스 하나만 사용할 경우 사용된다.
- class 키워드 대신에 object 키워드를 사용하면 된다.
- 싱글톤 객체는 개발자가 인스턴스를 직접 만들 수 없기 때문에 생성자를 포함할 수 없다. 대신 모든 속성이 중괄호 안에 정의되며 초기값이 부여된다.
object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
// 접근 방법
fun main() {
print("$StudentProgress.total")
}
companion 객체
- 다른 클래스 내에서 싱글톤 객체를 정의할 수 있다.
- 객체의 속성과 메서드가 해당 클래스에 속한 경우 사용한다.
- (확실하지 않지만, static 멤버변수, static 멤버함수 역할을 한다고 이해하자.)
class Quiz {
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
companion object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
}
// 사용법
fun main() {
println("${Quiz.answered} of ${Quiz.total} answered.") // 인스턴스 생성없이 바로 접근 가능하다
}
새 속성 및 메서드로 클래스 확장
- 기존 데이터 유형을 확장하여 해당 데이터 유형의 일부인 것처럼 점(.) 문법으로 액세스 할 수 있는 속성과 메서드를 추가할 수 있다.
// ===== 기본 사용법 =====
class Quiz {
}
val Quiz.progressText: String // 확장 속성
get() = "안녕"
fun Quiz.printProgressBar() { // 확장 메서드
println("잘가") // 확장 속정 사용버
}
fun main() {
val quiz = Quiz()
print(quiz.progressText) // 확장 속성 사용
quiz.printProgressBar() // 확장 메서드 사용
}
================================================
// ===== 응용 사용법 =====
class Quiz {
companion object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
}
val Quiz.StudentProgress.progressText: String // 확장 속성
get() = "${answered} of ${total} answered"
fun Quiz.StudentProgress.printProgressBar() { // 확장 메서드
repeat(Quiz.answered) { print("") }
repeat(Quiz.total - Quiz.answered) { print("") }
println()
println(Quiz.progressText) // 확장 속정 사용버
}
fun main() {
Quiz.printProgressBar() // 확장 메서드 사용법
}
12. 인터페이스
자바랑 동일하다.
// 구현
interface ProgressPrintable {
val progressText: String
fun printProgressBar()
}
// 사용법
class Quiz : ProgressPrintable {
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
companion object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
override val progressText: String // 구현
get() = "${answered} of ${total} answered"
override fun printProgressBar() { // 구현
repeat(Quiz.answered) { print("") }
repeat(Quiz.total - Quiz.answered) { print("") }
println()
println(progressText)
}
}
13. 범위함수
- 범위 함수는 개발자가 객체의 이름을 참조하지 않고 객체의 속성 및 메서드에 액세스 할 수 있도록 하는 고차 함수이다.
- 코드를 간결하게 사용하기 위한 용도이다.
- 범위함수로는 let, with, run, apply, also가 있다.
// === let을 사용하지 않았을 경우 ===
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
// === let을 사용한 예시 코드 ===
Person("Alice", 20, "Amsterdam").let { // 객체를 변수에 선언하지 않아도됨
println(it)
it.moveTo("London")
it.incrementAge()
println(it)
}
let을 적용한 코드는 조금 더 간결해진다.
14. 배열
val rockPlanets = arrayOf<String>("Mercury", "Venus", "Earth", "Mars") // 배열 선언 타입 지정
val gasPlanets = arrayOf("Jupiter", "Saturn", "Uranus", "Neptune") // 배열 선언 타입 생략
val solarSystem = rockPlanets + gasPlanets // 배열 덧셈이 가능하다.
println(solarSystem[0]) // 인덱스 접근
solarSystem[3] = "Little Earth" // 값 변경
15. 컬렉션
대부분 컬렉션 사용방법은 자바랑 동일하다.
목록(List)
읽기 전용인 List와 추가 및 삭제 수정이 가능한 MutableList가 있다.
// === List 사용법, 변경,추가,삭제 불가 ===
val solarSystem = listOf<String>("Mercury","Jupiter", "Neptune") // 선언,<String> 생략가능'
println(solarSystem.size) // 크기 조회
println(solarSystem[2]) // 인덱스 조회
println(solarSystem.get(3)) // 인덱스 조회
for (planet in solarSystem) { // 전체 조회 for문 사용
println(planet)
}
// === mutableListOf 사용법 ===
val solarSystem = mutableListOf("Mercury", "Uranus", "Neptune") // 선언
solarSystem.add("Pluto") // 추가
solarSystem.add(3, "Theia")
solarSystem[3] = "Future Moon" // 변경
solarSystem.removeAt(1) // 삭제
solarSystem.remove("Future Moon")
세트(Set)
- Set 과 MutableSet이 있다.
- 사용법은 리스트와 동일하다.
맵(Map)
- mapOf() 또는 mutableMapOf()을 사용한다.
val solarSystem = mutableMapOf(
"Mercury" to 0,
"Venus" to 0,
"Earth" to 1,
"Mars" to 2,
"Jupiter" to 79,
"Saturn" to 82,
"Uranus" to 27,
"Neptune" to 14
)
solarSystem["Pluto"] = 5 // 값이 없으면 추가, 있으면 수정
solarSystem.put("hello", 10)
println(solarSystem["Pluto"]) // 키값으로 value 조회, 키값없으면 null 반환
print(solarSystem.get("Earth"))
solarSystem.remove("Pluto") // 삭제
컬렉션을 사용한 고차 함수
- java의 stream과 같은 기능이다.
- forEach()를 사용하여 컬렉션의 각 요소를 반복할 수 있다.
- map()은 종종 다른 데이터 유형의 컬렉션으로 컬렉션의 항목 형식을 지정하는 데 사용된다.
- filter()는 컬렉션의 하위 집합을 생성할 수 있다.
- groupBy()는 함수 반환 값을 기준으로 컬렉션을 분할한다.
- fold()는 컬렉션을 단일 값으로 변환한다.
- sortedBy()는 지정된 속성별로 컬렉션을 정렬하는 데 사용된다.
여기까지 Jetpack Compose의 코틀린 문법을 정리하였습니다.
이 밖에도 다양한 문법이 존재하지만 추후 앱 개발을 진행하면서 필요해지면 그때그때 찾아서 공부할 예정입니다.