환경 설정

 

FIRST 규칙

📌 테스트 코드가 지켜야 할 규칙

  • Fast: 테스트는 빠르게 동작해야 한다.
  • Independent : 각 테스트는 서로 의존해선 안되며, 독립적으로 그리고 아무 순서로 실행해도 괜찮아야 한다.
  • Repeatable : 테스트는 어떤 환경에서도 반복 가능해야 한다.
  • Self-Validating : 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 검증해야 한다.
  • Timely : 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.
data class Item(
    val name: String,
    val weight: Int,
)

class Bag(
    val maxWeight: Int,
) {
    init {
        if (maxWeight <= 0) {
            throw Exception("가방의 최대 무게가 잘못 설정되었습니다.")
        }
    }

    val itemList: MutableList<Item> = mutableListOf()
    val currentWeight = this.itemList.fold(0) { acc, item -> acc + item.weight }

    fun putItem(item: Item) {
        if (item.weight + currentWeight > maxWeight) {
            throw Exception("가방에 아이템을 넣을 수 없습니다.")
        }

        this.itemList.add(item)
    }

    fun removeItem(item: Item) {
        this.itemList.remove(item)
    }
}

테스트 코드

class BagTest : BehaviorSpec({
    Given("a valid max weight") {
        val validMaxWeight = 10

        When("execute constructor") {
            val result = Bag(validMaxWeight)

            Then("max weight should be valid max weight") {
                result.maxWeight shouldBe validMaxWeight
            }
        }
    }

    Given("a max weight = 0") {
        val maxWeight = 0

        When("execute constructor") {
            val exception = shouldThrow<Exception> { Bag(maxWeight) }

            Then("exception message should be expected") {
                exception.message shouldBe "가방의 최대 무게가 잘못 설정되었습니다."
            }
        }
    }

    Given("a max weight is negative") {
        val maxWeight = -10

        When("execute constructor") {
            val exception = shouldThrow<Exception> { Bag(maxWeight) }

            Then("exception message should be expected") {
                exception.message shouldBe "가방의 최대 무게가 잘못 설정되었습니다."
            }
        }
    }
})

 

'Back-End (Web) > Kotlin' 카테고리의 다른 글

[Kotlin] Kotlin의 객체지향  (0) 2025.02.12
[Kotlin] Kotlin의 사용  (0) 2025.02.11
[Kotlin] Kotlin이란?  (0) 2025.02.10

객체지향 프로그래밍 기초

📌 책임, 역할, 상호작용

    • 객체지향 프로그래밍(OOP)은 프로그램을 여러 개의 독립된 객체로 나누고, 각 객체가 데이터와 기능을 가지며 상호작용하는 방식
    • 객체(Object): 상태와 그 상태를 처리하는 행동의 묶음
    • 책임(Responsibility): 객체가 수행해야 하는 역할 또는 기능을 말합니다. 예를 들어, 자동차 객체의 주행이나 정지는 그 객체의 책임입니다.
    • 역할(Role): 객체가 수행하는 책임의 집합을 의미합니다. 여러 객체가 동일한 역할을 수행할 수 있습니다.
    • 상호작용(Interaction): 객체 간에 서로 메시지를 주고받아 책임을 수행하고 상호작용하는 것을 의미합니다.
    • 객체지향 프로그래밍을 잘 한다는건 책임과 역할을 잘 나누어 코드를 작성한다는 것!

 

 

전략 패턴

/ 디자인 패턴들 / 행동 패턴 전략 패턴 다음 이름으로도 불립니다: Strategy 의도 전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환

refactoring.guru

 

 

 

클래스와 인스턴스

1. 클래스와 인스턴스

더보기

클래스

  • 프로그램의 각 요소별 설계도라고 해석할 수 있어요
  • 코틀린에서는 class 키워드를 활용해서 클래스를 만들어요
  • 클래스에는 정보(프로퍼티)와 행위(메소드)를 작성해요
  • 보통 하나의 파일은 한개의 클래스를 의미하지만, 하나의 파일안에 여러개의 클래스가 존재할 수도 있어요

 

인스턴스

  • 클래스형태로 설계된 객체를 실체화하면 인스턴스가 생겨요
  • 인스턴스는 메모리 공간을 차지해요
  • 정보와 행위를 작성한 클래스를 실체화해서 프로그램에 로딩해요 (메모리에 적재)
  • 정보가 행위가 그대로 로딩되는것이 아니라 위치정보를 메모리에 로딩해요
  • 프로그램은 객체의 위치정보를 변수에 저장해두고, 필요할 때 참조해요

2. 클래스 선언과 인스턴스 생성

// **클래스 선언**
class Person {
    var name: String = "" // 속성 (멤버 변수)
    var age: Int = 0

    fun introduce() { // 메서드
        println("Hi, my name is $name and I am $age years old.")
    }
}

// **인스턴스 생성**
val person = Person() // Person 클래스의 인스턴스를 생성
person.name = "Alice" // 속성 값 설정
person.age = 25
person.introduce() // 메서드 호출

// **결과**
// 출력: Hi, my name is Alice and I am 25 years old.

3. 생성자(Constructor)

1) 기본 생성자

// **기본 생성자 선언**
class Person(val name: String, var age: Int) { // 주 생성자
    fun introduce() {
        println("Hi, my name is $name and I am $age years old.")
    }
}

val person = Person("Bob", 30) // 생성자 호출
person.introduce() // 출력: Hi, my name is Bob and I am 30 years old.

2) 부 생성자

// **부 생성자 사용**
class Person {
    var name: String
    var age: Int

    constructor(name: String, age: Int) { // 부 생성자
        this.name = name
        this.age = age
    }

    fun introduce() {
        println("Hi, my name is $name and I am $age years old.")
    }
}

val person = Person("Charlie", 35)
person.introduce() // 출력: Hi, my name is Charlie and I am 35 years old.

4. 프로퍼티 초기화

class Person(name: String, age: Int) {
    var name = name
    var age = age
}

val person = Person("David", 40)
println("Name: ${person.name}, Age: ${person.age}") // 출력: Name: David, Age: 40

5. 초기화 블록

// **초기화 블록 (init) 사용**
class Person(val name: String, var age: Int) {
    init {
        println("Person initialized with name = $name and age = $age")
    }
}

val person = Person("Eve", 28) // 출력: Person initialized with name = Eve and age = 28

6. 기본값을 가진 클래스

class Person(val name: String = "Unknown", var age: Int = 0) {
    fun introduce() {
        println("Hi, my name is $name and I am $age years old.")
    }
}

val defaultPerson = Person() // 기본값 사용
defaultPerson.introduce() // 출력: Hi, my name is Unknown and I am 0 years old.

val customPerson = Person("Frank", 45) // 사용자 지정 값
customPerson.introduce() // 출력: Hi, my name is Frank and I am 45 years old.

7. 클래스의 주요 특징

1) 데이터 클래스

  • 데이터 저장용 클래스를 간결하게 선언.
data class User(val name: String, val age: Int)

val user = User("Alice", 25)
println(user) // 출력: User(name=Alice, age=25)

2) 상속

  • 클래스 상속은 open 키워드를 사용해야 가능.
open class Animal {
    open fun sound() {
        println("Some generic animal sound")
    }
}

class Dog : Animal() {
    override fun sound() {
        println("Bark")
    }
}

val dog = Dog()
dog.sound() // 출력: Bark

3) 인터페이스

  • Kotlin은 다중 상속 대신 인터페이스를 지원.
interface Flyable {
    fun fly()
}

class Bird : Flyable {
    override fun fly() {
        println("Bird is flying")
    }
}

val bird = Bird()
bird.fly() // 출력: Bird is flying

8. 객체 선언 (싱글톤)

object Singleton {
    var count = 0
    fun increment() {
        count++
    }
}

Singleton.increment()
println("Count: ${Singleton.count}") // 출력: Count: 1

요약

항목  설명 예제
클래스 정의 객체를 생성하기 위한 설계도. class Person { var name: String = "" }
인스턴스 생성 클래스에서 생성된 개별 객체. val person = Person()
생성자 객체 초기화를 위한 특별한 메서드. class Person(val name: String)
초기화 블록 초기화 로직을 정의하는 블록. init { ... }
데이터 클래스 데이터 저장 및 처리에 최적화된 클래스. data class User(val name: String)
상속 부모 클래스의 기능을 자식 클래스가 확장. class Dog : Animal()
인터페이스 다중 상속 대신 사용하는 구조. interface Flyable { fun fly() }
싱글톤 객체 하나의 인스턴스만 존재하는 객체. object Singleton { ... }

 

 

인터페이스

 

1. 인터페이스란?

  • 인터페이스는 클래스가 구현해야 할 메서드와 프로퍼티를 정의하는 틀입니다.
  • 인터페이스는 다중 상속을 지원하며, 구현체는 여러 인터페이스를 동시에 상속받을 수 있습니다.
  • 인터페이스는 구현부가 없는 추상 메서드와, 기본 구현을 가진 구체 메서드를 모두 포함할 수 있습니다.
  •  

2. 인터페이스 선언과 사용

// **인터페이스 선언**
interface Animal {
    fun sound() // 추상 메서드
    fun eat() { // 구체 메서드 (기본 구현 제공)
        println("This animal is eating.")
    }
}

// **클래스가 인터페이스 구현**
class Dog : Animal {
    override fun sound() {
        println("Bark")
    }
}

// **인스턴스 생성 및 사용**
val dog = Dog()
dog.sound() // 출력: Bark
dog.eat()   // 출력: This animal is eating.

3. 인터페이스와 다중 상속

// **두 개 이상의 인터페이스 정의**
interface Flyable {
    fun fly()
}

interface Swimable {
    fun swim()
}

// **클래스가 다중 인터페이스 구현**
class Duck : Flyable, Swimable {
    override fun fly() {
        println("Duck is flying.")
    }

    override fun swim() {
        println("Duck is swimming.")
    }
}

// **인스턴스 생성 및 사용**
val duck = Duck()
duck.fly()  // 출력: Duck is flying.
duck.swim() // 출력: Duck is swimming.

4. 인터페이스의 프로퍼티

// **인터페이스에서 프로퍼티 정의**
interface Shape {
    val name: String // 추상 프로퍼티
    fun area(): Double
}

// **클래스가 인터페이스 구현**
class Circle(val radius: Double) : Shape {
    override val name: String = "Circle" // 추상 프로퍼티 구현
    override fun area(): Double = Math.PI * radius * radius
}

val circle = Circle(5.0)
println("${circle.name} Area: ${circle.area()}") // 출력: Circle Area: 78.53981633974483

5. 인터페이스와 기본 구현 충돌

// **두 인터페이스의 기본 구현 충돌 처리**
interface A {
    fun show() {
        println("Interface A")
    }
}

interface B {
    fun show() {
        println("Interface B")
    }
}

// **클래스가 두 인터페이스를 구현**
class C : A, B {
    override fun show() {
        super<A>.show() // A의 show 호출
        super<B>.show() // B의 show 호출
    }
}

val c = C()
c.show()
// 출력:
// Interface A
// Interface B

6. 인터페이스 확장

// **인터페이스 확장**
interface Vehicle {
    fun drive()
}

interface ElectricVehicle : Vehicle {
    fun chargeBattery()
}

