어플리케이션, 앱 (Application)/안드로이드 (Android)

안드로이드 jetpack compose 공부 정리 3일차 (코틀린, 함수와 클래스)

sobal 2025. 5. 29. 21:43

함수와 클래스 

3일차는 함수와 클래스에 대한 내용이다.

1. 함수 (Function) 

함수는 특정 작업을 수행하는 코드 블록이다.

  • fun 키워드를 사용하여 정의한다.
  • 함수명, 변수명 규칙이 동일하다. (camelCase 권장).
  • main() 함수는 프로그램의 시작점이다.

1.1. 함수 정의

fun 함수이름(파라미터1: 타입, 파라미터2: 타입, ...): 반환타입 {
    // 함수 내용
    return 반환값 // 반환타입이 있을 시
}

// 반환값이 없으면 (Unit) 반환 타입 생략 가능
fun printMessage(message: String) { // : Unit이 원래 있지만 생략
    println(message)
}

1.2. 함수 호출

함수이름(인자1, 인자2, ...)

1.3. 함수 예제

fun greet(name: String): String {
    return "Hello, $name!"
}

fun main() {
    val message = greet("Kotlin")
    println(message) // Output: Hello, Kotlin!
    printMessage("Just a message.") // Output: Just a message.
}

1.4. 단일 표현식 함수 (Single-Expression Function)

함수 본문이 단일 표현식일 경우 아래와 같이 간단하게 표현할 수 있다.

// 일반 함수
fun double(x: Int): Int {
    return x * 2
}

// 단일 표현식 함수 (반환 타입 추론 가능)
fun double(x: Int) = x * 2

// 반환 타입 명시도 가능
fun double(x: Int): Int = x * 2

1.5. 파라미터 (Parameters, 매개변수)

  • 함수에 전달되는 입력값이다.
  • 각 파라미터는 이름과 타입이 필요하다.
  • 파라미터 목록은 괄호 안에 쉼표로 구분하여 나열한다.
  • 예시: fun power(base: Int, exponent: Int): Int

기본 파라미터 (Default Parameters)

fun greet(name: String, greeting: String = "Hello") {
    println("$greeting, $name!")
}

fun main() {
    greet("Amy")              // Output: Hello, Amy!
    greet("Bob", "Hi")          // Output: Hi, Bob!
}

명명된 인자 (Named Arguments)

fun createUser(name: String, age: Int, email: String) {
    println("이름: $name, 나이: $age, 이메일: $email")
}

fun main() {
    // 순서 상관없이 매개변수 이름으로 호출 가능
    createUser(email = "amy@example.com", name = "Amy", age = 25)
}

1.6. 반환 타입 (Return Type)

  • 함수가 반환하는 값의 타입이다.
  • 반환 타입은 함수명 뒤에 콜론(:)과 함께 명시한다.
  • 반환할 것이 없으면 Unit이고 보통 생략한다. (위 printMessage 함수 참고)
  • 예시: fun average(numbers: List<Double>): Double

1.7. 함수 오버로딩 (Overloading)

같은 이름의 함수를 여러 개 정의할 수 있다. (단, 파라미터 타입이나 개수가 달라야 함) 컴파일러는 호출 시 인자의 타입이나 개수를 기반으로 적절한 함수를 선택한다.

fun add(a: Int, b: Int): Int {
    println("Int 덧셈 호출됨")
    return a + b
}

fun add(a: Double, b: Double): Double {
    println("Double 덧셈 호출됨")
    return a + b
}

fun main() {
    println(add(5, 3))          // Output: Int 덧셈 호출됨 \n 8
    println(add(3.14, 2.71))    // Output: Double 덧셈 호출됨 \n 5.85
}

1.8. 함수 예제 2 (파라미터, 반환 타입 활용)

fun makeCoffee(sugarCount: Int, name: String): String {
    return "$name 커피, 설탕 $sugarCount 스푼."
}

fun calculateArea(width: Double, height: Double): Double {
    return width * height
}

fun main() {
    val order = makeCoffee(2, "모카")
    println(order) // Output: 모카 커피, 설탕 2 스푼.

    val area = calculateArea(5.0, 3.0)
    println("면적: $area") // Output: 면적: 15.0
}

2. 클래스 (Class) 🏗️

객체(Object)를 생성하기 위한 설계도이다. 클래스는 속성(Properties)과 기능(Methods)을 가진다.

2.1. 클래스 정의

// 주 생성자 파라미터에 val/var 사용 시 바로 프로퍼티(속성)로 선언됨
class 클래스이름(val 속성1: 타입, var 속성2: 타입, ...) {
    // 멤버 (추가 속성, 함수, 초기화 블록 등)
}

2.2. 객체 생성 (인스턴스화)

val 객체이름 = 클래스이름(인자1, 인자2, ...)

2.3. 클래스 예제

class Dog(val name: String, val breed: String, var age: Int = 0) { // 주 생성자 + 프로퍼티

    // 초기화 블록 (객체 생성 시 가장 먼저 실행)
    init {
        println("$name ($breed) 등장!")
    }

