dev-course day58

2rlokr·2025년 5월 28일

dev-course

목록 보기
39/43
post-thumbnail

오늘 배운 것

실습

생성자 (constructor)

  • 부 생성자는 주 생성자를 먼저 호출한 후 실행된다.
  • init() 은 여러 개 있을 수 있고, 순차적으로 실행된다.

메서드

val animal = OrdAnimal()
animal.eat("aa")

val playWithAnimal = animal.play()
playWithAnimal()

open fun eat(sth: String) = println("${sth}을/를 먹습니다!")

open fun play() : ()-> Unit = {
	println("동물이 뛰어다닙니다!")
    println("즐거워합니다.")
}
  • 함수에서 수행할 명령문이 한 줄이라면 eat과 같이 사용할 수 있다.
  • 여러 줄이더라도 등호를 사용하여 구현할 수 있는데, 이렇게 될 경우, play()는 함수 호출이 아닌, 함수를 반환하게 된다.

상속과 인터페이스

상속

기존 클래스(부모 클래스 또는 슈퍼클래스)의 속성과 메서드를 새로운 클래스(자식 클래스 또는 서브 클래스)에서 물려받아 사용하는 메커니즘이다. 상속을 통해 자식 클래스는 부모 클래스에서 정의된 모든 속성과 메서드를 그대로 사용할 수 있으며, 필요한 경우 이를 확장하거나 재정의(오버라이딩)할 수 있다. 따라서, 상속은 다형성을 구현하는 데에 중요한 역할을 한다.

상속은 객체 지향 설계의 원칙인 개방-폐쇄 원칙(Open-Closed Principle)을 구현하는 데에 중요한 도구이다.

open 키워드

open class Parent {

}

class Child : Parent() { // 부모 클래스의 생성자를 호출한 것

}
  • Kotlin에는 기본적으로 클래스에 final이 다 붙여져있다.
  • 따라서 어떤 클래스의 상속을 허용하려면 해당 클래스 앞에 open 변경자를 붙여야 한다.
  • 만약 부모 클래스에 기본 생성자가 달려있다면, 매개변수도 같이 넣어줘야 한다.

오버로딩 (Overloading)

같은 이름의 함수를 매개변수의 타입이나 개수만 다르게 하여 여러 개 정의하는 것을 말한다.

  • 메서드 시그니처(메서드 이름, 매개변수 타입 조합)가 달라야 한다.
  • 예외 처리는 동일해야 한다.

오버라이딩 (Overriding)

상위 클래스 (또는 인터페이스)에 정의된 메서드를 하위 클래스에서 재정의하는 것을 의미한다.

  • 명시적으로 open 키워드와 override 키워드를 통해 수행된다.
open class Dog {

	fun eat(food:String) : Unit {
    	println("${food}을/를 먹습니다.")
    }
    
    open fun bark(int times) : Unit {
    	for( n in 1..times) {
        	print("멍!")
        }
        println()
    }
}

class Husky : Dog() {
	override fun bark(times:Int) : Unit {
    	for(n in 1..times) {
        	print("멍멍!")
        }
    }
}
  • 오버라이드를 허용하고 싶은 메서드나 프로퍼티의 앞에도 open 변경자를 붙여야 합니다.

연산자 재정의 (Operator Override)

코틀린에서는 연산자도 재정의할 수 있다.

fun main() {

    val coffee1 = OpCoffee("아메리카노", "")
    val coffee2 = OpCoffee("카페라떼", "")

    val result: String = coffee1 + coffee2
    println("result = ${result}") // 첫번째 커피 : 아메리카노, 두번째 커피 : 카페라떼
}

class OpCoffee(val type: String, val size:String) {

    operator fun plus(other: OpCoffee) : String {
        return "첫번째 커피 : ${this.type}, 두번째 커피 : ${other.type}"
    }
}
  • operator 라는 키워드를 붙여주고 연산자를 사용하면 된다.
  • plus와 같이 고정된 이름을 사용해주면 되며, 이외에는 plusAssign (+=), minus(-), times(*), div(/), rem(%), compareTo(>), get(인덱싱), contains(in) 의 연산자를 오버라이딩할 수 있다.

super

상위 클래스 (또는 상위 인터페이스)의 멤버에 접근할 때 사용된다.

  • 주 생성자 선언 시 super()를 별도로 쓰지 않고 상속 구문에서 바로 인자를 전달하는 방식이 일반적이지만, 보조 생성자에서는 super(...)를 명시적으로 사용할 수 있다.
  • super()는 상속받은 클래스가 초기화되기 전에 반드시 상위 클래스가 먼저 초기화되어야 한다는 Kotlin의 규칙을 따르기 위해 필요하다.
class Husky : Dog() {
	override fun eat(food: String) : Unit {
    	super.eat(food)
        println("멍멍!")
    }
}
  • 부모 클래스의 메서드도 사용 가능하다.

추상 클래스 (Abstract Class)