// **구현 클래스**
class Tesla : ElectricVehicle {
    override fun drive() {
        println("Tesla is driving.")
    }

    override fun chargeBattery() {
        println("Tesla is charging.")
    }
}

val tesla = Tesla()
tesla.drive()         // 출력: Tesla is driving.
tesla.chargeBattery() // 출력: Tesla is charging.

7. 인터페이스를 활용한 다형성

// **다형성 예제**
interface Animal {
    fun sound()
}

class Cat : Animal {
    override fun sound() {
        println("Meow")
    }
}

class Dog : Animal {
    override fun sound() {
        println("Bark")
    }
}

fun makeSound(animal: Animal) {
    animal.sound() // Animal 타입으로 다양한 구현 호출 가능
}

makeSound(Cat()) // 출력: Meow
makeSound(Dog()) // 출력: Bark

8. 요약

특징 설명 예제
인터페이스 정의 interface 키워드로 정의, 메서드와 프로퍼티 선언 가능. interface Flyable { fun fly() }
추상 메서드 구현체에서 반드시 오버라이드해야 하는 메서드. fun sound()
구체 메서드 기본 구현을 제공하며, 필요 시 오버라이드 가능. fun eat() { println("Eating") }
프로퍼티 추상 프로퍼티 또는 기본값을 가진 프로퍼티 선언 가능. val name: String
다중 상속 여러 인터페이스를 동시에 구현 가능. class Duck : Flyable, Swimable
다형성 지원 공통된 인터페이스를 통해 다양한 구현체를 동일한 방식으로 처리 가능. fun makeSound(animal: Animal)

 

상속

 

1. 상속이란?

  • 상속은 부모 클래스(상위 클래스)의 속성과 메서드를 자식 클래스(하위 클래스)가 물려받는 개념입니다.
  • Kotlin에서는 클래스가 기본적으로 final(상속 불가)로 설정되어 있으므로, 상속하려면 부모 클래스에 open 키워드를 붙여야 합니다.

2. 기본 상속 구조

// **부모 클래스**
open class Animal { // open 키워드를 붙여야 상속 가능
    open fun sound() { // open 키워드를 붙여야 오버라이드 가능
        println("Some generic animal sound")
    }
}

// **자식 클래스**
class Dog : Animal() { // Animal 클래스를 상속
    override fun sound() { // 부모 메서드를 오버라이드
        println("Bark")
    }
}

// **인스턴스 생성 및 사용**
val dog = Dog()
dog.sound() // 출력: Bark

3. 부모 클래스의 속성과 메서드 상속

// **부모 클래스**
open class Person(val name: String, var age: Int) {
    open fun introduce() {
        println("Hi, I'm $name and I'm $age years old.")
    }
}

// **자식 클래스**
class Student(name: String, age: Int, val grade: Int) : Person(name, age) { // 부모 생성자 호출
    override fun introduce() {
        super.introduce() // 부모 메서드 호출
        println("I'm in grade $grade.")
    }
}

// **사용 예제**
val student = Student("Alice", 20, 3)
student.introduce()
// 출력:
// Hi, I'm Alice and I'm 20 years old.
// I'm in grade 3.

4. 생성자와 상속

// **부모 클래스**
open class Vehicle(val brand: String) {
    init {
        println("Vehicle brand: $brand")
    }
}

// **자식 클래스**
class Car(brand: String, val model: String) : Vehicle(brand) {
    init {
        println("Car model: $model")
    }
}

// **사용 예제**
val car = Car("Toyota", "Corolla")
// 출력:
// Vehicle brand: Toyota
// Car model: Corolla

5. 상속에서의 메서드 호출 순서

open class Parent {
    init {
        println("Parent class initialized")
    }

    open fun greet() {
        println("Hello from Parent")
    }
}

class Child : Parent() {
    init {
        println("Child class initialized")
    }

    override fun greet() {
        println("Hello from Child")
    }
}

val child = Child()
child.greet()
// 출력:
// Parent class initialized
// Child class initialized
// Hello from Child

6. 상속에서의 final, open, override 키워드

  • final: 더 이상 상속이나 오버라이드가 불가능하게 설정.
  • open: 상속이나 오버라이드를 허용.
  • override: 부모 클래스의 메서드나 프로퍼티를 재정의.
open class Parent {
    open fun greet() {
        println("Hello from Parent")
    }

    final fun sayGoodbye() {
        println("Goodbye from Parent")
    }
}

class Child : Parent() {
    override fun greet() { // 부모 메서드를 오버라이드
        println("Hello from Child")
    }

    // fun sayGoodbye() {} // 컴파일 에러: final 메서드는 오버라이드 불가능
}

7. 추상 클래스

  • 추상 클래스는 인스턴스를 생성할 수 없으며, 일부 메서드나 프로퍼티를 구현하지 않고 남겨둠.
  • abstract 키워드를 사용하며, 반드시 하위 클래스에서 구현해야 함.
abstract class Shape {
    abstract fun area(): Double // 추상 메서드 (구현 없음)
    open fun display() { // 구체 메서드
        println("This is a shape.")
    }
}

class Circle(val radius: Double) : Shape() {
    override fun area(): Double = Math.PI * radius * radius // 추상 메서드 구현
}

val circle = Circle(5.0)
circle.display() // 출력: This is a shape.
println("Area: ${circle.area()}") // 출력: Area: 78.53981633974483

8. 인터페이스와 상속

  • Kotlin은 다중 상속을 지원하지 않지만, 다중 인터페이스 구현이 가능합니다.
interface Flyable {
    fun fly() {
        println("Flying...")
    }
}

open class Bird {
    open fun sound() {
        println("Tweet")
    }
}

class Eagle : Bird(), Flyable { // Bird를 상속하고 Flyable 인터페이스 구현
    override fun sound() {
        println("Screech")
    }
}

val eagle = Eagle()
eagle.sound() // 출력: Screech
eagle.fly()   // 출력: Flying...

9. 요약

키워드 설명 예제
open 상속 및 오버라이드 가능. open class Parent { open fun greet() }
final 상속 및 오버라이드 불가능. final fun sayGoodbye()
override 부모의 메서드나 프로퍼티를 재정의. override fun greet()
abstract 구현되지 않은 메서드나 프로퍼티를 정의. abstract fun area(): Double

10. 상속의 장점

  1. 코드 재사용성: 공통된 로직을 부모 클래스에 정의하여 중복 코드를 줄임.
  2. 유지보수 용이: 부모 클래스 수정만으로 자식 클래스의 동작도 일관성 유지.
  3. 다형성 구현: 동일한 부모 클래스를 공유하는 객체를 일관된 방식으로 처리 가능.

 

11. 상속의 단점

  • 유연성, 확장성이 떨어져요
  • 다중 상속에 의해 문제가 발생할 수 있어요. (그래서 kotlin에서는 다중상속을 막아놓았어요)
  • 자식 클래스에서 부모 클래스의 내부 구조를 잘 알아야만 해요

 

 

의존성 주입 (DI)

이것은 "의존성 주입"(Dependency Injection)과 관련된 개념으로, 중복되는 로직을 별도의 공통 객체로 추출하여, 이를 다른 클래스에서 재사용하는 방식입니다. 이는 **SOLID 원칙 중 DIP(의존성 역전 원칙)**에 해당하며, 코드의 재사용성과 유지보수성을 크게 향상시킵니다.


구현 방법

1. 중복되는 로직을 갖는 공통 객체 정의

  • 공통 로직을 별도의 인터페이스클래스로 정의합니다.

2. 공통 객체를 호출하는 클래스에 주입

  • 의존성 주입을 사용하여 공통 객체를 호출하는 클래스에 전달합니다.

구현 예제

1. 중복 로직이 들어갈 객체 정의

// **1. 인터페이스 정의**
interface CommonLogic {
    fun executeLogic(data: String): String
}

// **2. 구현체 정의**
class CommonLogicImpl : CommonLogic {
    override fun executeLogic(data: String): String {
        return "Processed: $data" // 중복 로직 구현
    }
}

2. 공통 객체를 주입받아 사용하는 클래스

// **1. 주입받아 사용하는 클래스**
class Service(private val commonLogic: CommonLogic) {
    fun performTask(input: String): String {
        return commonLogic.executeLogic(input) // 주입받은 객체의 로직 호출
    }
}

3. 의존성 주입 및 사용

fun main() {
    // **공통 로직 구현체 생성**
    val commonLogic = CommonLogicImpl()

    // **Service 클래스에 주입**
    val service = Service(commonLogic)

    // **Service 메서드 호출**
    val result = service.performTask("Hello Kotlin")
    println(result) // 출력: Processed: Hello Kotlin
}

의존성 주입(DI) 활용

의존성 주입을 직접 구현하는 방식

fun main() {
    val commonLogic = CommonLogicImpl() // 의존성 객체 생성
    val service = Service(commonLogic) // 생성자 주입

    println(service.performTask("DI Example")) // 출력: Processed: DI Example
}

의존성 주입 프레임워크 활용 (예: Spring, Koin)

// Koin DI 예제
import org.koin.core.context.startKoin
import org.koin.dsl.module

// **1. 모듈 정의**
val appModule = module {
    single<CommonLogic> { CommonLogicImpl() } // CommonLogic에 CommonLogicImpl 주입
    factory { Service(get()) } // Service에 CommonLogic 주입
}

// **2. Koin 시작**
fun main() {
    startKoin {
        modules(appModule)
    }

    // **3. Koin으로 객체 가져오기**
    val service: Service = getKoin().get()
    println(service.performTask("Koin DI")) // 출력: Processed: Koin DI
}

장점

  1. 중복 제거: 중복 로직을 공통 객체로 추출하여 재사용성 증가.
  2. 유지보수성 향상: 공통 로직 수정 시, 한 곳에서 수정하면 모든 클래스에 반영.
  3. 의존성 관리 용이: 의존성 주입을 통해 클래스 간 결합도를 낮춤.
  4. 테스트 용이성: Mock 객체를 주입하여 테스트 가능.

요약

구성 요소  설명
공통 객체 정의 인터페이스나 클래스에 중복 로직 구현.
주입받는 클래스 공통 객체를 의존성으로 받아 로직 호출.
의존성 주입 방법 직접 주입(생성자, 메서드) 또는 DI 프레임워크(Koin, Dagger 등) 활용.

 

 

생성자

 

1. 생성자란?

  • **생성자(Constructor)**는 객체 생성 시 호출되는 함수.
  • Kotlin은 두 가지 생성자를 제공:
    • 주 생성자(Primary Constructor): 클래스 헤더에서 선언.
    • 부 생성자(Secondary Constructor): 클래스 본문에서 선언.


2. 주 생성자 (Primary Constructor)

1) 기본 주 생성자

// **주 생성자 정의**
class Person(val name: String, val age: Int)

// **객체 생성**
val person = Person("Alice", 25)
println("Name: ${person.name}, Age: ${person.age}")
// 출력: Name: Alice, Age: 25

2) 주 생성자와 초기화 블록

// **init 블록을 사용한 초기화**
class Person(val name: String, val age: Int) {
    init {
        println("Person initialized with name = $name and age = $age")
    }
}

// **객체 생성**
val person = Person("Bob", 30)
// 출력: Person initialized with name = Bob and age = 30

