Kotlin Study 06_1

IDE : Intellij
JDK : zulu11

클래스와 객체

객체 지향 프로그래밍

용어

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

In Kotlin

KotlinOther Language
Class-
PropertyAttribute, Variable, Field, Data
MethodFunction, Operation, Behavior
ObjectInstance

클래스와 추상화

BirdClassDefine.kt

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) = println("Sing vol: $vol")
}

fun main() {
    val coco = Bird()   // 객체 생성
    coco.color = "blue" // 객체 프로퍼티에 값 할당

    println("coco.color: ${coco.color}")    // 객체 프로퍼티 읽기
    coco.fly()  // 객체 메서드 사용
    coco.sing(3)
}

객체 생성 및 사용

생성자

class 클래스이름 constructor(매개변수) {	// 주 생성자
	...
	constructor(매개변수){	// 부 생성자
		// 프로퍼티 초기화
	}
    
    ...
}

객체가 생성될 때 기본적으로 호출되는 함수

BirdSecondaryConstructor.kt

class Bird{    // 클래스 정의
    var name: String
    var wing: Int
    var beak: String
    var color: String

    constructor(_name: String, _wing: Int, _beak: String, _color: String){  // 제 1 부 생성자
        this.name = _name
        this.wing = _wing
        this.beak = _beak
        this.color = _color
    }

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

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

fun main() {
    val bird1 = Bird("mybird", 2, "short", "blue")
    val bird2 = Bird("mybird2", "long")

    // bird1
    println("bird1.name: ${bird1.name}")
    println("bird1.wing: ${bird1.wing}")
    println("bird1.beak: ${bird1.beak}")
    println("bird1.color: ${bird1.color}")
    bird1.fly()

    println()
    // bird2
    println("bird2.name: ${bird2.name}")
    println("bird2.wing: ${bird2.wing}")
    println("bird2.beak: ${bird2.beak}")
    println("bird2.color: ${bird2.color}")
    bird2.fly()

주 생성자 없이 부 생성자를 여러개 가지는 Bird 클래스

실행결과

bird1.name: mybird
bird1.wing: 2
bird1.beak: short
bird1.color: blue
Fly wing: 2

bird2.name: mybird2
bird2.wing: 2
bird2.beak: long
bird2.color: grey
Fly wing: 2

BirdPrimaryConstructor.kt

class Bird(
    var name: String,
    var wing: Int,
    var beak: String,
    var color: String
    ) {
    // 프로퍼티는 매개변수 안에 var를 사용해 프로퍼티로서 선언됨

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

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

    coco.color = "yello"
    println("coco.color: ${coco.color}")
    coco.fly()
    coco.sing(3)
}

주 생성자의 매개변수에 프로퍼티가 선언되었으므로 본문에서 프로퍼티 선언이 생략된다.

실행결과

coco.color: yello
Fly wing: 2
Sing vol: 3

BirdPrimaryInit.kt

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")

    coco.color = "yello"
    println("coco.color: ${coco.color}")
    coco.fly()
}

init 초기화 블록에서 출력문, 프로퍼티, 메서드 등과 같은 코드를 사용할 수 있다.

실행 결과

----- 초기화 블록 시작 -----
이름은 mybird, 부리는 short
Sing vol: 3
-----  초기화 블록 끝  -----
coco.color: yello
Fly wing: 2

프로퍼티 기본값 지정

class Bird(
    var name: String = "NONAME",	// name의 기본값 "NONAME"
    var wing: Int = 2,	// wing의 기본값 2
    var beak: String,
    var color: String
) {
    // 메서드
    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = println("Sing vol: $vol")
}

fun main() {
    val coco = Bird(beak = "long", color = "red")

    println("coco.name: ${coco.name}, coco.wing: ${coco.wing}")
    println("coco.color: ${coco.color}, coco.beak: ${coco.beak}")
}

생성자 매개변수에 기본값을 지정할 수 있다.

coco.name: NONAME, coco.wing: 2
coco.color: red, coco.beak: long

상속

BirdChildClassed.kt

// open 키워드로 상속 가능한 클래스 선언
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()
}

open 키워드로 상속 가능한 클래스를 선언하고, 파생 클래스에서 상속하여 사용할 수 있다.

실행결과

Coco: mybird, 2, short, blue
Lark: mylark, 2, long, brown
Parrot: myparrot, 2, short, multiple, korean
Happy Song!
Speak! korean
Fly wing: 2

다형성

OverloadCalc.kt

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): Int = x + y + z
    fun add(x: String, y: String): String = x + y
}

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

다양한 매개변수로 오버로딩된 add 메서드

실행 결과

5
4.5
8
HelloWorld

BirdOverridingEx.kt

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 Parrot(
    name: String,
    wing: Int = 2,
    beak: String,
    color: String,
    var language: String = "natural"
) : Bird(name, wing, beak, color) {
    fun speak() = println("Speak! $language")
    override fun sing(vol: Int) {
        println("I'm a parrot! The volume level is $vol")
        speak()
    }
}

fun main() {
    val parrot = Parrot(name = "myparrot", beak = "short", color = "multiple")
    parrot.language = "English"

    println("Parrot: ${parrot.name}, ${parrot.wing}, ${parrot.beak}, ${parrot.color}, ${parrot.language}")
    parrot.sing(5)
}

기반 클래스에서 open 키워드, 파생 클래스에서 override 키워드를 사용하여 메서드 또는 프로퍼티를 오버라이딩 할 수 있다.
파생 클래스에서 더이상 메소드의 오버라이딩을 막고자 한다면, override 키워드 앞에 final 키워드를 사용하여 재정의를 막을 수 있다.


super와 this의 참조