클래스의 기본 형태를 정의하고, 이를 상속받아 구체적인 구현을 제공할 수 있도록 하는 클래스이다. 추상 클래스는 상속을 통해 공통적인 기능을 제공하면서, 자식 클래스에서 구체적인 동작을 정의할 수 있는 기반을 마련한다.

  • 직접적으로 인스턴스화될 수 없으며, 다른 클래스가 이를 상속받아 구현해야 한다.
  • 추상 클래스는 추상 메서드를 포함할 수 있다. (없어도 된다.)
    • 추상 메서드는 메서드의 선언만을 포함하며, 메서드 본체 ({})가 없다.
    • 추상 메서드는 자식 클래스에서 반드시 구현되어야 한다.
  • 직접적으로 인스턴스를 생성할 수 없다.

abstract

abstract class 클래스_이름 {

}
  • 추상 클래스 내에서 open으로 열어주지 않은 메서드는 오버라이드할 수 없다.
  • abstract 키워드가 붙여져있으면, open하지 않아도 오버라이드할 수 있다.
    • abstract가 붙여져있더라도, 오버라이드할 때는 override 키워드를 붙여줘야 한다.

인터페이스 (Interface)

클래스가 구현해야 하는 메서드의 집합을 정의하는 일종의 클래스를 위한 설계도이다. 인터페이스는 메서드의 시그니처(이름, 반환형, 매개변수)만을 정의하며, 메서드의 실제 구현은 이를 구현하는 클래스에서 수행해야 한다.

❓ 추상 클래스 vs 인터페이스

둘 다 객체 지향 프로그래밍에서 다형성을 지원하는 수단이지만, 추상 클래스는 구현된 메서드와 필드를 가질 수 있다. 반면, 인터페이스는 메서드의 시그니처만을 정의한다. 또, 인터페이스는 다중 상속을 통해 여러 기능을 클래스에 제공하는 역할에 중점을 두지만, 추상 클래스는 단일 상속만 가능하고, 클래스 간의 공통된 동작을 제공하는 데 주로 사용된다.

interface 키워드

interface Car {

	fun drive()
    
    fun reverse()
    
    fun onTurnSignal()
}
  • 메서드의 시그니처만 포함하면 된다.
  • 구현 메서드도 가질 수 있다.
  • 인터페이스의 메서드들은 public, abstract를 포함하고 있기 때문에, 명시하지 않아도 무관하다.
  • 인터페이스는 상수만을 가질 수 있으며, 이 상수들은 자동으로 public, static, final로 선언된다.

구현 (Implement)

class Sedan : Car {
	override fun drive() {
    	println("주행합니다.")
    }
    
    override fun reverse() {
    	println("후진합니다.")
    }
    
    override fun onTurnSignal() {
    	println("방향지시등을 켭니다.")
    }
}
  • 구현하고자 하는 인터페이스 뒤에 소괄호 없이 써주면 된다. 인터페이스는 생성자가 없기 때문에 소괄호를 쓰지 않는다.
  • 인터페이스의 메서드들이 abstract를 포함하고 있기 때문에, 오버라이드하기 위해 open을 쓰지 않아도 되지만, 구현체에서는 override를 붙여줘야 한다.

확장 함수 (Extension Function)

기존 클래스(내가 만든 클래스든, 라이브러리 클래스든)를 수정하지 않고도 마치 그 클래스의 메서드인 것처럼 새로운 함수를 "확장"해서 추가할 수 있다. 확장 함수는 클래스타입.함수이름 형식으로 선언하며, 해당 클래스의 인스턴스에서 바로 사용할 수 있다.

val sentence = "Hello World!"
sentence.printUpperCase() // HELLO WORLD!

fun String.printUpperCase() { 
    println(this.uppercase())
}

fun Int.customSum(target: Int) {
    println("두 값을 더하면 ${this + target}")
}

fun Int.printPrice() {
    println("가격가격")
}
  • 위와 같이 사용할 수 있다.
  • 확장함수도 가시성 지시자를 따르며, 기본값은 public이다.

느낀점

오늘은 집중도 잘됐고, 시간도 잘 흘렀던 것 같다. 몸이 다시 수업에 적응해가고 있는 것 같다! 아주 좋아 !
그리고 코틀린 재밌다 ! 신기한 기능이 너무 많다. 확장 함수 아주 흥미롭고, 상속이랑 구현하는 방식도 아주 흥미롭다. 코드 짤 때 override, open 이런 거 안 까먹고 잘 챙겨야 할 텐데 ㅎ하하하

그리고 오늘 점심시간 때 우수 참여자와 우수 출결자를 뽑아서 알려주셨는데,, ! 우수 참여자에 뽑혔다 !!! 너무 뿌듯하고, 기분이 좋잖아.. ㅠㅠ 열심히 들으려고 노력했는데, 그걸 알아봐주셔서 정말 감사합니다.. 더 열심히 해야지.. !! 근데 진짜 우수 출결자들 대단하신 것 같다. 아무튼 뿌듯한 날이다 ! 🥹🔥

수요일 수업도 잘 끝나가고 있다.. ! 내일, 모레 이틀만 버티자.. 아자 아자 !!

아 그리고, 오늘 마치고 할머니댁 가서 소고기 먹는다. 아싸.

0개의 댓글