3) 디폴트 값을 가진 주 생성자

// **디폴트 값 설정**
class Person(val name: String = "Unknown", val age: Int = 0)

// **객체 생성**
val person1 = Person() // 기본값 사용
val person2 = Person("Charlie", 35) // 커스텀 값 사용

println("Person1: ${person1.name}, ${person1.age}") // 출력: Person1: Unknown, 0
println("Person2: ${person2.name}, ${person2.age}") // 출력: Person2: Charlie, 35

3. 부 생성자 (Secondary Constructor)

1) 기본 부 생성자

// **부 생성자 정의**
class Person {
    var name: String
    var age: Int

    constructor(name: String, age: Int) { // 부 생성자
        this.name = name
        this.age = age
    }
}

// **객체 생성**
val person = Person("Dave", 40)
println("Name: ${person.name}, Age: ${person.age}")
// 출력: Name: Dave, Age: 40

2) 주 생성자와 부 생성자 동시 사용

// **주 생성자와 부 생성자**
class Person(val name: String) {
    var age: Int = 0

    constructor(name: String, age: Int) : this(name) { // 주 생성자 호출
        this.age = age
    }
}

// **객체 생성**
val person = Person("Eve", 28)
println("Name: ${person.name}, Age: ${person.age}")
// 출력: Name: Eve, Age: 28

4. 데이터 클래스와 생성자

  • 데이터 클래스는 주 생성자에서 속성을 정의.
data class User(val id: Int, val name: String)

// **객체 생성**
val user = User(1, "Alice")
println(user)
// 출력: User(id=1, name=Alice)

5. 생성자와 가시성 (Visibility)

  • 생성자에 가시성 변경자를 추가해 접근 제어 가능.
class Person private constructor(val name: String) { // private 생성자
    companion object {
        fun create(name: String): Person {
            return Person(name)
        }
    }
}

// **객체 생성**
val person = Person.create("Alice")
println("Name: ${person.name}")
// 출력: Name: Alice

6. 추상 클래스와 생성자

// **추상 클래스**
abstract class Animal(val name: String) {
    abstract fun sound()
}

class Dog(name: String) : Animal(name) {
    override fun sound() {
        println("$name says: Woof!")
    }
}

// **객체 생성**
val dog = Dog("Buddy")
dog.sound()
// 출력: Buddy says: Woof!

요약

유형  설명 예제
주 생성자 클래스 헤더에서 선언되는 기본 생성자. class Person(val name: String)
부 생성자 클래스 본문에서 선언되며 주 생성자를 호출하거나 별도의 로직 추가 가능. constructor(name: String)
init 블록 주 생성자와 함께 사용되며, 초기화 로직을 정의. init { ... }
디폴트 값 주 생성자에 기본값을 제공하여 선택적 매개변수 구현 가능. val name: String = "Unknown"
데이터 클래스 데이터를 저장하기 위한 클래스로, 주 생성자에서 속성을 정의. data class User(val id: Int, name: String)
가시성 제어 생성자의 접근 범위를 제한. class Person private constructor(...)

7. 생성자 사용 예제

fun main() {
    // 주 생성자를 사용한 객체 생성
    val person = Person("Alice", 25)
    println("Name: ${person.name}, Age: ${person.age}")

    // 데이터 클래스를 사용한 객체 생성
    val user = User(1, "Bob")
    println(user)

    // 부 생성자를 사용한 객체 생성
    val person2 = Person("Charlie", 30)
    println("Name: ${person2.name}, Age: ${person2.age}")
}

 

 

접근제한자

 

1. 접근 제한자란?

  • 접근 제한자는 클래스, 함수, 프로퍼티, 생성자 등에 대한 접근 범위를 제어합니다.
  • Kotlin에서 지원하는 접근 제한자:
    • public: 어디서나 접근 가능 (기본값).
    • private: 해당 선언이 속한 클래스 또는 파일 내부에서만 접근 가능.
    • protected: 클래스와 그 하위 클래스에서만 접근 가능.
    • internal: 같은 모듈(컴파일 단위) 내에서만 접근 가능.

2. 접근 제한자 종류 및 사용 예제


1) public (기본값)

  • 모든 클래스, 파일, 모듈에서 접근 가능.
  • 선언하지 않으면 기본적으로 public으로 설정됨.
class PublicExample { // public이 기본값
    public val name = "Public Property" // 명시적 public
    fun show() {
        println("This is a public function.")
    }
}

val obj = PublicExample()
println(obj.name) // 접근 가능
obj.show() // 접근 가능

2) private

  • 선언된 클래스 또는 파일 내부에서만 접근 가능.
  • 클래스 외부다른 파일에서는 접근 불가.
class PrivateExample {
    private val secret = "Private Property" // private 프로퍼티
    fun accessSecret() {
        println(secret) // 클래스 내부에서 접근 가능
    }
}

val obj = PrivateExample()
// println(obj.secret) // 오류: secret은 private
obj.accessSecret() // 정상: 클래스 내부에서 secret 접근
  • 파일 스코프에서의 private
private fun fileScopedFunction() {
    println("This function is private to this file.")
}

// 다른 파일에서는 fileScopedFunction() 호출 불가
fileScopedFunction() // 동일 파일에서만 호출 가능

3) protected

  • 선언된 클래스 및 그 하위 클래스에서만 접근 가능.
  • 다른 클래스에서는 접근 불가.
open class Parent {
    protected val protectedProperty = "Protected Property"
}

class Child : Parent() {
    fun showProtected() {
        println(protectedProperty) // 하위 클래스에서 접근 가능
    }
}

val child = Child()
// println(child.protectedProperty) // 오류: protected는 외부에서 접근 불가
child.showProtected() // 정상 실행

4) internal

  • 같은 모듈 내에서만 접근 가능.
  • 다른 모듈(예: 다른 라이브러리)에서는 접근 불가.
internal class InternalExample {
    internal val internalProperty = "Internal Property"
    internal fun internalFunction() {
        println("This is an internal function.")
    }
}

// 같은 모듈에서는 접근 가능
val internalObj = InternalExample()
println(internalObj.internalProperty) // 정상
internalObj.internalFunction() // 정상
  • 다른 모듈에서의 접근
// 다른 모듈에서는 접근 불가
val internalObj = InternalExample()
// println(internalObj.internalProperty) // 오류

5) 생성자와 접근 제한자

  • 생성자에도 접근 제한자를 사용할 수 있음.
class Restricted private constructor() { // private 생성자
    companion object {
        fun create(): Restricted {
            return Restricted() // 클래스 내부에서는 호출 가능
        }
    }
}

val obj = Restricted.create() // 생성자는 외부에서 직접 호출 불가
// val obj2 = Restricted() // 오류: private 생성자

3. 접근 제한자 비교

제한자 클래스 내부 하위 클래스 같은 파일  같은 모듈 다른 모듈
public
private
protected
internal

4. 주요 사용 사례

제한자 사용 사례
public 대부분의 일반적인 코드, API, 또는 외부 모듈에 제공되는 클래스/메서드.
private 클래스 내부에서만 사용하는 데이터나 메서드를 숨기고 싶을 때.
protected 상속을 사용하는 경우 부모 클래스에서 하위 클래스에 로직을 공유할 때.
internal 같은 모듈 내에서만 사용되며 외부에 노출되지 않아야 하는 코드.

 

 

오버라이딩

 

1. 오버라이딩이란?

  • 오버라이딩은 부모 클래스에서 정의한 메서드나 프로퍼티를 자식 클래스에서 재정의하는 것.
  • Kotlin에서는 오버라이딩하려면 부모 메서드나 프로퍼티에 open 키워드를 붙이고, 자식 클래스에서는 override 키워드를 사용해야 함.

2. 메서드 오버라이딩

// **부모 클래스**
open class Animal {
    open fun sound() { // open 키워드를 사용해야 오버라이드 가능
        println("Some generic animal sound")
    }
}

// **자식 클래스**
class Dog : Animal() {
    override fun sound() { // 부모의 sound() 메서드 재정의
        println("Bark")
    }
}

// **사용 예제**
val animal: Animal = Dog() // 다형성
animal.sound() // 출력: Bark

3. 프로퍼티 오버라이딩

  • 프로퍼티도 open 키워드가 있어야 오버라이딩 가능.
  • val로 선언된 프로퍼티는 val이나 var로 오버라이딩 가능.
  • var로 선언된 프로퍼티는 반드시 var로만 오버라이딩 가능.
// **부모 클래스**
open class Animal {
    open val type: String = "Unknown Animal"
}

// **자식 클래스**
class Dog : Animal() {
    override val type: String = "Dog" // 부모의 type 프로퍼티 재정의
}

// **사용 예제**
val dog = Dog()
println(dog.type) // 출력: Dog

4. 메서드와 프로퍼티 오버라이딩

// **부모 클래스**
open class Shape {
    open val name: String = "Shape"
    open fun area(): Double {
        return 0.0
    }
}

// **자식 클래스**
class Circle(val radius: Double) : Shape() {
    override val name: String = "Circle" // 프로퍼티 재정의
    override fun area(): Double { // 메서드 재정의
        return Math.PI * radius * radius
    }
}

// **사용 예제**
val circle = Circle(5.0)
println("Shape: ${circle.name}, Area: ${circle.area()}") 
// 출력: Shape: Circle, Area: 78.53981633974483

5. 부모 메서드 호출 (super)

  • 오버라이드된 메서드에서 부모 클래스의 메서드를 호출할 때 super 사용.
open class Animal {
    open fun sound() {
        println("Some generic animal sound")
    }
}

class Dog : Animal() {
    override fun sound() {
        super.sound() // 부모 클래스의 메서드 호출
        println("Bark") // 추가 로직
    }
}

// **사용 예제**
val dog = Dog()
dog.sound()
// 출력:
// Some generic animal sound
// Bark

6. 오버라이딩 제한 (final)

  • final 키워드를 사용하면 해당 메서드나 프로퍼티는 오버라이딩 불가.
open class Animal {
    open fun eat() {
        println("Animal is eating")
    }

    final fun sleep() { // 오버라이딩 불가
        println("Animal is sleeping")
    }
}

class Dog : Animal() {
    override fun eat() {
        println("Dog is eating")
    }

    // override fun sleep() { ... } // 오류: final로 선언된 메서드는 오버라이딩 불가
}

7. 추상 클래스와 오버라이딩

  • 추상 클래스에서는 추상 메서드를 선언 가능.
  • 추상 메서드는 반드시 하위 클래스에서 오버라이딩해야 함.
// **추상 클래스**
abstract class Animal {
    abstract fun sound() // 구현 없음 (추상 메서드)
    open fun eat() { // 기본 구현 제공
        println("Animal is eating")
    }
}

// **구현 클래스**
class Dog : Animal() {
    override fun sound() {
        println("Bark") // 추상 메서드 구현 필수
    }
}

// **사용 예제**
val dog = Dog()
dog.sound() // 출력: Bark
dog.eat()   // 출력: Animal is eating

8. 요약

