본문 바로가기
Kotlin/Kotlin 프로그래밍

[Kotlin] 클래스와 객체 (1)

by 주 녕 2021. 4. 16.
728x90

모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다. 

 

객체 지향 프로그래밍(OOP: Object-Oriented Programming)

: 프로그램의 구조를 객체 간 상호작용으로 표현한 프로그래밍 방식

↔ 절차적 프로그래밍(Procedual Programming) : 코딩한 순서대로 프로그래밍 수행될 수 있도록 작성하는 프로그래밍 방식

       * 연속적인 코드의 순소에 따라 작동하기 때문에 단순하고 오류를 예측하기 쉽지만 구조적이지 못해 프로그램 설계가 어려움

  • 추상화 (Abstraction) : 특정 클래스를 만들 때 기본 형식을 규정하는 방법
  • 인스턴스 (Instance) :  클래스로부터 생성한 객체
  • 상속 (Inheritance) : 부모 클래스의 내용을 자식 클래스가 그대로 물려받음
  • 다형성 (Polymorphism) : 하나의 이름으로 다양한 처리 제공
  • 캡슐화 (Encapsulation) : 내용을 숨기고 필요한 부분만 사용
  • 메세지 전송 (Message Sending) : 객체 간에 주고받은 메세지
  • 연관 (Association) : 클래스 간의 관계

 


클래스와 객체의 정의

Kotlin에서 사용하는 용어 정리

  • 클래스 (Class) : 분류, 범주
  • 프로퍼티 (Property) : 속성(Attribute), 변수(Variable), 필드(Field), 데이터(Data)
  • 메서드 (Method) : 함수(Function), 동작(Operation), 행동(Behavior)
  • 객체 (Object) : 인스턴스(Instance)

클래스

추상화 : 목표로 하는 대상에 대해 필요한 만큼 속성과 동작을 정의하는 과정

→ 공통의 특징을 골라내 클래스로 정의 : class 키워드

    class Bird {
        // 프로퍼티
        // 메서드
    }
    
    class Bird  // 중괄호 생략 가능

객체

클래스라는 개념의 실체

→ 클래스는 일종의 선언일 뿐 실제 메모리에 존재하는 것이 아님. 객체를 생성해야 객체가 물리적인 메모리 영역에서 실행됨

클래스로부터 객체(인스턴스, Instance)를 생성하는 것을 '구체화' or '인스턴스화(Instantiate)'

 좀 더 정확한 표현 : 특정 클래스로부터 만들어진 객체는 그 클래스의 인스턴스 

 

class Bird {
    var name: String = "mybird"
    var wing: Int = 2
    var beak: String = "short"
    var color: String = "blue"

    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = print("Sing vol: $vol")
}

fun main() {
    val coco = Bird()
    coco.color = "blue"

    println("coco.color: ${coco.color}")
    coco.fly()
    coco.sing(3)
}
  • class 키워드를 사용하여 Bird 라는 클래스를 정의함
  • 변수 선언과 같은 방법으로 프로퍼티 선언 (name, wing, beak, color)
  • 함수 선언과 같은 방법으로 메서드 선언(fly(), sing())
  • coco 라는 객체를 생성함 : 클래스()
  • 점(.) 표기법으로 객체의 프로퍼티, 메서드 접근 가능

 

생성자

외부에서 인자를 받아 초기화할 수 있도록 하는 특별한 함수 constructor()

클래스를 통해 객체가 만들어질 때 기본적으로 호출되는 함수

부 생성자

class Bird {
    // 프로퍼티 - 선언만 함
    var name: String
    var wing: Int 
    var beak: String
    var color: String
    
    // 부 생성자
    constructor(name: String, wing: Int, beak: String, color: String) {
        this.name = name
        this.wing = wing
        this.beak = beak
        this.color = color
    }

    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = print("Sing vol: $vol")
}