    // 멤버 함수 (Methods)
    fun bark() {
        println("$name: 멍멍!")
    }

    fun eat(food: String) {
        println("$name이(가) $food을(를) 먹는 중입니다.")
    }

    // 멤버 프로퍼티 (Properties)
    var weight: Double = 0.0
        // Custom Getter
        get() {
            // println("$name 몸무게 확인: $field kg") // 필요시 로그 추가
            return field // field: 실제 프로퍼티 값을 가리키는 백킹 필드
        }
        // Custom Setter
        set(value) {
            if (value >= 0) {
                // println("$name 몸무게 ${value}kg 설정 시도.") // 필要시 로그 추가
                field = value
            } else {
                println("몸무게는 음수가 될 수 없습니다 ($value).")
            }
        }
}

fun main() {
    val daisy = Dog("데이지", "푸들", 1) // init 블록 실행
    // Output: 데이지 (푸들) 등장!

    println("${daisy.name}는 ${daisy.breed}이며 ${daisy.age}살입니다.")
    // Output: 데이지는 푸들이며 1살입니다.
    daisy.bark() // Output: 데이지: 멍멍!

    daisy.eat("사료") // Output: 데이지이(가) 사료을(를) 먹는 중입니다.
    daisy.weight = 10.5 // Setter 호출
    println("${daisy.name} 몸무게: ${daisy.weight} kg.") // Getter 호출
    // Output: 데이지 몸무게: 10.5 kg.

    daisy.weight = -1.0 // Setter 호출 (음수 값)
    // Output: 몸무게는 음수가 될 수 없습니다 (-1.0).
    println("${daisy.name} 몸무게: ${daisy.weight} kg.") // Getter 호출 (값 변경 안됨)
    // Output: 데이지 몸무게: 10.5 kg.
}

주의: 프로퍼티의 getter/setter 내부에서 프로퍼티 자신을 직접 참조하면 무한 루프가 발생한다. (예: get() = weight). 반드시 field 키워드를 사용해야 한다.

2.4. 생성자 (Constructor)

클래스의 객체를 생성할 때 호출되는 특수한 함수. 주 생성자(Primary Constructor)와 부 생성자(Secondary Constructor)가 있다.

1. 주 생성자 (Primary Constructor)

  • 클래스명 뒤 괄호 안에 정의한다.
  • 클래스 정의 시 단 하나만 가질 수 있다.
  • init 블록을 사용하여 주 생성자의 초기화 작업을 수행한다.
class Person(val name: String, var age: Int) {
    init { // 주 생성자 로직은 init 블록 안에
        if (age < 0) {
            throw IllegalArgumentException("나이는 음수가 될 수 없습니다.")
        }
        println("$name ($age 살) 생성됨.")
    }
}

fun main() {
    val john = Person("John", 30)
    // Output: John (30 살) 생성됨.
    println(john.name) // Output: John
    println(john.age)  // Output: 30

    // val invalidPerson = Person("Jane", -1) // 예외 발생: 나이는 음수가 될 수 없습니다.
}

2. 부 생성자 (Secondary Constructor)

  • 클래스 내부에 constructor 키워드로 정의한다.
  • 여러 개 가질 수 있다.
  • 부 생성자는 다른 생성자(주 또는 다른 부 생성자)를 호출해야 한다 (this() 사용).
class Book(val title: String, val author: String) { // 주 생성자
    var price: Int = 0

    // 부 생성자: 주 생성자를 반드시 호출해야 함 (this(title, author))
    constructor(title: String, author: String, price: Int) : this(title, author) {
        this.price = price // 부 생성자 고유 로직
    }
}

fun main() {
    val book1 = Book("반지의 제왕", "J.R.R. 톨킨")
    println("${book1.title} - 가격: ${book1.price}")
    // Output: 반지의 제왕 - 가격: 0

    val book2 = Book("오만과 편견", "제인 오스틴", 15000)
    println("${book2.title} - 가격: ${book2.price}")
    // Output: 오만과 편견 - 가격: 15000
}

3. 주 생성자와 부 생성자 함께 사용

class Car(val model: String, val year: Int) { // 주 생성자
    var color: String = "Unknown" // 프로퍼티 기본값

    // 부 생성자: 주 생성자 호출 후 추가 작업
    constructor(model: String, year: Int, color: String) : this(model, year) {
        this.color = color
    }
}

fun main() {
    val car1 = Car("테슬라 모델S", 2022)
    println("${car1.model} (${car1.year}) 색상: ${car1.color}")
    // Output: 테슬라 모델S (2022) 색상: Unknown

    val car2 = Car("현대 소나타", 2023, "Silver")
    println("${car2.model} (${car2.year}) 색상: ${car2.color}")
    // Output: 현대 소나타 (2023) 색상: Silver
}

4. 기본 생성자 (Default Constructor)

클래스에 생성자를 명시적으로 정의하지 않으면 컴파일러가 자동으로 생성한다. 매개변수가 없는 형태.