키워드 설명 예제
open 부모 클래스에서 메서드나 프로퍼티를 오버라이드 가능하게 설정. open fun sound()
override 자식 클래스에서 부모의 메서드나 프로퍼티를 재정의. override fun sound()
super 부모 클래스의 메서드나 프로퍼티에 접근. super.sound()
final 오버라이드를 제한. final fun sleep()
추상 메서드 반드시 하위 클래스에서 구현해야 하는 메서드. abstract fun sound()

9. 오버라이딩의 장점

  1. 코드 재사용성: 부모 클래스의 로직을 활용하여 중복 코드 제거.
  2. 다형성 지원: 상위 타입의 참조로 다양한 하위 클래스의 동작을 처리 가능.
  3. 유연성: 기본 동작을 수정하거나 확장 가능.

 

 

오버로딩

 

1. 오버로딩이란?

  • **오버로딩(Overloading)**은 같은 이름의 메서드나 생성자를 매개변수의 개수, 타입, 순서가 다르게 정의하는 것.
  • 컴파일러는 메서드 호출 시 매개변수의 타입과 개수를 기준으로 적절한 메서드를 선택.

2. 메서드 오버로딩

class Calculator {
    // **정수 두 개의 합**
    fun add(a: Int, b: Int): Int {
        return a + b
    }

    // **실수 두 개의 합**
    fun add(a: Double, b: Double): Double {
        return a + b
    }

    // **세 개의 정수의 합**
    fun add(a: Int, b: Int, c: Int): Int {
        return a + b + c
    }
}

// **사용 예제**
val calculator = Calculator()
println(calculator.add(2, 3))          // 출력: 5
println(calculator.add(2.5, 3.5))      // 출력: 6.0
println(calculator.add(1, 2, 3))       // 출력: 6

3. 생성자 오버로딩

  • 클래스의 생성자도 매개변수의 종류와 개수를 다르게 설정하여 오버로딩 가능.
class Person {
    var name: String
    var age: Int

    // **기본 생성자**
    constructor() {
        this.name = "Unknown"
        this.age = 0
    }

    // **이름만 받는 생성자**
    constructor(name: String) {
        this.name = name
        this.age = 0
    }

    // **이름과 나이를 받는 생성자**
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

// **사용 예제**
val person1 = Person()
val person2 = Person("Alice")
val person3 = Person("Bob", 25)

println("${person1.name}, ${person1.age}") // 출력: Unknown, 0
println("${person2.name}, ${person2.age}") // 출력: Alice, 0
println("${person3.name}, ${person3.age}") // 출력: Bob, 25

4. 확장 함수 오버로딩

// **Int 확장 함수**
fun Int.times(value: Int): Int = this * value

// **Double 확장 함수**
fun Double.times(value: Int): Double = this * value

// **사용 예제**
val intResult = 5.times(2) // Int 타입 확장 함수 호출
val doubleResult = 5.5.times(2) // Double 타입 확장 함수 호출

println(intResult) // 출력: 10
println(doubleResult) // 출력: 11.0

5. 매개변수 기본값과 오버로딩

  • 기본값을 사용하여 오버로딩 대체 가능.
class Greeter {
    fun greet(message: String = "Hello", name: String = "Guest") {
        println("$message, $name!")
    }
}

// **사용 예제**
val greeter = Greeter()
greeter.greet()                    // 기본값 사용: 출력: Hello, Guest!
greeter.greet("Hi")                // 이름 기본값: 출력: Hi, Guest!
greeter.greet("Hi", "Alice")       // 출력: Hi, Alice!

6. 오버로딩의 주의점

  • 매개변수의 타입이나 개수가 같고 반환 타입만 다르면 컴파일러가 구분하지 못해 오류 발생.
class Example {
    // 오류: 반환 타입만 다른 경우 컴파일 불가
    // fun process(): Int = 0
    // fun process(): String = ""
}

7. 요약

항목 설명 예제
메서드 오버로딩 매개변수의 타입, 개수, 순서가 다르게 동일 이름의 메서드를 여러 개 정의. fun add(a: Int, b: Int)
생성자 오버로딩 클래스의 생성자를 매개변수의 개수와 타입에 따라 여러 개 정의. constructor(name: String, age: Int)
확장 함수 오버로딩 동일한 이름의 확장 함수를 타입에 따라 정의. fun Int.times(value: Int)
기본값 활용 매개변수 기본값으로 오버로딩을 대체하여 코드 간결화. fun greet(message: String = "Hello")

8. 오버로딩의 장점

  1. 코드 가독성 향상: 같은 이름으로 다양한 동작을 처리할 수 있어 가독성이 높아짐.
  2. 유연성 증가: 사용자가 다양한 방식으로 메서드나 생성자를 호출 가능.
  3. 코드 재사용성 증가: 같은 이름으로 다양한 동작을 구현하여 중복 코드 제거.

 

 

다양한 클래스들

 

1. 데이터 클래스 (data class)

  • 데이터를 저장하기 위해 설계된 클래스.
  • data 키워드를 사용하면 기본적인 메서드(예: toString, equals, hashCode, copy)가 자동으로 생성.
  • 특징:
    • 주 생성자에 최소 하나 이상의 프로퍼티를 선언해야 함.
    • 데이터만 관리하는 클래스에 적합.
// **데이터 클래스 선언**
data class User(val id: Int, val name: String)

fun main() {
    val user1 = User(1, "Alice")
    val user2 = User(1, "Alice")

    // **기본 메서드 사용**
    println(user1) // 출력: User(id=1, name=Alice) -> toString()
    println(user1 == user2) // 출력: true -> equals()
    println(user1.copy(name = "Bob")) // 출력: User(id=1, name=Bob) -> copy()
}

2. 열거 클래스 (enum class)

  • 상수값을 관리하기 위한 클래스.
  • 상수와 관련된 속성이나 메서드를 정의할 수 있음.
  • 특징:
    • 관리할 상수값을 명확히 정의.
    • 각 상수는 고유한 값이나 동작을 가질 수 있음.
// **기본 열거 클래스**
enum class ProgrammingLanguage {
    C, JAVA, KOTLIN
}

// **속성과 메서드를 가진 열거 클래스**
enum class ProgrammingLanguageWithInt(val code: Int) {
    C(10),
    JAVA(20),
    KOTLIN(30);

    fun description() = "Code: $code, Language: $name"
}

fun main() {
    println(ProgrammingLanguage.C) // 출력: C
    println(ProgrammingLanguageWithInt.KOTLIN.code) // 출력: 30
    println(ProgrammingLanguageWithInt.KOTLIN.name) // 출력: KOTLIN
    println(ProgrammingLanguageWithInt.KOTLIN.description()) // 출력: Code: 30, Language: KOTLIN
}

3. 실드 클래스 (sealed class)

  • 상속을 제한하고, 미리 정의된 자식 클래스만 허용.
  • 특징:
    • 새로운 자식 클래스를 추가하려면 같은 파일에서만 정의 가능.
    • when 표현식과 함께 사용하면, 모든 경우를 처리했는지 컴파일러가 확인 가능.
    • 불필요한 상속을 방지하고, 구조적 안정성을 제공.
// **실드 클래스 선언**
sealed class Shape {
    data class Circle(val radius: Double) : Shape()
    data class Rectangle(val width: Double, val height: Double) : Shape()
    object Unknown : Shape()
}

fun describeShape(shape: Shape): String {
    return when (shape) {
        is Shape.Circle -> "Circle with radius ${shape.radius}"
        is Shape.Rectangle -> "Rectangle with width ${shape.width} and height ${shape.height}"
        Shape.Unknown -> "Unknown shape"
    }
}

fun main() {
    val circle = Shape.Circle(5.0)
    val rectangle = Shape.Rectangle(4.0, 6.0)
    val unknown = Shape.Unknown

    println(describeShape(circle)) // 출력: Circle with radius 5.0
    println(describeShape(rectangle)) // 출력: Rectangle with width 4.0 and height 6.0
    println(describeShape(unknown)) // 출력: Unknown shape
}

요약

클래스  설명  주요 기능
데이터 클래스 데이터 저장 및 처리용 클래스. toString, equals, hashCode, copy 자동 생성.
열거 클래스 상수를 그룹화하여 관리. 상수별 속성 및 메서드 정의 가능.
실드 클래스 상속 가능한 클래스와 자식 클래스를 제한하여 안전성을 높임. when 표현식과 함께 모든 경우 처리 보장.

사용 사례

  1. 데이터 클래스:
    • 사용자 정보, 설정값 저장 등 데이터 중심의 로직에서 사용.
  2. 열거 클래스:
    • HTTP 상태 코드, 프로그래밍 언어 목록 등 고정된 상수값 관리.
  3. 실드 클래스:
    • API 응답 모델, 상태(State) 관리, 예외 처리 등 구조적인 상속 설계.

장점

  1. 데이터 클래스: 코드 간결화, 기본 메서드 자동 생성으로 생산성 증가.
  2. 열거 클래스: 상수값 관리 일원화, 가독성과 유지보수성 향상.
  3. 실드 클래스: 무분별한 상속 방지, 안정성 있는 설계 가능.

 

'Back-End (Web) > Kotlin' 카테고리의 다른 글

[Kotlin] Kotlin의 테스트코드  (0) 2025.02.16
[Kotlin] Kotlin의 사용  (0) 2025.02.11
[Kotlin] Kotlin이란?  (0) 2025.02.10

기본 지식 - 변수와 자료

 

기본 연산자

// **1. 산술 연산자 (Arithmetic Operators)**
val sum = 10 + 5  // 덧셈
val diff = 10 - 5 // 뺄셈
val product = 10 * 5 // 곱셈
val quotient = 10 / 5 // 나눗셈
val remainder = 10 % 3 // 나머지

// **2. 복합 대입 연산자 (Assignment Operators)**
var num = 10
num += 5  // num = num + 5 (덧셈 후 대입)
num -= 3  // num = num - 3 (뺄셈 후 대입)
num *= 2  // num = num * 2 (곱셈 후 대입)
num /= 2  // num = num / 2 (나눗셈 후 대입)
num %= 3  // num = num % 3 (나머지 후 대입)

// **3. 증가 및 감소 연산자 (Increment and Decrement Operators)**
var count = 0
count++ // 후위 증가: count 값을 사용한 후 1 증가
++count // 전위 증가: count 값을 증가시킨 후 사용
count-- // 후위 감소: count 값을 사용한 후 1 감소
--count // 전위 감소: count 값을 감소시킨 후 사용

// **4. 비교 연산자 (Comparison Operators)**
val a = 10
val b = 20
val isEqual = (a == b) // 값이 같은지 비교
val isNotEqual = (a != b) // 값이 다른지 비교
val isGreater = (a > b) // a가 b보다 큰지 비교
val isLess = (a < b) // a가 b보다 작은지 비교
val isGreaterOrEqual = (a >= b) // a가 b보다 크거나 같은지 비교
val isLessOrEqual = (a <= b) // a가 b보다 작거나 같은지 비교

// **5. 논리 연산자 (Logical Operators)**
val condition1 = true
val condition2 = false
val andResult = condition1 && condition2 // AND 연산: 둘 다 true일 때만 true
val orResult = condition1 || condition2 // OR 연산: 둘 중 하나라도 true면 true
val notResult = !condition1 // NOT 연산: true → false, false → true

// **6. 비트 연산자 (Bitwise Operators)**
val x = 5  // 0b0101
val y = 3  // 0b0011
val bitAnd = x and y // AND 연산: 0b0001 (비트 단위)
val bitOr = x or y  // OR 연산: 0b0111
val bitXor = x xor y // XOR 연산: 0b0110
val bitInv = x.inv() // 비트 반전: -6 (0b...11111010)
val leftShift = x shl 2 // 왼쪽 시프트: 0b10100 (5 * 2^2)
val rightShift = x shr 1 // 오른쪽 시프트: 0b0010 (5 / 2)

// **7. 범위 연산자 (Range Operators)**
val range = 1..5 // 1에서 5까지 포함된 범위
val untilRange = 1 until 5 // 1에서 5 미만 (1, 2, 3, 4)
val downToRange = 5 downTo 1 // 5에서 1까지 감소
val stepRange = 1..10 step 2 // 1, 3, 5, ..., 9 (2씩 증가)

// **8. 인 연산자 (in Operator)**
val isInRange = 3 in range // true (3이 range 안에 포함)
val isNotInRange = 6 !in range // true (6이 range에 포함되지 않음)

// **9. 타입 연산자 (Type Operators)**
val str: Any = "Hello"
val isString = str is String // true (str이 String 타입인지 확인)
val isNotString = str !is String // false (str이 String 타입이 아닌지 확인)

// **10. 엘비스 연산자 (Elvis Operator)**
val nullable: String? = null
val result = nullable ?: "Default" // nullable이 null이면 "Default" 반환

// **11. 안전 호출 연산자 (Safe Call Operator)**
val nullableStr: String? = "Hello"
val length = nullableStr?.length // nullableStr이 null이 아니면 length 반환, null이면 null 반환

// **12. 강제 호출 연산자 (Not-null Assertion Operator)**
val nonNullLength = nullableStr!!.length // nullableStr이 null이 아니라고 확신하는 경우 사용 (null이면 예외 발생)

// **13. 기타 연산자**
val aStr = "Kotlin"
val bStr = "kotlin"
val ignoreCaseEquals = (aStr.equals(bStr, ignoreCase = true)) // 대소문자 무시하고 비교

요약

