코틀린 기초 문법

Bobby·2023년 2월 18일
0

또 까먹었지..?

목록 보기
4/4
post-thumbnail

1. 변수

  • val(value): 불변
  • var(variable): 가변

💡 할당

// val 변수명: 타입 = 값
val a: Int = 1

// 타입 생략 가능
val b = 1 

// 타입이 고정되면 다른 타입으로 할당 불가
var c = 123 // Int
c = "test" // 불가능

🧨 탑 레벨에도 변수 할당이 가능

var x: Int = 5
fun main() {
    x += 1
    println(x)
}
  • 실행

💡 지연할당

  • 변수 생성시 초기 값을 할당하지 않고 이후에 할당
  • 지연할당시 타입 생략 불가
val d: Int
d = 1

2. 함수

💡 함수선언

// 기본적인 함수 선언 스타일
fun sum(a: Int, b: Int) : Int {
    return a + b
}

// 표현식 스타일
fun sum2(a: Int, b: Int) : Int = a + b

// 표현식 & 반환타입 생략
fun sum3(a: Int, b: Int) = a + b

// 몸통이 있는 함수는 반환 타입 생략 불가
fun sum4(a: Int, b: Int) : Int {
    return a + b
}

// 반환타입이 없는 함수는 Unit 을 반환 (생략가능)
fun printSum(a: Int, b: Int) : Unit {
    println("$a + $b = ${a + b}")
}

💡 Default arguments

  • 함수 파라미터의 기본값을 설정 할 수있다.
fun main() {
    greeting()
    greeting("Hi!!!")
}

fun greeting(message: String = "Hello World!") {
    println(message)
}
  • 실행

💡 Named arguments

  • 함수의 각 파라미터에 이름을 지정하여 전달할 수 있다.
fun main() {
    welcome("Hi", "bobby")
    welcome(name = "bobby")
    welcome(message = "Hi", name = "bobby")
    welcome(name = "bobby", message = "Hey")
    welcome(message = "Hi", "bobby")
}

fun welcome(message: String = "Welcome", name: String) {
    println("$message, $name!!")
}
  • 실행

3. 흐름제어(if, when, for, while)

💡 if

기본적인 if else 사용

fun main() {

    // if...else
    val age: Int = 20

    if (age < 20) {
        println("학생입니다.")
    } else {
        println("성인입니다.")
    }
}
  • 실행

🧨 코틀린은 if else 도 값을 리턴할 수 있다.

fun main() 

    // 코틀린의 if...else 는 값을 리턴할 수 있다.
    val score: Int = 80

    val message = if (score >= 80) {
        "합격"
    } else {
        "불합격"
    }

    println(message)
}
  • 실행

💡 when

  • 자바의 switch 문

when ... else

fun main() {

    // when ... else
    val day = 2

    val result = when (day) {
        1 -> "월요일"
        2 -> "화요일"
        3 -> "수요일"
        4 -> "목요일"
        5 -> "금요일"
        6 -> "토요일"
        7 -> "일요일"
        else -> "잘못입력"
    }

    println(result)
}
  • 실행

🧨 else 생략

  • enum 타입의 모든 값에 대한 조건을 적어주면 생략가능
fun main() {

    val day = Day.SATURDAY

    when(day) {
        Day.MONDAY -> println("월요일")
        Day.TUESDAY -> println("화요일")
        Day.WEDNESDAY -> println("수요일")
        Day.THURSDAY -> println("목요일")
        Day.FRIDAY -> println("금요일")
        Day.SATURDAY -> println("토요일")
        Day.SUNDAY -> println("일요일")
    }
}

enum class Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
  • 실행

🧨 여러개의 조건을 정의할 수 있다.

fun main() {
    val grade = "A"
    
    when(grade) {
        "A", "B", "C" -> println("합격")
        else -> println("불합격")
    }
}
  • 실행

💡 For loop