super, this 키워드 이용

superthis
super.{프로퍼티 이름}this.{프로퍼티 이름}
super.{메서드 이름}()this.{메서드 이름}()
super()this()

상위 클래스는 super 키워드, 현재 클래스는 this 키워드로 참조 가능하다.

PersonThisSuper.kt

open class Person {
    constructor(firstName: String){
        println("[Person] firstName: $firstName")
    }

    constructor(firstName: String, age: Int){	// 3
        println("[Person] firstName: $firstName, $age")
    }
}

class Developer: Person {
    constructor(firstName: String): this(firstName, 10) {	// 1
        println("[Developer] $firstName")
    }

    constructor(firstName: String, age: Int): super(firstName, age) {	// 2
        println("[Developer] $firstName, $age")
    }
}

fun main() {
    val sean = Developer("Sean")
}

main의 Developer("sean")으로 Developer의 1번 생성자를 진입하며, this(firstName, 10)에 의해 2번 생성자를 호출한다. 여기서 다시 super(firstName, age)에 의해 상위 클래스인 Person의 3번 생성자를 호출한다.

실행 결과

[Person] firstName: Sean, 10
[Developer] Sean, 10
[Developer] Sean

PersonPriSeconRef.kt

class Person(
    firstName: String,
    out: Unit = println("[Primary Constructor] Parameter")
) { // 2. 주 생성자
    val fName = println("[Property] Person fName: $firstName")  // 3. 프로퍼티 할당

    init {
        println("[init] Person init block") // 4. 초기화 블록
    }

    // 1. 부 생성자
    constructor(
        firstName: String, age: Int,
        out: Unit = println("[Secondary Constructor] Parameter")
    ) : this(firstName) {
        println("[Secondary Constructor] Body: $firstName, $age")   // 5. 부 생성자 본문
    }
}

fun main() {
    val p1 = Person("Kildong", 30)  // 1 -> 2 호출 | 3 -> 4 -> 5 실해
    println()
    val p2 = Person("Dooly")    // 2 호출 | 3 -> 4 실행
}

부 생성자에서 this를 사용해 주 생성자를 호출할 수 있다.

실행 결과

[Secondary Constructor] Parameter
[Primary Constructor] Parameter
[Property] Person fName: Kildong
[init] Person init block
[Secondary Constructor] Body: Kildong, 30

[Primary Constructor] Parameter
[Property] Person fName: Dooly
[init] Person init block

InnerClassRef.kt

open class Base {
    open val x: Int = 1
    open fun f() = println("Base Class f()")
}

class Child : Base() {
    override val x: Int = super.x + 1
    override fun f() = println("Child Class f()")

    inner class Inside {
        fun f() = println("Inside Class f()")

        fun test() {
            f() // Inside의 f()
            Child().f() // Child의 f()
            super@Child.f() // Base의 f()
            println("[Inside] [email protected]: ${super@Child.x}")
        }
    }
}

fun main() {
    val c1 = Child()
    c1.Inside().test()
}

Inner Class에서 Outer Class의 Super Class를 호출하려면 super 키워드와 함께 @기호 옆에 Outer Class의 이름을 작성해야 한다.

실행 결과

Inside Class f()
Child Class f()
Base Class f()
[Inside] [email protected]: 1

AngleBracketTest.kt

open class A {
    open fun f() = println("A Class f()")
    fun a() = println("A Class a()")
}

interface B {
    fun f() = println("B Interface f()")
    fun b() = println("B Interface b()")
}

class C : A(), B {
    override fun f() = println("C Class f()")

    fun test() {
        f() // C의 f()
        b() // B의 b()
        super<A>.f()    // A의 f()
        super<B>.f()    // B의 f()
    }
}

fun main() {
    val c = C()
    c.test()
}

Kotlin에서는 Java처럼 다중 상속이 되지 않는다. 하지만 인터페이스로 필요한 만큼 다수의 인터페이스를 지정해 구현할 수 있다.
동일한 이름의 프로퍼티나 메서드가 있다면 앵글 브래킷(<>) 을 사용하여 접근하려는 클래스나 인터페이스 이름을 정하여 사용할 수 있다.


캡슐화

가시성 지시자

  1. private : 이 요소는 외부에서 접근할 수 없다.
  2. public : 이 요소는 어디서든 접근이 가능하다. (default)
  3. protected : 외부에서 접근할 수 없으나 하위 상속 요소에서는 가능하다.
  4. internal : 같은 정의의 모듈 내부에서는 접근이 가능하다.

    주 생성자 앞에 가시성 지시자를 사용하는 경우, constructor
    키워드를 생략할 수 없다.

InternalTest.kt

internal class InternalClass {
    internal var i = 1
    internal fun icFunc() {
        i += 1  // 접근허용
    }
    fun access() {
        icFunc()    // 접근허용
    }
}

class Other {
    internal val ic = InternalClass()   // 프로퍼티를 지정할 때, internal로 맞춰줘야 함.
                                        // 근데 intellij에서 private으로 바꾸라고 권장
    fun test() {
        ic.i
        ic.icFunc()
    }
}

fun main() {
    val mic = InternalClass()
    mic.i
    mic.icFunc()

    val o = Other()
    o.test()
}

Kotlin의 Internal은 프로젝트 단위의 모듈을 가리킨다. 기존 Java에서는 package라는 지시자에 의해 패키지 이름이 같은 경우 접근을 허용했으나, Kotlin에서는 패키지에 제한하지 않고 하나의 모듈 단위를 대변하는 internal을쓴다.
만약 프로젝트에 모듈이 하나일 경우, internal의 접근 범위는 프로젝트 전체가 된다.


좋은 웹페이지 즐겨찾기