  • Kotlin 연산자는 Java의 연산자와 유사하지만, 더 간결하고 안전성을 높인 연산자를 제공합니다.
  • 특히 Elvis 연산자 (?:), 안전 호출 연산자 (?.), 강제 호출 연산자 (!!) 등은 Kotlin의 강력한 특징 중 하나로 Null 안전성을 보장합니다.
  • 기본적으로 자바보다도 더 가독성이 좋은 형태로 구성이 되어 있어, 코딩시에 유리합니다.

 

출력과 입력

📌 흔히 다른 장치로 데이터를 전송하는 행위를 Output(출력), 반대로 데이터를 불러오는 행위를 Input(입력)이라고 합니다.

  • 프로그램에서 스피커로 사운드 출력
  • 마이크에서 녹음한 목소리를 불러와서 프로그램에서 확인

 

1. 출력 (Output)

// **기본 출력**
println("Hello, Kotlin!") // 줄 바꿈과 함께 메시지 출력
print("Hello, ") // 줄 바꿈 없이 메시지 출력
print("World!") // 이어서 출력

// **문자열 보간(String Interpolation)**
val name = "Alice"
val age = 25
println("My name is $name, and I am $age years old.") // 변수 값을 문자열에 삽입
println("Next year, I will be ${age + 1} years old.") // 중괄호를 사용해 식도 삽입 가능

// **서식 지정 출력**
val pi = 3.14159
println("The value of pi is %.2f".format(pi)) // 소수점 둘째 자리까지 출력
println("Hello, %s! You are %d years old.".format(name, age)) // %s: 문자열, %d: 정수

2. 입력 (Input)

// **readLine() 사용 (기본 입력)**
print("Enter your name: ")
val userName = readLine() // 사용자 입력을 문자열로 읽음
println("Hello, $userName!")

// **숫자 입력 처리**
print("Enter your age: ")
val userAge = readLine()?.toIntOrNull() // 문자열을 정수로 변환 (null 안전)
if (userAge != null) {
    println("You are $userAge years old!")
} else {
    println("Invalid input. Please enter a valid number.")
}

// **여러 값 입력**
print("Enter two numbers separated by a space: ")
val (num1, num2) = readLine()!!.split(" ").map { it.toInt() } // 공백으로 구분된 두 숫자를 읽어 정수로 변환
println("Sum of $num1 and $num2 is ${num1 + num2}")

// **예외 처리와 함께 입력 처리**
try {
    print("Enter a decimal number: ")
    val decimal = readLine()!!.toDouble() // 문자열을 실수(Double)로 변환
    println("The value you entered is $decimal")
} catch (e: NumberFormatException) {
    println("Invalid input. Please enter a valid decimal number.")
}

3. 입력 및 출력 예제

fun main() {
    // 사용자 이름과 나이 입력받기
    print("What's your name? ")
    val name = readLine() ?: "Unknown" // Null 안전성을 위해 기본값 설정
    print("How old are you? ")
    val age = readLine()?.toIntOrNull() ?: 0 // 숫자 변환 실패 시 기본값 0
    
    // 입력 결과 출력
    println("Nice to meet you, $name!")
    println("You are $age years old.")
    
    // 두 숫자를 입력받아 합 계산
    print("Enter two numbers (e.g., 5 10): ")
    val (a, b) = readLine()!!.split(" ").map { it.toInt() }
    println("The sum of $a and $b is ${a + b}")
}

요약

작업 방법 설명
출력 print, println, format 사용 문자열 출력, 서식 지정 출력, 문자열 보간 등 다양한 방식 제공.
입력 readLine 사용 사용자로부터 문자열 입력받음. 숫자 변환 시 toIntOrNull, toDouble 활용.
에러 처리 try-catch 또는 null 체크 잘못된 입력에 대한 에러 처리 및 기본값 설정 가능.

 

 

조건식

 

1. if-else 조건문

// **기본 if-else**
val a = 10
val b = 20

if (a > b) {
    println("a is greater than b") // 조건이 참일 때 실행
} else {
    println("a is not greater than b") // 조건이 거짓일 때 실행
}

// **if-else if-else (여러 조건 처리)**
val number = 15

if (number > 0) {
    println("$number is positive") // 양수일 때 실행
} else if (number < 0) {
    println("$number is negative") // 음수일 때 실행
} else {
    println("$number is zero") // 0일 때 실행
}

// **if-else 표현식 (Expression)**
val max = if (a > b) a else b // 조건 결과를 변수에 저장
println("The maximum value is $max")

2. when 조건문

// **기본 when 사용**
val x = 2

when (x) {
    1 -> println("x is 1") // x가 1일 때
    2 -> println("x is 2") // x가 2일 때
    else -> println("x is neither 1 nor 2") // 어떤 경우에도 해당되지 않을 때
}

// **범위와 여러 값 검사**
val y = 10

when (y) {
    in 1..10 -> println("y is in the range 1 to 10") // 범위 검사
    in 11..20 -> println("y is in the range 11 to 20")
    else -> println("y is out of range")
}

// **is 연산자 사용**
val obj: Any = "Hello"

when (obj) {
    is String -> println("obj is a String") // obj가 String 타입일 때
    is Int -> println("obj is an Integer") // obj가 Int 타입일 때
    else -> println("obj is of an unknown type") // 다른 타입일 때
}

// **결과를 반환하는 when**
val z = 5
val result = when (z) {
    1 -> "One" // z가 1일 때 반환값
    2, 3 -> "Two or Three" // z가 2 또는 3일 때 반환값
    else -> "Other" // 어떤 경우에도 해당되지 않을 때
}
println("Result: $result")

3. 조건식 간소화

// **삼항 연산자 대체 (Kotlin은 삼항 연산자를 지원하지 않음)**
val isEven = if (x % 2 == 0) "Even" else "Odd" // 삼항 연산자 대신 if-else 표현식 사용
println("x is $isEven")

4. 조건식 활용 예제

fun main() {
    // 숫자 크기 비교
    val a = 10
    val b = 15
    println("Larger number: ${if (a > b) a else b}")

    // 범위와 타입 검사
    val input: Any = 42
    when (input) {
        in 1..50 -> println("Input is in range 1 to 50")
        is String -> println("Input is a string")
        else -> println("Input is something else")
    }

    // 다양한 조건 처리
    val score = 85
    val grade = when {
        score >= 90 -> "A"
        score >= 80 -> "B"
        score >= 70 -> "C"
        else -> "F"
    }
    println("Your grade is $grade")
}

요약

조건식 유형 설명 사용 예제
if-else 조건이 참인지 거짓인지 확인 후 실행. if (a > b) println("a > b") else println("a <= b")
if-else 표현식 조건 결과를 값으로 반환. val max = if (a > b) a else b
when 여러 조건을 확인하거나 값을 반환. when (x) { 1 -> ...; else -> ... }
범위/타입 검사 in으로 범위 확인, is로 타입 확인 가능. when (x) { in 1..10 -> ...; is String -> ... }
삼항 연산자 대체 Kotlin은 삼항 연산자를 지원하지 않아 if-else 표현식을 사용. val result = if (a > b) a else b

 

 

반복문

 

1. for 반복문

// **1. 기본 for문**
for (i in 1..5) { // 1부터 5까지 포함된 범위
    println("i = $i")
}

// **2. with step (간격 설정)**
for (i in 1..10 step 2) { // 1부터 10까지 2씩 증가
    println("i = $i")
}

// **3. downTo (역방향 반복)**
for (i in 5 downTo 1) { // 5부터 1까지 감소
    println("i = $i")
}

// **4. until (미포함 범위)**
for (i in 1 until 5) { // 1부터 4까지 (5는 포함하지 않음)
    println("i = $i")
}

// **5. 배열 및 리스트 반복**
val fruits = listOf("Apple", "Banana", "Cherry")
for (fruit in fruits) {
    println("Fruit: $fruit")
}

// **6. 인덱스와 함께 반복**
for ((index, value) in fruits.withIndex()) {
    println("Index $index: $value")
}

2. while 반복문

// **1. 기본 while문**
var count = 5
while (count > 0) {
    println("Count: $count")
    count--
}

// **2. 조건이 참일 때 실행**
val numbers = listOf(1, 2, 3, 4, 5)
var index = 0
while (index < numbers.size) {
    println("Number: ${numbers[index]}")
    index++
}

3. do-while 반복문

// **1. 기본 do-while문**
var num = 3
do {
    println("Number: $num")
    num--
} while (num > 0)

// **2. 무조건 한 번 실행**
do {
    println("This will run at least once!")
} while (false)

4. 반복문 제어 키워드

// **1. break (반복문 종료)**
for (i in 1..5) {
    if (i == 3) break // i가 3이면 반복문 종료
    println("i = $i")
}

// **2. continue (다음 반복으로 넘어감)**
for (i in 1..5) {
    if (i == 3) continue // i가 3이면 아래 코드 건너뜀
    println("i = $i")
}

// **3. 라벨(label)을 사용한 제어**
outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) break@outer // outer 반복문 종료
        println("i = $i, j = $j")
    }
}

5. 반복문 활용 예제