class Mouse { // 생성자 없음 -> 기본 생성자 자동 추가됨
    fun click() = println("마우스 클릭!")
}

fun main() {
    val myMouse = Mouse() // 기본 생성자 호출
    myMouse.click() // Output: 마우스 클릭!
}

5. private 생성자

클래스 외부에서의 객체 생성을 방지하기 위해 사용한다. 주로 싱글톤(Singleton) 패턴 구현 시 활용한다.

class Database private constructor() { // private 생성자
    init {
        println("데이터베이스 인스턴스가 생성되었습니다.")
    }

    companion object { // 동반 객체 (자바의 static 멤버와 유사)
        private var instance: Database? = null

        fun getInstance(): Database {
            if (instance == null) {
                instance = Database() // 클래스 내부에서는 private 생성자 호출 가능
            }
            return instance!!
        }
    }

    fun connect() = println("데이터베이스에 연결됩니다.")
}

fun main() {
    val db1 = Database.getInstance() // Output: 데이터베이스 인스턴스가 생성되었습니다.
    db1.connect() // Output: 데이터베이스에 연결됩니다.

    val db2 = Database.getInstance() // 이미 생성된 인스턴스 반환
    db2.connect() // Output: 데이터베이스에 연결됩니다.

    // val db = Database() // 에러: private 생성자는 외부에서 호출 불가
    println(db1 === db2) // Output: true (같은 인스턴스), 참조 동등성 확인
}

다양한 생성자를 활용하여 유연한 클래스 설계가 가능하다.

2.5. 멤버 (Members)

클래스 내부에 정의된 속성(프로퍼티), 함수(메서드), 초기화 블록 등을 말한다.

2.6. 프로퍼티 (Properties)

  • 클래스의 속성을 의미한다.
  • val(읽기 전용) 또는 var(읽기/쓰기)로 선언한다.
  • 프로퍼티는 getter/setter를 가진다 (자동 생성).
  • 필요시 getter/setter를 직접 정의할 수 있다. (위 Dog 클래스의 weight 프로퍼티 참고)

3. 데이터 클래스 (Data Class) 

데이터 저장/보관 목적의 클래스이다.

  • data 키워드를 사용하여 정의한다.
  • equals(), hashCode(), toString(), copy(), componentN() 등이 자동으로 생성된다. (보일러플레이트 코드 감소)

3.1. 데이터 클래스 정의

data class 데이터클래스이름(val 속성1: 타입, val 속성2: 타입, ...)

3.2. 데이터 클래스 예제

data class CoffeeDetails(
    val sugarCount: Int,
    val name: String,
    val size: String,
    val creamAmount: Int
)

fun main() {
    val coffeeForDenis = CoffeeDetails(0, "Denis", "XXL", 1)
    println(coffeeForDenis) // toString() 자동 호출
    // Output: CoffeeDetails(sugarCount=0, name=Denis, size=XXL, creamAmount=1)

    // 데이터 클래스 객체 복사 (copy() - 일부 값 변경 가능)
    val coffeeForJohn = coffeeForDenis.copy(name = "John", sugarCount = 2)
    println(coffeeForJohn)
    // Output: CoffeeDetails(sugarCount=2, name=John, size=XXL, creamAmount=1)

    // 데이터 클래스 객체 분해 (Destructuring Declarations - componentN() 함수들 사용)
    val (sugar, name, size, cream) = coffeeForDenis
    println("$name 커피: 설탕 $sugar, 사이즈 $size, 크림 $cream")
    // Output: Denis 커피: 설탕 0, 사이즈 XXL, 크림 1

    makeCoffeeWithDetails(coffeeForDenis)
    // Output: XXL Denis 커피를 만드는 중: 설탕 0, 크림 1
}

fun makeCoffeeWithDetails(coffeeDetails: CoffeeDetails) {
    println("${coffeeDetails.size} ${coffeeDetails.name} 커피를 만드는 중: 설탕 ${coffeeDetails.sugarCount}, 크림 ${coffeeDetails.creamAmount}")
}

3.3. 데이터 클래스의 자동 생성 함수들

data class User(val name: String, val age: Int)

fun main() {
    val user1 = User("Alice", 25)
    val user2 = User("Alice", 25)
    val user3 = User("Bob", 30)
    
    // equals() - 내용 비교
    println(user1 == user2)  // Output: true
    println(user1 == user3)  // Output: false
    
    // hashCode() - 해시 코드 생성
    println(user1.hashCode()) // 동일한 데이터는 동일한 해시 코드
    println(user2.hashCode()) // user1과 같은 값
    
    // toString() - 문자열 변환
    println(user1.toString()) // Output: User(name=Alice, age=25)
    
    // copy() - 객체 복사
    val user4 = user1.copy(age = 26)
    println(user4) // Output: User(name=Alice, age=26)
    
    // componentN() - 구조 분해
    val (name, age) = user1
    println("이름: $name, 나이: $age") // Output: 이름: Alice, 나이: 25
}

4. 참고 자료

You will be redirected shortly

kotlinlang.org