범위 연산자 .. 사용

fun main() {
    // 0 <= i <= 2
    for (i in 0..2) {
        println(i)
    }
}
  • 실행

until 사용

fun main() {

    // 0 <= i < 3
    for (i in 0 until 2) {
        println(i)
    }
}
  • 실행

step 값 만큼 증가

fun main() {

    for (i in 0..4 step 2) {
        println(i)
    }
}
  • 실행

downTo 사용

fun main() {

    for (i in 3 downTo 1) {
        println(i)
    }
}
  • 실행

배열 반복

fun main() {

    val numbers = arrayOf(1, 2, 3)
    
    for (i in numbers) {
        println(i)
    }
}
  • 실행

💡 While loop

  • 조건을 확인하고 참이면 코드 실행 후 조건 확인
fun main() {

    var x = 3

    while (x > 0) {
        println(x)
        x--
    }
}
  • 실행

4. null safety

  • 코틀린은 null값을 할당 할 수 없다
fun main() {
    
    val a: String = null // 컴파일 에러
    var b: String = "asdf"
    b = null // 컴파일 에러
}

💡 nullable

  • 변수 타입에 ? 을 작성
fun main() {
    
    val a: String? = null
    println(a?.length)
    
    val b: Int = if (a != null) a.length else 0
    println(b)
    
    // 엘비스 연산자 (값이 null일 경우 반환 값 지정)
    val c = a?.length ?: 0
    println(c)
}
  • 실행

💡 null check 활용

fun getNullStr(): String? = null

fun getLengthIfNotNull(str: String?) = str?.length ?: 0

fun main() {

    val nullableStr = getNullStr()

    val nullableStrLength = nullableStr?.length ?: 0
    println(nullableStrLength)

    val length = getLengthIfNotNull(null)
    println(length)
}
  • 실행

NullPointerException 이 발생하는 경우

  1. 직접 에러 throw
throw NullPointerException()
  1. 단언연산자 사용 후 null이 들어왔을 때
val c: String? = null
val d = c!!.length
  1. 자바 코드와 같이 사용할 때 자바 코드에서 null이 발생하는 경우

5. 예외처리

코틀린은 checked exception을 강제하지 않음.

fun main() {

    Thread.sleep(1) // (O)
}

예외 발생시 후 처리가 필요한 경우 try...catch 사용

fun main() {

    try {
        throw RuntimeException()
    } catch (e: Exception) {
        println("에러 발생시 수행")
    } finally {
        println("finally 실행")
    }
}
  • 실행

try...catch 도 값을 리턴할 수 있다.

fun main() {

    val a = try {
        "test".length
    } catch (e: Exception) {
        println("에러 발생시 수행")
    }

    println(a)
}
  • 실행

함수에서 예외를 반환 할 경우 타입은 Nothing타입

fun main() {

    val a: String? = null
    val b = a ?: fail("a is null")

    println(b.length)
}

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message);
}
  • 실행

참고

fun main() {
    val a: String? = null
    val b: String = a ?: fail("a is null")
    println(b.length) // b 가 null인 경우 위에서 예외가 발생하기 때문에 nullable 하지 않아도 컴파일 에러가 발생하지 않음. 
}
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message);
}

6. 클래스

💡 생성

  • class 키워드를 사용하며 내용이 없는 빈 클래스도 생성이 가능
class Coffee {

}

class EmptyClass

💡 생성자

  • constructor 키워드를 사용하며 생략 가능
  • 기본값 지정 가능
class Coffee constructor(val name:String) {

}

// 생략
class Coffee(val name:String) {

}

// 기본값 지정
class Coffee (
    val name: String = "", 
    // 기본값 지정
    val price: Int = 0,
) {

}
  • 인스턴스 생성 및 필드값 사용
  • getter, setter 자동 생성(val 타입은 불변이므로 getter만 생성)