fun main() {
    // 1부터 10까지의 숫자 중 짝수만 출력
    for (i in 1..10) {
        if (i % 2 == 0) println("Even number: $i")
    }

    // 리스트 요소 출력
    val items = listOf("Kotlin", "Java", "Python")
    for (item in items) {
        println("Language: $item")
    }

    // 2중 반복문으로 구구단 출력
    for (i in 2..9) {
        for (j in 1..9) {
            print("${i * j}\t")
        }
        println()
    }

    // while문으로 합 계산
    var sum = 0
    var n = 1
    while (n <= 10) {
        sum += n
        n++
    }
    println("Sum of 1 to 10: $sum")
}

요약

반복문 설명 예제
for문 특정 범위 또는 컬렉션을 순회하며 반복. for (i in 1..5) println(i)
while문 조건이 참인 동안 반복 실행. while (x > 0) { x-- }
do-while문 조건과 관계없이 블록을 최소 한 번 실행한 후 조건을 확인. do { x-- } while (x > 0)
break 반복문을 즉시 종료. if (x == 3) break
continue 현재 반복을 건너뛰고 다음 반복 실행. if (x == 3) continue
라벨 사용 다중 반복문에서 특정 반복문을 제어. break@outer

 

 

형변환

📌 어떤 타입의 값을 다른 타입으로 변환하는 것을 말합니다. 흔히 타입 캐스팅이라고도 불립니다.

 

1. Kotlin에서의 형변환

  • Kotlin은 타입 안정성을 중시하여 명시적 형변환을 요구.
  • 형변환을 위해 toInt(), toDouble(), toString() 등의 변환 함수를 제공.
  • 안전한 형변환과 비안전한 형변환 연산자도 사용 가능.

2. 기본 형변환 (Primitive Type Casting)

// **정수형 변환**
val number = 42
val doubleValue = number.toDouble() // Int → Double
val stringValue = number.toString() // Int → String
println("Double: $doubleValue, String: $stringValue")

// **실수형 변환**
val decimal = 3.14
val intValue = decimal.toInt() // Double → Int (소수점 버림)
val longValue = decimal.toLong() // Double → Long
println("Int: $intValue, Long: $longValue")

// **문자열 변환**
val str = "123"
val parsedInt = str.toInt() // String → Int
val parsedDouble = str.toDouble() // String → Double
println("Parsed Int: $parsedInt, Parsed Double: $parsedDouble")

// **예외 처리**
val invalidStr = "abc"
val safeConversion = invalidStr.toIntOrNull() // 실패 시 null 반환
println("Safe Conversion: $safeConversion")

3. 스마트 캐스팅 (Smart Casting)

// **스마트 캐스팅이란?**
// Kotlin 컴파일러가 타입을 자동으로 추론하여 형변환을 수행.

fun printLength(obj: Any) {
    if (obj is String) { // is 연산자로 타입 검사
        println("String length: ${obj.length}") // obj는 자동으로 String으로 스마트 캐스팅
    } else {
        println("Not a String")
    }
}

printLength("Hello") // 출력: String length: 5
printLength(123)     // 출력: Not a String

4. 안전한 형변환 (Safe Casting)

// **as? 연산자 사용**
val obj: Any = "Kotlin"
val str: String? = obj as? String // 안전한 형변환 (실패 시 null 반환)
println("Safe Cast Result: $str") // 출력: Kotlin

val notString: Any = 123
val failedCast: String? = notString as? String // 실패 시 null 반환
println("Failed Cast Result: $failedCast") // 출력: null

5. 비안전한 형변환 (Unsafe Casting)

// **as 연산자 사용**
val obj: Any = "Kotlin"
val str: String = obj as String // 명시적 형변환
println("Unsafe Cast Result: $str") // 출력: Kotlin

val notString: Any = 123
// val failedCast: String = notString as String // ClassCastException 발생

6. Any, Unit, Nothing 타입

// **Any 타입**
val anyValue: Any = "This can be any type"
println("Any Value: $anyValue")

// **Unit 타입 (void와 유사)**
fun printMessage(): Unit { // 반환값이 없는 함수
    println("This function returns Unit")
}

// **Nothing 타입 (절대 반환되지 않음)**
fun throwError(): Nothing {
    throw IllegalArgumentException("This function always throws an exception")
}

7. 형변환 활용 예제

fun main() {
    // 1. 입력 값을 숫자로 변환 후 계산
    print("Enter a number: ")
    val input = readLine()?.toIntOrNull() ?: 0 // Null-safe 변환
    println("Double of your input: ${input * 2}")

    // 2. 다양한 타입을 처리하는 스마트 캐스팅
    val values: List<Any> = listOf(42, "Kotlin", 3.14, true)
    for (value in values) {
        when (value) {
            is Int -> println("$value is an Integer")
            is String -> println("$value is a String")
            is Double -> println("$value is a Double")
            else -> println("$value is of an unknown type")
        }
    }

    // 3. 안전한 형변환
    val unknown: Any = "Kotlin"
    val safeString: String? = unknown as? String
    println("Safe Cast: $safeString")
}

요약

형변환 종류 설명  예제
기본 형변환 toInt(), toDouble(), toString() 등 기본 제공 함수로 타입 변환. val x = "123".toInt()
스마트 캐스팅 is 연산자로 타입 확인 후 자동 형변환. if (obj is String) obj.length
안전한 형변환 as? 연산자로 실패 시 null 반환. val str = obj as? String
비안전한 형변환 as 연산자로 명시적 형변환, 실패 시 예외 발생. val str = obj as String
Any, Unit, Nothing Any: 모든 타입의 최상위 클래스.Unit: 반환값 없는 함수.Nothing: 반환 불가 타입. val x: Any = "Hello"

 

 

 

함수 사용

📌 특정 로직을 가지는 소스코드에 별명을 붙인 것으로, 특정 기능을 수행하는 로직을 함수라고 부릅니다.

 

1. 함수 기본 구조

// **기본 함수**
fun greet() {
    println("Hello, Kotlin!") // 출력만 수행
}

greet() // 함수 호출

2. 매개변수와 반환값이 있는 함수

// **매개변수와 반환값이 있는 함수**
fun add(a: Int, b: Int): Int { // 두 개의 정수 매개변수와 반환값(Int)을 선언
    return a + b // 두 값을 더해 반환
}

val sum = add(3, 5) // 함수 호출 후 결과를 변수에 저장
println("Sum: $sum") // 출력: Sum: 8

3. 단일 표현식 함수

// **단일 표현식 함수**
fun multiply(a: Int, b: Int): Int = a * b // 단일 식을 사용하는 함수

val product = multiply(4, 5) // 결과: 20
println("Product: $product")

4. 기본값을 가진 매개변수

// **기본값 매개변수**
fun greetUser(name: String = "Guest") {
    println("Hello, $name!")
}

greetUser("Alice") // 출력: Hello, Alice!
greetUser() // 출력: Hello, Guest!

5. 가변인자 (Varargs)

// **가변인자**
fun printAll(vararg numbers: Int) {
    for (num in numbers) {
        println(num)
    }
}

printAll(1, 2, 3, 4, 5) // 가변 개수의 매개변수를 전달

6. 함수 호출 시 이름 붙인 매개변수

// **이름 붙인 매개변수**
fun formatMessage(greeting: String, name: String): String {
    return "$greeting, $name!"
}

val message = formatMessage(greeting = "Hi", name = "Bob") // 매개변수 이름을 명시적으로 지정
println(message) // 출력: Hi, Bob!

7. 고차 함수 (함수를 매개변수로 받는 함수)

// **고차 함수**
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b) // 전달받은 함수로 계산 수행
}

val sumResult = calculate(3, 7) { x, y -> x + y } // 람다 표현식으로 함수 전달
println("Sum Result: $sumResult") // 출력: Sum Result: 10

8. 확장 함수

// **확장 함수**
fun String.addExclamation(): String {
    return this + "!" // 문자열에 느낌표 추가
}

val excited = "Kotlin".addExclamation() // 기존 String 클래스에 함수 추가
println(excited) // 출력: Kotlin!

9. 인라인 함수

// **인라인 함수**
inline fun executeInline(action: () -> Unit) {
    action() // 전달받은 함수 실행
}

executeInline { println("Inline function executed!") } // 출력: Inline function executed!

10. 지역 함수

// **지역 함수**
fun outerFunction(a: Int, b: Int): Int {
    fun innerFunction(x: Int): Int { // 외부 함수 내부에서 정의된 함수
        return x * x
    }
    return innerFunction(a) + innerFunction(b)
}

val result = outerFunction(2, 3) // 결과: 13
println("Result: $result")

11. 재귀 함수

// **재귀 함수**
fun factorial(n: Int): Int {
    return if (n == 1) 1 else n * factorial(n - 1) // 자기 자신 호출
}

val fact = factorial(5) // 결과: 120
println("Factorial: $fact")

요약

유형 설명 예제
기본 함수 입력값 없이 실행되는 함수. fun greet() { ... }
매개변수와 반환값 매개변수와 반환 타입을 지정. fun add(a: Int, b: Int): Int
단일 표현식 함수 간단한 식으로 반환값을 처리. fun multiply(a: Int, b: Int): Int = a * b
기본값 매개변수 매개변수에 기본값을 설정. fun greet(name: String = "Guest")
가변인자 여러 개의 값을 배열처럼 받을 수 있음. fun printAll(vararg nums: Int)
고차 함수 함수를 매개변수로 전달하거나 반환. fun calculate(a: Int, b: Int, op: (Int, Int) -> Int)
확장 함수 기존 클래스에 새로운 함수 추가. fun String.addExclamation(): String
인라인 함수 컴파일 시 코드가 삽입되어 성능 최적화 가능. inline fun executeInline(action: () -> Unit)
지역 함수 함수 내에 정의된 함수로 외부 함수 내부에서만 사용 가능. fun outer() { fun inner() { ... } }
재귀 함수 자기 자신을 호출하여 작업을 반복. fun factorial(n: Int): Int

 

 

 

Collection과 반복

📌 Collection은 데이터를 수집하거나 그룹화하는 자료구조입니다. Kotlin에서는 주로 List와 Map을 사용합니다.

  • List: 순서가 있는 데이터의 목록으로, 중복된 값이 포함될 수 있습니다.
  • Map: 키와 값의 쌍으로 이루어진 데이터 구조로, 각 키는 유일해야 합니다.
  • Set: 중복되지 않는 요소의 집합

1. List

// **불변 리스트**
val immutableList = listOf("Apple", "Banana", "Cherry") // 불변 리스트 (수정 불가)
println(immutableList[1]) // 인덱스로 접근 (출력: Banana)

// **가변 리스트**
val mutableList = mutableListOf("Apple", "Banana")
mutableList.add("Cherry") // 요소 추가
mutableList[0] = "Orange" // 요소 수정
println(mutableList) // 출력: [Orange, Banana, Cherry]

// **리스트 반복**
for (item in immutableList) { // for-each 반복
    println(item)
}

// **인덱스와 함께 반복**
for ((index, value) in immutableList.withIndex()) {
    println("Index $index: $value") // 출력: Index 0: Apple, Index 1: Banana, ...
}

2. Set

// **불변 집합**
val immutableSet = setOf("Apple", "Banana", "Cherry", "Apple") // 중복 허용하지 않음
println(immutableSet) // 출력: [Apple, Banana, Cherry]