fun main() {
    // 객체 생성과 동시에 초기화
    val coco = Bird("mybird", 2, "short", "blue")
    coco.color = "blue"

    println("coco.color: ${coco.color}")
    coco.fly()
    coco.sing(3)
}
  • 부 생성자는 클래스의 본문에 함수처럼 선언
  • 프로퍼티는 선언만 하고, 부 생성자의 매개변수를 통해 초기화할 수 있음
  • this 키워드 : 객체 자신에 대한 참조로 클래스 내부에 있는 함수에서 프로퍼티를 참조
    • 프로퍼티의 nameconstructor의 매개변수 name은 다른 것
    • Bird 클래스를 가리키는 this 키워드를 통해 프로퍼티를 지정하고 있음
    • this 키워드를 사용하지 않으려면 매개변수 이름 앞에 언더스코어를 이용(_)
class Bird {
    // 프로퍼티 - 선언만 함
    var name: String
    var wing: Int
    var beak: String
    var color: String

    // 부 생성자1 : 매개변수에 언더스코어 사용
    constructor(_name: String, _wing: Int, _beak: String, _color: String) {
        name = _name
        wing = _wing
        beak = _beak
        color = _color
    }

    // 부 생성자2 
    constructor(_name: String, _beak: String) {
        name = _name
        wing = 2
        beak = _beak
        color = "grey"
    }
    ...
}

fun main() {
    val coco = Bird("mybird", 2, "short", "blue") // 부 생성자 1
    val nana = Bird("bird2", "long") // 부 생성자 2
    ...
}
  • 코틀린에서는 한 클래스에 여러 개의 부 생성자를 가질 수 있음
    • constructor()의 형태로 매개변수가 다르게 여러 번 선언 가능
    • 인자의 개수에 따라 생성자를 다르게 호출 할 수 있음

주 생성자

class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
    // 프로퍼티는 본문에서 생략

    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = print("Sing vol: $vol")
}

fun main() {
    val coco = Bird("mybird", 2, "short", "blue") 
    coco.color = "blue"

    println("coco.color: ${coco.color}")
    coco.fly()
    coco.sing(3)
}
  • 클래스 이름과 블록 시작 부분 사이에 선언 : 클래스명 constructor (프로퍼티1: 자료형, ...)
    • constructor 키워드 생략 가능
    • 내부의 프로퍼티를 생략하고 생성자의 매개변수에 프로퍼티 표현을 넣을 수 있음
    • var, val을 사용하여 매개변수를 선언 - 생성자에 this 키워드나 매개변수에 언더스코어를 붙여 인자를 할당할 필요가 없음
    • 프로퍼티에 기본 값을 같이 선언하여 지정할 수 있음

주 생성자를 사용하는 경우, 변수를 초기화하는 것 말고 특정한 작업을 하도록 코드를 작성할 수 없음

따라서 초기화에 꼭 사용해야할 코드가 있다면 init{ ... } 초기화 블록을 클래스 선언부에 넣어 초기화해야 함

 

객체 생성 시점에서 해당 코드 블록이 실행됨

class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
    // 프로퍼티는 본문에서 생략

    init {
        println("----------초기화 블록 시작----------")
        println("이름은 $name, 부리는 $beak")
        this.sing(3)
        println("----------초기화 블록 끄읕----------")
    }

    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = println("Sing vol: $vol")
}

fun main() {
    val coco = Bird("mybird", 2, "short", "blue")
}

 

상속

: 자식 클래스를 만들 때 상위 클래스(부모 클래스)의 속성과 기능을 물려받아 계승함

상속을 이용하면 파생 클래스(자식 클래스)는 기반 클래스(부모 클래스)의 모든 내용을 다시 만들지 않아도 되며,

기반 클래스와 다른 프로퍼티와 메서드만 추가하면 되는 편리함이 있음

open class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = println("Sing vol: $vol")
}

class Lark(name: String, wing: Int, beak: String, color: String) : Bird(name, wing, beak, color) {
    fun singHitone() = println("Happy Song!")
}