fun main() {
    val coffee = Coffee();
    coffee.name = "아이스 아메리카노"
    coffee.price = 2000

    println("${coffee.name} 가격은 ${coffee.price}원")
}
  • 실행

💡 getter, setter

  • getter, setter를 커스텀 할 수 있다.
class Coffee (
    var name: String = "", // 기본값 지정
    var price: Int = 0,
) {
    val brand: String
    	// getter 직접 생성
        get() {
            return  "스타벅스"
        }

    var quantity: Int = 0
    	// setter 직접 생성
        set(value) {
            if (value > 0) {
                field = value // 필드에 접근하여 데이터 저장
            }
        }
}

인스턴스 생성 및 사용

fun main() {
    val coffee = Coffee();
    coffee.name = "아이스 아메리카노"
    coffee.price = 2000
    coffee.quantity = 1

    println("${coffee.brand}: ${coffee.name} ${coffee.quantity}개 가격은 ${coffee.price}원")
}
  • 실행

🧨 field를 사용하는 이유

coffee.quantity = 1 // 1. 사용시 setter 호출하고 
var quantity: Int = 0
    set(value) {
        if (value > 0) {
            quantity = value // 2. setter 내부에서 다시 setter 호출
        }
    }

무한으로 setter를 호출 하여 StackOverFlow 발생


7. 상속

💡 상속

  • 코틀린의 모든 클래스는 Any 클래스를 상속받고 있다.
  • 코틀린은 기본적으로 불변 클래스이고 open 키워드를 사용해 상속이 가능하게 할 수 있다.
  • 프로퍼티와 함수도 open 키워드를 사용해 하위 클래스에서 재정의 할 수 있다.
open class Dog {
    open var age: Int = 0

    open fun bark() {
        println("멍멍")
    }
}

class Bulldog: Dog() {

    override var age: Int = 0

    override fun bark() {
        println("컹컹")
    }
}
  • 기본 생성자를 통해 프로퍼티를 재정의 할 수 있다.
class Bulldog(override var age: Int = 0): Dog() {
    
    override fun bark() {
        println("컹컹")
    }
}
  • 재정의한 프로퍼티나 함수는 기본적으로 open이다.
  • 재정의한 프로퍼티와 함수를 하위 클래스에서 재정의하지 못하게 하려면 final 키워드를 사용한다.
class Bulldog(final override var age: Int = 0): Dog() {

    final override fun bark() {
        println("컹컹")
    }
}
  • 하위 클래스에서 상위 클래스에 접근하려면 super키워드 사용
    override fun bark() {
        super.bark()
    }

인스턴스 생성 및 사용

fun main() {
    val dog = Bulldog(age = 2)
    println(dog.age)
    dog.bark()
}
  • 실행

💡 추상 클래스

  • abstract 키워드를 사용하여 추상 클래스를 생성할 수 있다.
  • 하위 클래스에서 구현해야 하는 프로퍼티, 함수를 정의한다.
abstract class Developer {

    abstract var age: Int
    abstract fun code(language: String)
}

class Backend(override var age: Int = 0): Developer() {

    override fun code(language: String) {
        println("I code with $language")
    }
}

인스턴스 생성 및 사용

fun main() {
    val backendDeveloper = Backend(age = 20)
    println(backendDeveloper.age)
    backendDeveloper.code("Kotlin")
}
  • 실행

8. 인터페이스

💡 생성

  • 추상 함수 및 기본 함수 생성 가능
  • 추상 프로퍼티 정의 가능
  • backing field 사용 불가
class Product(val name: String, val price: Int)

interface Cart {
	
    // 추상 프로퍼티
    val coin: Int
    
    val weight: String
        get() = "20kg" // backing field 사용 불가
        
	// 추상 함수
    fun add(product: Product)

	// 구현이 있는 함수
    fun rent() {
        if (coin > 0) {
            println("카트를 대여합니다.")
        }
    }
}