// **가변 집합**
val mutableSet = mutableSetOf("Apple", "Banana")
mutableSet.add("Cherry") // 요소 추가
mutableSet.remove("Banana") // 요소 제거
println(mutableSet) // 출력: [Apple, Cherry]

// **Set 반복**
for (item in mutableSet) {
    println(item)
}

3. Map

// **불변 맵**
val immutableMap = mapOf("A" to 1, "B" to 2, "C" to 3) // 키-값 쌍 생성
println(immutableMap["B"]) // 키로 값 접근 (출력: 2)

// **가변 맵**
val mutableMap = mutableMapOf("A" to 1, "B" to 2)
mutableMap["C"] = 3 // 키-값 추가
mutableMap["A"] = 10 // 기존 키의 값 수정
mutableMap.remove("B") // 키-값 제거
println(mutableMap) // 출력: {A=10, C=3}

// **Map 반복**
for ((key, value) in immutableMap) {
    println("$key -> $value") // 출력: A -> 1, B -> 2, ...
}

4. 반복문을 사용한 컬렉션 처리

// **forEach 사용**
val fruits = listOf("Apple", "Banana", "Cherry")
fruits.forEach { fruit -> println(fruit) } // 각 요소에 대해 작업 수행

// **map을 사용한 변환**
val lengths = fruits.map { it.length } // 각 문자열의 길이를 계산
println(lengths) // 출력: [5, 6, 6]

// **filter를 사용한 조건 필터링**
val filtered = fruits.filter { it.startsWith("A") } // "A"로 시작하는 요소만 필터링
println(filtered) // 출력: [Apple]

// **flatMap으로 중첩된 리스트 처리**
val nestedList = listOf(listOf(1, 2, 3), listOf(4, 5))
val flatList = nestedList.flatMap { it } // 중첩 리스트를 단일 리스트로 변환
println(flatList) // 출력: [1, 2, 3, 4, 5]

5. 기타 컬렉션 함수

// **find: 조건에 맞는 첫 번째 요소 반환**
val firstMatch = fruits.find { it.contains("e") } // "e"를 포함하는 첫 번째 요소
println(firstMatch) // 출력: Apple

// **any: 조건에 맞는 요소가 하나라도 있으면 true**
val hasLongName = fruits.any { it.length > 5 }
println(hasLongName) // 출력: true

// **all: 모든 요소가 조건을 만족하면 true**
val allShortNames = fruits.all { it.length < 7 }
println(allShortNames) // 출력: true

// **count: 조건에 맞는 요소의 개수**
val countStartsWithB = fruits.count { it.startsWith("B") }
println(countStartsWithB) // 출력: 1

// **sort: 정렬**
val sortedFruits = fruits.sorted() // 오름차순 정렬
println(sortedFruits) // 출력: [Apple, Banana, Cherry]

// **groupBy: 조건에 따라 그룹화**
val grouped = fruits.groupBy { it.first() } // 첫 글자로 그룹화
println(grouped) // 출력: {A=[Apple], B=[Banana], C=[Cherry]}

요약

컬렉션 타입  불변 가변
List listOf("A", "B") mutableListOf("A", "B")
Set setOf("A", "B") mutableSetOf("A", "B")
Map mapOf("A" to 1, "B" to 2) mutableMapOf("A" to 1, "B" to 2)

 

반복문 사용  설명 예제
for 각 요소에 대해 순회. for (item in list) { ... }
forEach 고차 함수를 사용해 각 요소에 대해 작업 수행. list.forEach { println(it) }
map 각 요소를 변환하여 새로운 컬렉션 생성. list.map { it.length }
filter 조건에 맞는 요소만 포함한 새로운 컬렉션 생성. list.filter { it.startsWith("A") }
flatMap 중첩된 리스트를 단일 리스트로 변환. nestedList.flatMap { it }
groupBy 조건에 따라 요소를 그룹화. list.groupBy { it.first() }

 

고차함수

📌 고차 함수는 함수를 매개변수로 받거나, 함수를 반환하는 함수입니다.

  • Kotlin은 함수형 프로그래밍을 지원하며, 고차 함수는 코드의 간결성과 재사용성을 높입니다.

 

 

1. 고차 함수의 정의

// **함수를 매개변수로 받는 고차 함수**
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b) // 전달받은 함수를 호출
}

// 고차 함수 호출
val sum = calculate(5, 10) { x, y -> x + y } // 람다식으로 함수 전달
println("Sum: $sum") // 출력: Sum: 15

// **함수를 반환하는 고차 함수**
fun getOperation(type: String): (Int, Int) -> Int {
    return when (type) {
        "add" -> { x, y -> x + y } // 덧셈 함수 반환
        "multiply" -> { x, y -> x * y } // 곱셈 함수 반환
        else -> { _, _ -> 0 } // 기본값 반환
    }
}

// 반환된 함수 호출
val operation = getOperation("add")
println("Result: ${operation(3, 7)}") // 출력: Result: 10

2. 람다 표현식 (Lambda Expressions)

// **람다 기본 문법**
val add: (Int, Int) -> Int = { x, y -> x + y }
println("Add: ${add(3, 4)}") // 출력: Add: 7

// **단일 매개변수 사용**
val square: (Int) -> Int = { it * it } // `it` 키워드로 단일 매개변수 사용
println("Square: ${square(5)}") // 출력: Square: 25

// **다양한 람다 사용법**
val printMessage: (String) -> Unit = { message -> println("Message: $message") }
printMessage("Hello, Kotlin!") // 출력: Message: Hello, Kotlin!

3. 익명 함수 (Anonymous Functions)

// **익명 함수**
val subtract = fun(a: Int, b: Int): Int {
    return a - b
}

println("Subtract: ${subtract(10, 4)}") // 출력: Subtract: 6

4. 주요 고차 함수 예제

1. forEach

  • 컬렉션의 각 요소에 대해 작업 수행.
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { println("Number: $it") } // 각 요소 출력

2. map

  • 컬렉션의 각 요소를 변환하여 새 컬렉션 생성.
val squaredNumbers = numbers.map { it * it } // 각 요소를 제곱
println("Squared: $squaredNumbers") // 출력: Squared: [1, 4, 9, 16, 25]

3. filter

  • 조건에 맞는 요소만 포함한 새 컬렉션 생성.
val evenNumbers = numbers.filter { it % 2 == 0 } // 짝수만 필터링
println("Even Numbers: $evenNumbers") // 출력: Even Numbers: [2, 4]

4. reduce

  • 컬렉션의 모든 요소를 하나의 값으로 누적.
val sum = numbers.reduce { acc, num -> acc + num } // 요소들의 합 계산
println("Sum: $sum") // 출력: Sum: 15

5. fold

  • 초기값과 함께 컬렉션을 누적.
val sumWithInitial = numbers.fold(10) { acc, num -> acc + num } // 초기값 10 포함
println("Sum with Initial: $sumWithInitial") // 출력: Sum with Initial: 25

6. flatMap

  • 중첩된 리스트를 단일 리스트로 변환.
val nestedList = listOf(listOf(1, 2), listOf(3, 4, 5))
val flatList = nestedList.flatMap { it }
println("Flat List: $flatList") // 출력: Flat List: [1, 2, 3, 4, 5]

7. take / drop

  • 일부 요소를 선택하거나 제외.
val firstTwo = numbers.take(2) // 처음 두 요소 선택
println("First Two: $firstTwo") // 출력: First Two: [1, 2]

val dropTwo = numbers.drop(2) // 처음 두 요소 제외
println("After Drop: $dropTwo") // 출력: After Drop: [3, 4, 5]

5. 고차 함수의 장점

장점 설명
코드 간결화 반복적이고 장황한 코드 대신 고차 함수를 사용해 간결하게 작성 가능.
재사용성 고차 함수는 추상화 수준을 높여 다양한 상황에서 재사용 가능.
유연성 매개변수로 함수를 전달하여 동작을 동적으로 변경 가능.
함수형 프로그래밍 지원 Kotlin의 함수형 프로그래밍 특성을 활용하여 더 높은 수준의 코드를 작성 가능.

6. 예제: 고차 함수 활용

fun performOperation(numbers: List<Int>, operation: (Int) -> Int): List<Int> {
    return numbers.map { operation(it) }
}

// 고차 함수 사용
val doubledNumbers = performOperation(listOf(1, 2, 3, 4)) { it * 2 } // 각 숫자를 2배로
println("Doubled: $doubledNumbers") // 출력: Doubled: [2, 4, 6, 8]

val squaredNumbers = performOperation(listOf(1, 2, 3, 4)) { it * it } // 각 숫자를 제곱
println("Squared: $squaredNumbers") // 출력: Squared: [1, 4, 9, 16]

요약

항목 설명 예제
고차 함수 함수를 매개변수로 받거나 반환하는 함수. fun calculate(a: Int, b: Int, op: (Int, Int) -> Int)
람다 표현식 익명 함수의 간결한 표현으로 함수형 프로그래밍 지원. { x, y -> x + y }
주요 고차 함수 map, filter, reduce, fold, flatMap, forEach. list.map { it * 2 }
장점 코드 간결화, 유연성, 재사용성 증가.  

 

 

Nullable type

📌 null이 될 수 있는 변수를 의미한다.

  • null은 값이 없음, 값이 존재하지 않음을 의미한다. 변수를 만들었지만 그 안에 값을 넣지 않은 경우를 흔히 말한다.
  • Kotlin에서 모든 타입은 기본적으로 NULL을 허용하지 않는다. 다만 타입에 ? 을 명시하면 사용이 가능하다.

1. Nullable 타입 선언

// **Nullable 타입 선언**
var name: String? = null // `?`를 사용해 Null을 허용
name = "Kotlin" // Null이 아닌 값 할당 가능
println(name) // 출력: Kotlin

// **Non-null 타입**
var nonNullableName: String = "Hello"
// nonNullableName = null // 컴파일 에러: Null 불허

2. Null 안전성 연산자

1) Safe Call Operator (?.)

  • Null 값을 허용하는 변수에서 안전하게 프로퍼티나 메서드를 호출.
val nullableName: String? = null
println(nullableName?.length) // 출력: null (예외 발생 없이 안전)

val nonNullName: String? = "Kotlin"
println(nonNullName?.length) // 출력: 6

2) Elvis Operator (?:)

  • Null 값일 경우 기본값을 반환.
val nullableText: String? = null
val result = nullableText ?: "Default Value" // Null이면 "Default Value" 반환
println(result) // 출력: Default Value

3) Non-null Assertion Operator (!!)

  • 변수 값이 Null이 아니라고 확신할 때 사용.
  • Null일 경우 NullPointerException 발생.
val nullableString: String? = "Kotlin"
println(nullableString!!.length) // 출력: 6

val nullValue: String? = null
// println(nullValue!!.length) // NullPointerException 발생

3. Nullable 타입 활용

1) if 문을 사용한 Null 체크

  • Null 여부를 확인하여 처리.
val str: String? = null
if (str != null) {
    println("Length: ${str.length}")
} else {
    println("String is null")
}

2) Smart Casting (is)

  • Kotlin은 Null 체크 후 해당 변수의 타입을 자동으로 변환(스마트 캐스팅).
