Jetpack Compose

Jetpack Compose을 위한 코틀린 문법 정리

junjunjun 2023. 7. 1. 00:24
반응형

본 글은 정확하지 않을 수 있습니다. 참고용으로만 봐주시면 감사하겠습니다.

 

Jetpack Compose 튜토리얼을 보고 정리한 글입니다.

자바를 어느 정도 아는 상태에서 정리하였습니다. 따라서 자세한 설명보다는 예시 코드 위주로 설명되어 있습니다.

여기서 코틀린 실습하실 수 있습니다.

 

목차

1. 기본 형식

2. 변수

3. 함수 1

4. if 문

    4.1. when 절

    4.2 in 절

    4.3 is 절

    4.4 조건문을 표현식으로 사용

5. null

6. 클래스

7. 공개 상태 수정자

8. 속성 위임 정의

9. 함수 2

10. 제네릭

11. 클래스 2

    enum 클래스

    data 클래스

    싱글톤 객체 사용

    companion 객체

    새 속성 및 메서드로 클래스 확장

12. 인터페이스

13. 범위함수

14. 배열

15. 컬렉션

    

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의 코틀린 문법을 정리하였습니다.

이 밖에도 다양한 문법이 존재하지만 추후 앱 개발을 진행하면서 필요해지면 그때그때 찾아서 공부할 예정입니다.

반응형