class Parrot : Bird {
    val language: String
    constructor(name: String, wing: Int, beak: String, color: String, language: String) : super(name, wing, beak, color) {
        this.language = language
    }
    fun speak() = println("Speak! $language")
}

fun main() {
    val coco = Bird("mybird", 2, "short", "blue")
    val lark = Lark("mylark", 2, "long", "brown")
    val parrot = Parrot("myparrot", 2, "short", "multiple", "korean")

    println("Coco: ${coco.name}, ${coco.wing}, ${coco.beak}, ${coco.color}")
    println("Lark: ${lark.name}, ${lark.wing}, ${lark.beak}, ${lark.color}")
    println("Parrot: ${parrot.name}, ${parrot.wing}, ${parrot.beak}, ${parrot.color}, ${parrot.language}")

    lark.singHitone()
    parrot.speak()
    lark.fly()
}
  • 코틀린의 모든 클래스는 Any 클래스의 하위 클래스: 상위 클래스를 명시하지 않으면 Any 클래스를 상속받게 됨
  • open 키워드
    • open 키워드가 있어야 클래스가 상속할 수 있는 상태가 됨 (디폴트 : 상속할 수 없는 상태)
    • Java에서는 기본 상태에서 상속이 가능하며, fianl 키워드를 붙여야 상속할 수 없게 됨
  • Bird 상위 클래스 : Any , Bird 하위 클래스 : Lark, Parrot
    • Lark : 주 생성자를 사용하여 파생 클래스를 선언함
    • Parrot : 부 생성자를 사용하여 파생 클래스를 선언하고, language 프로퍼티를 추가/초기화하여 확장

🤨 Brid 클래스의 프로퍼티에는 var이 붙어있는데, Lark 클래스의 프로퍼티에는 없다?!

😯 val/var를 사용하는 경우는 클래스 내부에서 프로퍼티를 선언하는 경우에만!
→ 만약 값을 넣지 않는다면(위의 경우처럼) 주생성자에게 파라미터를 넘기기만 하면 된다!

 

다형성

이름이 동일하지만 매개변수가 서로 다른 형태를 취하거나 실행 결과를 다르게 가질 수 있는 것

동작은 동일하지만 인자의 형식이 달라지는 오버로딩(Overloading)과 상위와 하위 클래스에서 메서드나 프로퍼티의 이름은 같지만 기존의 동작을 다른 동작으로 재정의하는 오버라이딩(Overriding)이 있음

오버로딩

동일한 클래스 안에서 같은 이름의 메서드가 매개변수만 달리해서 여러 번 정의될 수 있음

→ 보통 다양한 자료형을 받아들일 수 있도록 연산자에 많이 사용함

fun main() {
    val calc = Calc()
    println(calc.add(3, 2))
    println(calc.add(3.2, 1.3))
    println(calc.add(3, 4, 5))
    println(calc.add("Hello ", "World"))
}

class Calc {
    fun add(x: Int, y: Int): Int = x+y
    fun add(x: Double, y: Double): Double = x+y
    fun add(x: Int, y: Int, z: Int) = x+y+z
    fun add(x: String, y: String) = x+y
}

오버라이딩

하위 클래스에서 새로 만들어지는 메서드가 이름이나 매개변수, 반환값이 이전 메서드와 똑같지만 기능이 새로 정의되는 것

open class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
    fun fly() = println("Fly wing: $wing")
    open fun sing(vol: Int) = println("Sing vol: $vol") // 오버라이딩 허용
}

class Lark(name: String, wing: Int, beak: String, color: String) : Bird(name, wing, beak, color) {
    fun singHitone() = println("Happy Song!")
    override fun sing(vol: Int) {  // 오버라이딩
        println("Lark Sing: $vol")
    }
}
  • open 키워드 : 오버라이딩을 허용
  • override 키워드 : 오버라이딩한 메서드임을 알림
  • final 키워드 : 하위 클래스에서 오버라이딩을 막음

 


reference >

728x90

댓글