val nullable: String? = "Kotlin"
if (nullable is String) {
    println("Length: ${nullable.length}") // 스마트 캐스팅으로 안전하게 호출
}

4. Safe Call with Let

  • **let**은 Null 값을 제외하고 실행할 코드를 작성 가능.
val nullableValue: String? = "Hello"
nullableValue?.let { println("Length: ${it.length}") } // 출력: Length: 5

val nullValue: String? = null
nullValue?.let { println(it) } // 아무 작업도 수행하지 않음

5. Nullable 타입과 컬렉션

1) 컬렉션에 Nullable 타입 포함

  • 컬렉션에 Null 값을 허용.
val list: List<String?> = listOf("A", null, "C")
println(list) // 출력: [A, null, C]

2) Null 값 필터링

  • 컬렉션에서 Null 값을 제거.
val nullableList: List<String?> = listOf("A", null, "C")
val nonNullList = nullableList.filterNotNull() // Null 제거
println(nonNullList) // 출력: [A, C]

6. Nullable 타입 예제

fun printLength(text: String?) {
    val length = text?.length ?: 0 // Null이면 0 반환
    println("Length: $length")
}

fun main() {
    printLength("Kotlin") // 출력: Length: 6
    printLength(null) // 출력: Length: 0

    // Safe call과 let 활용
    val message: String? = "Hello Kotlin"
    message?.let { println("Message length: ${it.length}") } // 출력: Message length: 12
}

요약

연산자/기법 설명 예제
Safe Call (?.) Null일 경우 호출하지 않고 Null 반환. nullable?.length
Elvis Operator (?:) Null일 경우 기본값 반환. val result = nullable ?: "Default"
Non-null Assertion (!!) Null이 아니라고 확신할 때 사용, Null이면 예외 발생. nullable!!.length
if 문을 사용한 체크 Null 여부를 확인하여 안전하게 처리. if (str != null) { ... }
Smart Casting (is) Null 체크 후 자동으로 Non-null 타입으로 변환. if (nullable is String) { ... }
let 블록 사용 Null이 아닌 경우에만 코드 블록 실행. nullable?.let { println(it.length) }
Null 필터링 컬렉션에서 Null 값을 제거. list.filterNotNull()

 

 

예외 처리

📌 예외(Exception)는 프로그램 실행 중 발생하는 오류 상황을 말하며 이런 상황에 대한 대응을 예외 처리라 한다.

  • Kotlin은 try-catch-finally 블록을 사용하여 예외를 처리.
  • 모든 예외는 Throwable 클래스를 상속받음.

 

1. try-catch 블록

// **기본 구조**
try {
    // 예외 발생 가능성이 있는 코드
    val number = "abc".toInt() // 문자열을 정수로 변환하려다 예외 발생
    println("Number: $number")
} catch (e: NumberFormatException) {
    // 예외 처리
    println("Exception occurred: ${e.message}")
}

// **결과**
// 출력: Exception occurred: For input string: "abc"

2. 여러 catch 블록

try {
    val result = 10 / 0 // ArithmeticException 발생
    println("Result: $result")
} catch (e: ArithmeticException) {
    println("ArithmeticException: Division by zero is not allowed.")
} catch (e: Exception) {
    println("Generic Exception: ${e.message}")
}

// **결과**
// 출력: ArithmeticException: Division by zero is not allowed.

3. finally 블록

// **try-catch-finally 구조**
try {
    val value = "123".toInt() // 정상적으로 실행
    println("Value: $value")
} catch (e: Exception) {
    println("Exception: ${e.message}")
} finally {
    // 예외 발생 여부와 관계없이 항상 실행
    println("Finally block executed.")
}

// **결과**
// 출력:
// Value: 123
// Finally block executed.

4. 예외 발생시키기 (throw)

// **throw 키워드로 예외 발생**
fun divide(a: Int, b: Int): Int {
    if (b == 0) throw IllegalArgumentException("Division by zero is not allowed.") // 예외 발생
    return a / b
}

try {
    println(divide(10, 0)) // 예외 발생
} catch (e: IllegalArgumentException) {
    println("Caught Exception: ${e.message}")
}

// **결과**
// 출력: Caught Exception: Division by zero is not allowed.

5. 사용자 정의 예외

// **사용자 정의 예외 클래스**
class CustomException(message: String) : Exception(message)

fun checkValue(value: Int) {
    if (value < 0) throw CustomException("Negative values are not allowed.") // 사용자 정의 예외 발생
}

try {
    checkValue(-10) // 예외 발생
} catch (e: CustomException) {
    println("Custom Exception: ${e.message}")
}

// **결과**
// 출력: Custom Exception: Negative values are not allowed.

6. 예외 처리와 Null 안전성

// **toIntOrNull 사용으로 Null 안전 처리**
val number = "abc".toIntOrNull() ?: 0 // 변환 실패 시 Null 반환
println("Number: $number") // 출력: Number: 0

7. 예외 처리 활용 예제

fun readNumber(): Int {
    print("Enter a number: ")
    val input = readLine()
    return try {
        input?.toInt() ?: 0 // 입력값을 정수로 변환, 실패 시 0 반환
    } catch (e: NumberFormatException) {
        println("Invalid number format: ${e.message}")
        -1 // 예외 발생 시 -1 반환
    }
}

val result = readNumber()
println("Result: $result")

8. 예외 처리의 주요 키워드

키워드 설명 예제
try 예외 발생 가능성이 있는 코드를 실행. try { ... }
catch 발생한 예외를 처리. catch (e: Exception) { ... }
finally 예외 발생 여부와 관계없이 항상 실행되는 코드. finally { ... }
throw 예외를 명시적으로 발생. throw IllegalArgumentException("Error")
toIntOrNull 예외 없이 문자열을 안전하게 정수로 변환, 실패 시 Null 반환. "123".toIntOrNull()

9. 예외 처리의 장점

장점 설명
프로그램 안정성 유지 예외를 처리하여 프로그램이 중단되지 않고 계속 실행 가능.
문제 디버깅 용이 예외 메시지를 통해 문제를 정확히 파악 가능.
NullPointerException 방지 Kotlin의 toIntOrNull 등 Null 안전 기능과 결합하여 예외를 줄임.

 

'Back-End (Web) > Kotlin' 카테고리의 다른 글

[Kotlin] Kotlin의 테스트코드  (0) 2025.02.16
[Kotlin] Kotlin의 객체지향  (0) 2025.02.12
[Kotlin] Kotlin이란?  (0) 2025.02.10

Kotlin

📌Kotlin은 JetBrains(intelliJ IDEA의 개발사)가 2011년에 발표한 현대적인 프로그래밍 언어입니다.

  • JVM(Java Virtual Machine) 위에서 실행되며, Java와 100% 호환됩니다.
  • 간결하고 안전하며, 함수형 프로그래밍과 객체지향 프로그래밍(OOP)을 지원합니다.
  • Android의 공식 프로그래밍 언어로 채택되었습니다(Google I/O 2017).

 


Kotlin의 주요 특징

특징 설명
Java와 100% 호환 Java 코드와 상호운용 가능하며, 기존 Java 프로젝트에 Kotlin을 통합 가능.
간결한 문법 Java보다 코드 길이가 짧고 명확해 유지보수 용이.
Null 안전성 NullPointerException을 방지하기 위한 안전한 설계.
함수형 프로그래밍 지원 람다 표현식, 고차 함수, 확장 함수 등 함수형 프로그래밍에 적합한 기능 제공.
안드로이드 최적화 Android 앱 개발에 최적화된 도구와 확장 기능 제공.
코루틴 지원 비동기 프로그래밍을 간결하고 효율적으로 처리.
멀티플랫폼 개발 지원 Kotlin Multiplatform을 통해 Android, iOS, 웹 등 다양한 플랫폼에서 코드 공유 가능.

3. Kotlin의 장점

항목 설명
코드 간결성 Java 코드보다 단순하고 짧은 코드로 동일한 기능 구현 가능.
생산성 향상 간결한 문법과 다양한 내장 기능으로 개발 속도 증가.
NullPointerException 방지 컴파일 단계에서 Null 체크를 강제하여 안정성 제공.
확장 함수 기존 클래스나 라이브러리를 수정하지 않고도 기능 추가 가능.
다중 패러다임 지원 객체지향(OOP)과 함수형 프로그래밍(FP)을 모두 지원.

4. Kotlin의 단점

항목 설명
학습 곡선 Java에 비해 문법이 다소 복잡해 초기 학습이 필요.
컴파일 속도 일부 경우 Java보다 느릴 수 있음(특히 처음 빌드 시).
생태계 규모 Java에 비해 라이브러리와 커뮤니티 규모가 작지만, 빠르게 성장 중.
런타임 크기 증가 Kotlin 표준 라이브러리로 인해 애플리케이션 크기가 약간 증가할 수 있음(Android 환경에서 주의 필요).

5. Kotlin의 주요 사용 사례

분야  사용 사례
Android 앱 개발 Android의 공식 언어로 사용. 기존 Java 프로젝트와 통합 용이.
서버 개발 Ktor, Spring과 같은 프레임워크로 서버 애플리케이션 개발 가능.
멀티플랫폼 개발 Kotlin Multiplatform을 통해 Android, iOS, 웹 등 다양한 플랫폼에서 코드 공유.
데스크톱 애플리케이션 JavaFX와 통합하여 데스크톱 애플리케이션 개발 가능.

6. Kotlin의 핵심 기능

기능 설명
데이터 클래스 간단한 구문으로 데이터 객체 생성. 자동으로 getter, setter, equals, hashCode 생성.
코루틴(Coroutines) 비동기 작업을 간단하고 효율적으로 처리 가능.
확장 함수 기존 클래스에 새로운 메서드를 추가하는 기능 제공.
Null 안전성 ?와 !! 연산자를 통해 NullPointerException 방지.
람다(Lambda) 표현식 간결한 함수 표현으로 함수형 프로그래밍 지원.

7. Kotlin과 Java 비교

항목 Kotlin Java
출시 연도 2011년 (JetBrains) 1995년 (Sun Microsystems, 현재 Oracle)
코드 길이 간결하고 명확. 더 장황하고 반복적인 코드 요구.
Null 안전성 기본적으로 NullPointerException 방지. NullPointerException 직접 관리 필요.
모던 기능 지원 코루틴, 확장 함수, 고차 함수 등 최신 프로그래밍 기능 지원. 일부 최신 기능(Java 8 이상)에서만 지원.

8. Kotlin의 현재와 미래

  • Android 개발의 표준:
    • Google이 Kotlin을 Android의 공식 프로그래밍 언어로 채택.
  • 빠르게 성장 중인 생태계:
    • Spring Framework, Gradle 등 다양한 툴과의 통합으로 서버 개발에서도 인기 상승.
  • 멀티플랫폼 개발:
    • Kotlin Multiplatform으로 iOS, 웹, 데스크톱 등 다양한 플랫폼 지원.

 

'Back-End (Web) > Kotlin' 카테고리의 다른 글

[Kotlin] Kotlin의 테스트코드  (0) 2025.02.16
[Kotlin] Kotlin의 객체지향  (0) 2025.02.12
[Kotlin] Kotlin의 사용  (0) 2025.02.11

+ Recent posts