코틀린 기초

김소희·2023년 12월 5일
2

변수 선언 방법

  • val(value)와 var(variable)키워드를 사용한다.
  • val은 불변(재할당 불가), var는 가변 변수일때 사용한다.
  • 아무리 var 코드라도 타입을 바꾸어 재할당은 오류가 발생한다.
//키워드 변수명 : 타입 값
val a : Int = 1

//타입을 생략해도 추론해서 컴파일됨
val b = 1

//지연 할당시 타입 지정은 필수
val c : Int
c = 3

함수 선언 방법

  • function의 약자인 fun 키워드를 사용한다.
  • 바디가 있으면 반환타입을 필수로 써야 컴파일 오류가 발생하지 않는다.
//fun 함수명(함수의 인자) : 반환타입 {바디에 메인로직}
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

//반환타입이 없는 함수는 Unit타입을 반환한다 
fun sum(a: Int, b: Int) {
	println("$a + $b = ${a + b}")
}

//디폴트 파라미터
fun greeting(message : String = "안녕하세요") {
	println(message)
}
fun main() {
	greeting()
    greeting("Hi)
} // 결과값 : 안녕하세요와 Hi 두개가 반환됨

//named argument
fun log(level: String = "INFO", message : String) {
	println("[$level]$messag")
}
fun main() {
	log(massage = "인포 로그")
    log(level = "DEBUG", "디버그 로그")
    log("WARN", "워닝 로그")
    log(level = "ERROR", message = "에러 로그")
}
// 결과값 : [INFO]인포 로그, [DEBUG]디버그 로그...

흐름제어

  • 코틀린에서는 자바의 if-else문이 아닌 if-else'식'이기 때문에 값을 할당할 수도 있다.
if-elseval job = "Developer"

if(job == "Developer") {
	println("개발자")
} else {
	println("개발자아님")
}

val age : int = 10

val str = if(age > 20) {
	"성인"
} else {
	"미성년자"
}
  • 자바의 switch/case문과 비슷하게 코틀린에서는 when식을 사용한다.
  • else는 생략가능하다.
val day = 2

val result = when (day) {
	1 -> "월요일"
    2 -> "회요일"
    3 -> "수요일"
    4 -> "목요일"
    5 -> "금요일"
    else -> "주말"
}
println(result) //결과 화요일

//예시2
enum class Color {
    red, green
}

fun getColor() = Color.red

fun main() {
    when (getColor()) {
        Color.red -> print("RED")
        Color.green -> print("GREEN")
        else -> {
            print("BLUE")
        }
    }
}
  • 범위 연산자(..)를 이용해서 for-loop 돌리기
for (i in 0..3) {
	print(i)
}
//결과 0123 (0<=..<=3)


for (i in 0 until 3) {
	print(i)
}
//결과 012 (0<=until<3)


for (i in 0..6 step 2) {
	print(i)
}
//결과 0246 (2씩증가)


for (i in 3 downTo 1) {
	print(i)
}
//결과 321 (1씩감소)


val num = arrayOf(1,2,3)

for(i in num) {
	print(i)
}
//결과 123 for문과 동일


val x = 5

while (x>0) {
	print(x)
    x--
}
//결과 54321 while문과 동일

널 안정성

  • 코틀린을 비롯한 최신 언어에서는 null가능성을 컴파일러가 미리 감지하기 때문에 nullPointerException 가능성이 줄어든다.
  • 자바 코드를 코틀린으로 바꾸거나 함께 운용할 경우에는 nullable 가능성을 염두해야 한다.
val a : String = null
// null에 빨간 밑줄이 생겨서 알려줌


val b : String = "abc"
b= null
// null을 재할당해도 알려줌


val a : String? = null
println(a?.length)

val b : Int = if (a != null) a.length else 0
println(b)
// 결과값 null,0
// 안전연산자(?)를 붙이면 nullable하기 때문에 컴파일 오류가 사라짐


val c = a?.length ?: 0
println(c)
// 엘비스 연산자를 사용하면,
// 좌변이 null일때 우변을 리턴하여 null을 처리함


throw NullPointException()
//직접 호출하면 NPE발생함

val name: String? = null
val length: Int = name!!.length
// 단언연산자(??)를 사용하면 null이 발생하지않는
// 안전한 코드다 라고 컴파일러에게 알려주는 역할을 하는데
// 이때 null이 들어오면 NPE가 발생함

예외처리

  • 예외구조는 자바와 동일하며 Exception은 예외처리를 필수로 해야한다.
  • 다만 자바와 달리 체크드 익셉션을 강제하지 않기에 컴파일러가 오류를 띄우지 않는다.
  • 하지만 원한다면 try-catch와 finally을 이용해서 처리할 수 있다.
  • try-catch문이 아닌 try-catch식이기에 값을 반환할 수도 있다.
try {
	Thread.sleep(1)
} catch (e: Exception) {
	println("예외 발생")
} finally {
	println("finally발생")
}


val a = try {
	"1234".toInt()
} catch (e: Exception) {
	println("예외 발생")
} 
println(a)


fun failFast(massage: String): Nothing {
	throw IllegalArgumentException(message)
}
//직접 호출할 수도 있다.

클래스와 프로퍼티

  • class 키워드를 사용하여 클래스를 만들 수 있는데 바디가 없는 클래스도 생성가능하다.
  • constructor 키워드로 클래스 생성자를 만들 수 있지만 주로 애너테이션을 같이 쓰는 경우에만 쓰인다.
  • class property를 선언할 때 property간에 후행 쉼표(Trailing commas)를 사용할 수 있다.
  • 클래스 생성시 컴파일러에 의해 getter, setter가 자동으로 생성되는데,
    만약 타입이 val이라면 setter없이 getter만 생성된다.
  • field 식별자를 이용해서 필드(property)의 참조에 접근할 수 있다.
    (=backing field에 접근한다)
    field를 사용하지않으면 set이 set을 호출하는 무한재귀 상황으로 스택오버플로우가 발생한다.
  • property로 객체의 상태를 표현하고, 메서드로 행위를 표현함으로서 코틀린의 클래스는 객체지향적이다.
    특정 조건에 따라 상태를 나타낼 때 프로퍼티 문법을 사용하면 유용하다.
class Coffee {
}
// 클래스 생성


class BodyEmptyClass
// 바디가 없는 클래스 생성

class Coffee constructor(val name:String){
}
//생성자가 있는 클래스


class Coffee(
	val name:String = "", // 후행쉼표
    val price: Int = 0, // 후행쉼표
) {...}

fun main() {
	val coffee = Coffee()
    coffee.name = "아메리카노"
    coffee.price = 2000
    
    println("${coffee.name} 가격은${coffee.price})
}
// 결과 아메리카노 가격은 2000


val brand: String
	get() = "스타벅스"
// getter 커스텀 방법 1   


val brand: String
    get() {
    	return "스타벅스"
    }
// getter 커스텀 방법 2


val quantity: Int = 0
    set(value) {
    	if(value>0) {
        	field = value 
        }
    }
// field식별자 사용예시


coffee.iecd = true
if(coffe.iced) {
	println("아이스 커피")
}
//if안에 쓰인 coffe.iced는 프로퍼티이다.

상속

  • 상속은 객체지향 핵심 원칙 중 하나로 상속을 통해 기존 코드를 재사용하거나 확장할 수 있다.
  • 자바는 모든 클래스가 상속이 가능하지만 상속에 따른 부작용이 발생할 경우를 대비해 final 키워드로 상속을 막을 수 있다. 상속이 목적인 클래스가 아니면 모두 final로 작성하는 것이 좋다.
  • 코틀린은 모든 클래스가 final 이기에 상속이 필요한 경우 open 키워드로 상속을 허용할 수 있다.
  • override 키워드로 상위클래스에서 open한 함수나 프로퍼티를 재정의 할 수 있다.
  • super키워드로 상위클래스 함수나 프로퍼티를 재사용 할 수 있다.
  • abstract키워드로 추상 클래스(상속 후 하위클래스에서 재정의를 강제하는 클래스)를 구현할 수 있다.
  • 코틀린의 최상위 클래스는 any클래스로 queals() toString() HashCode() 등을 가지고 있다.
open class Cat { 
	open var age: Int = 0
    open fun crying() {
    	println("냐옹")
    }
}
// open키워드 사용


class Persian : Cat() { // 상속받음
	override var age : Int = 0
    override fun crying() {
    	println("미야")
    }
}
// 상속받음


class RussianBlue(override var age : Int = 0) : Cat() { 

    override fun crying() {
    	println("미야")
    }
}

fun main() {
	val cat = RussianBlue(2)
    println(cat.age)
    cat.crying()
//기본생성자 사용예시


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

class BackendDeveloper(override var age : Int) : Developer() {

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

fun main() {
	val backendDeveloper = BackendDeveloper(20)
    println(backendDeveloper.age)
    backendDeveloper.code("Kotlin")
}
//추상클래스 예시

인터페이스

  • interface 키워드로 인터페이스를 만들 수 있다.
  • 코틀린의 인터페이스는 인터페이스 내부에 구현해야하는 추상함수 + 구현을 가진 함수 모두를 정의 할 수 있다.
  • 클래스에서 인터페이스를 구현할때는 콜론(:)과 인터페이스명을 적으면 된다.
  • 인터페이스도 상위 인터페이스를 가질 수 있다. (: 상위인터페이스명을 붙이면 된다.)
  • 클래스가 여러개의 인터페이스를 구현할 수도 있지만 동일한 시그니처(동일한 함수명인) 함수가 있으면 오류가 발생할 수 있으므로 'super<인터페이스명>.함수명()' 으로 접근할 수 있다.
class Product(val name: Stringm val price: Int)

Interface Wheel { //상위 인터페이스
	fun roll()
}

Interface Cart : Wheel { //하위 인터페이스
	var coin: Int // 구현체에서 무조건 구현해야할 추상 프로퍼티가 됨
	fun add(product: Product)
    fun rent() {
    	if (coin > 0) {
        	println("카트를 대여합니다.")
        }
    }
    
    override fun roll() {
    	println("카트가 굴러갑니다.")
    }
}

class MyCart(override var coin: Int) : Cart { //프로퍼티 구현함
	override fun add(product: Product) {
    	if(coin <= 0) prontln("코인을 넣어주세요.")
        else println("${product.name}이 카트에 추가되었습니다.")
    }
}

fun main() {
	val cart = MyCart(coin=0)
    cart.rent()
	val cart = MyCart(coin=100)
    cart.rent()
    cart.roll()
    cart.add(Product(name = "연필", price = 1000))
}
// 결과값: 코인을 넣어주세요., 결과 카트를 대여합니다.,
// 카트가 굴러갑니다., 연필이 카트에 추가되었습니다.

열거형 클래스

  • 코틀린에서는 서로 연관된 상수들의 집합을 enum class를 사용하여 정의할 수 있다.
  • 이넘클래스도 클래스이기 때문에 인터페이스를 구현할 수 있다.
  • ordinal 키워드를 사용하면 프로퍼티에 선언된 순서를 알 수 있다.
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
}

fun main() {
	if(PaymentStatus.UNPAID.ispayable()) {
		println("결제 가능 상태")
    }
}
// 결과: 결제 가능 상태

출처: 패스트캠퍼스 코틀린강의

profile
백엔드 자바 개발자 소희의 노트

0개의 댓글