💡 상속

  • class 상속과 다르게 생성자 호출이 들어가지 않는다.
  • 추상 프로퍼티 및 추상 함수를 구현해야 한다.
class MyCart(override val coin: Int) : Cart {

    override fun add(product: Product) {
        if (coin <= 0) println("코인을 넣어주세요.")
        else println("${product.name}이(가) 카트에 추가됐습니다.")
    }
}
  • 인터페이스도 인터페이스를 상속받을 수 있고 구현 클래스가 아닌 하위 인터페이스에서 추상 함수를 구현 할 수 있다.
interface Wheel {
    fun roll()
}

interface Cart: Wheel {

    ...

    override fun roll() {
        println("카트가 굴러갑니다.")
    }
}

인스턴스 생성 및 사용

fun main() {
    val cart = MyCart(coin = 100)
    cart.rent()
    cart.roll()
    cart.add(Product(name = "장난감", price = 1000))
}
  • 실행

💡 다중 상속

  • 인터페이스 다중 상속을 할 수 있다.
interface Order {
    
    fun add(product: Product) {
        println("${product.name} 주문이 완료되었습니다.")
    }
}

class MyCart(override val coin: Int) : Cart, Order {

    override fun add(product: Product) {
        if (coin <= 0) println("코인을 넣어주세요.")
        else println("${product.name}이(가) 카트에 추가됐습니다.")

        // 다중 상속 함수 사용
        super<Order>.add(product)
    }
}

다중 상속한 인터페이스에서 같은 함수 명이 있으면 super 키워드를 사용해서 해당 함수를 실행한다.

인스턴스 생성 및 사용

fun main() {
    val cart = MyCart(coin = 100)
    cart.rent()
    cart.roll()
    cart.add(Product(name = "장난감", price = 1000))
}
  • 실행

9. 열거형 클래스

  • enum class 키워드 사용
  • 생성자 및 프로퍼티, 함수를 정의 할 수 있다.
  • 함수를 정의하기 위해서는 세미콜론이 필요
enum class PaymentStatus(val label: String) {
    UNPAID("미지급"), 
    PAID("지급 완료"),
    FAILED("지급 실패"),
    REFUNDED("환불"); // 함수를 정의하기 위해서는 세미콜론이 필요
}
  • 추상 함수를 선언하고 구현 할 수 있다.
enum class PaymentStatus(val label: String) {
    UNPAID("미지급") {
        override fun isPayable(): Boolean = true
    },
    PAID("지급 완료") {
        override fun isPayable(): Boolean = false

    },
    FAILED("지급 실패") {
        override fun isPayable(): Boolean = false

    },
    REFUNDED("환불") {
        override fun isPayable(): Boolean = false
    };

    abstract fun isPayable(): Boolean
}
  • 인터페이스를 상속받을 수 있다.
enum class PaymentStatus(val label: String): Payable {
    UNPAID("미지급") {
        override fun isPayable(): Boolean = true
    },
    PAID("지급 완료") {
        override fun isPayable(): Boolean = false

    },
    FAILED("지급 실패") {
        override fun isPayable(): Boolean = false

    },
    REFUNDED("환불") {
        override fun isPayable(): Boolean = false
    };

}

interface Payable {
    fun isPayable(): Boolean
}

인스턴스 생성 및 사용

fun main() {

    println(PaymentStatus.REFUNDED.label)

    if (PaymentStatus.UNPAID.isPayable()) {
        println("결제 가능 상태")
    }

    val paymentStatus = PaymentStatus.valueOf("PAID")
    println(paymentStatus.label)

    if (paymentStatus == PaymentStatus.PAID) {
        println("결제 완료 상태")
    }

    for (status in PaymentStatus.values()) {
        println("[${status.name}](${status.label}) : ${status.ordinal}")
    }
}
  • 실행
profile
물흐르듯 개발하다 대박나기

0개의 댓글