GDSC HUFS 3기/Android with Kotlin Team 1

[1팀] Section 7: Kotlin - Object Oriented Programming(2)

dev-yen 2021. 10. 7. 03:28

이 글은 udemy 강의를 참고하여 작성하였습니다.

작성자 : 김예은

개발환경은 Windows, Android Studio입니다. 

 

1. 생성자 (Constructors)


코틀린에서 우리는 기본 생성자(primary constructor)와 하나 이상의 보조 생성자(secondary constructor)를 가질 수 있다.

val meeting = Meeting("Thursday", "Bob")
val anotherMeeting = Meeting("Wednesday", "Dave", "3 PM")

class Meeting(val day: String, val person : String){
	var time: String = "To be decided"
	init {
		println("첫 번째 초기화")
	}
	constructor(day: String, person: String, time : String) : this(day, person){
	this.time = time
	}
	init {
		println("두 번째 초기화")
	}
}

 

  • 주 생성자는 클래스 헤더의 일부이며 클래스 이름 뒤에 나오고 타입 파라미터는 선택사항이다.
  • 기본 생성자는 어떤 코드도 가지지 않고, 초기화 코드는 초기화 블록 init안에 위치할 수 있다.
  • 인스턴스를 초기화 하는 동안, 초기화 블럭은 클래스 본체에 나오는 순서와 같은 순서로 실행된다.
    (위 코드에서 '첫 번째 초기화'가 print된 후 '두 번째 초기화'가 print된다)
  • 생성자가 anootation이나 visibility modifier(public, private등)을 가진다면 constructor 키워드가 필요하다.

  • 우리는 필요에 따라 2개 이상의 보조 생성자를 가질 수 있는데, constructor 접두사를 사용하여 보조 생성자를 선언한다.
  • 클래스가 기본 생성자를 가진다면 각 보조 생성자는 기본 생성자에게 위임해야만 한다.
    (기본 생성자를 호출해야만 한다.)
    • 기본 생성자에 직접 하거나 다른 보조 생성자를 통해 간접적으로 하면된다.
    • 같은 클래스의 다른 생성자에 대한 위임은 this 키워드를 사용한다.

 

2. 상속 (Inheritance)


상속은 클래스의 기능을 확장하고자 할 때 현재의 클래스의 기능을 모두 가지고 자신만의 기능이 추가된 새로운 클래스를 정의 하는 방법이다. 따라서 상속을 하게 되면 클래스가 상, 하의 계층구조를 가지게 된다.

class MainActivity: AppCompatActivity() {
  val soldier = Soldier()
  soldier.shoot()

  val specialForces = SpecialForces()
  specialForces.sneakUpOnEnemy()
  specialForces.shoot()

  val paratRooper = Paratrooper()
  paratRooper.jumpOutOufPlane()
  paratRooper.shoot()

  val sniper = Sniper()
  sniper.shoot()
  sniper.getIntoPosition()
}

class Sniper : Soldier(){
	var sniperAmmo = 3
	override fun shoot() {
		when(sniperAmmo > 0){
			true->{
				Log.i("ACTION:", "Steady...Adjust for Wind... Bang")
				sniperAmmo--
		} false -> super.shoot()
	}
	fun getIntoPosition() {Log.i("ACTION:", "Preparing line of sight to target")}
}

class Paratrooper : Soldier(){
	fun jumpOutOfPlane(){Log.i("ACTION:", "Jump out of plane")}
}

class SpecialForces : Soldier() {
	fun sneakUpOnEnemy() {Log.i("ACTION:" ,"Sneaking Up on Enemy")}
}

open class Soldier(){ //상위 클래스
	open fun shoot(){
		Log.i("ACTION:", "BANG BANG BANG")
	}
}

코틀린에서 모든 클래스는 공통의 상위 클래스(superclass)로 Any클래스를 가진다. 이것이 클래스에 상위 클래스를 선언하지 않을 때 가지는 기본 상위 클래스이다.

  • 위 코드에선 Soldier 클래스가 상위 클래스이며 나머지는 하위 클래스이다.
  • open 키워드를 사용하여 상속할 수 있다.
    (자바와 다르게 코틀린은 오버라이드를 위해 명시적인 수정자가 필요한데, 멤버가 오버라이드 가능하다는 것을 나타내기 위해 open 키워드를 사용하고, 오버라이드 하고 있다는 것을 나타내기 위해 override 키워드를 사용한다.)
    open class Base {
        open fun v() { ... }
        fun nv() { ... }
    }
    
    class Derived() : Base() {
        override fun v() { ... }
    }
    이때 재 오버라이드를 못하게 하고싶으면 final수정자를 앞에 붙여주면 된다.
  • final override fun v()​
  • 파생 클래스의 코드는 super 키워드를 사용하여 상위 클래스의 메서드와 속성 접근자 구현을 호출할 수 있다.
    open class Foo {
        open fun f() { println("Foo.f()") }
        open val x: Int get() = 1
    }
    
    class Bar : Foo() {
        override fun f() {
            super.f()
            println("Bar.f()")
        }
        override val x: Int get() = super.x + 1
    }​

3. Polymorphism & Abstraction (다형성과 추상화)


클래스를 상속하다보면 하위 클래스에서 상위 클래스와 똑같은 이름의 프로퍼티나 메서드를 지정할 일이 생긴다. 하위 클래스에서 이름은 같지만 호출 매개변수가 다르거나, 전혀 다른 동작의 메서드를 정의할 필요가 있다.

이런 식으로, 이름은 같지만 매개변수가 다르거나 다른 동작을 하도록 하는 것을 다형성(Polymorphism)이라고 한다.

다형성의 종류로는 오버로딩(overloading)과 오버라이딩(overriding)이 있다.

 

오버로딩(Overloading)

오버로딩은 같은 이름의 메서드가 매개 변수만 달리하여 여러번 재정의 되는 것을 말한다. 덧셈에 대한 오버로딩을 다음 예시를 통해 알아보자.

class Calc{
    fun add(a: Int, b: Int): Int = a + b
    fun add(a: Int, b: Int, c: Int): Int = a + b + c
    fun add(a: String, b: String): String = a + b
}
fun main() {
    val calc = Calc()
    println(calc.add(1, 2))
    println(calc.add(1, 2, 3))
    println(calc.add("Hello", "Kotlin"))
}

 

오버라이딩(Overriding)

오버라이드는 자식 클래스가 상속 받은 부모 클래스에 있는 메서드를 재정의 하는 것을 말한다. 상속을 하는 부모 클래스에는 open 키워드가, 자식 클래스에서 오버라이드 하는 함수에는 override 키워드가 사용된다.

오버라이딩 예는 '상속' 설명에서 사용했던 Soldier 코드를 확인해보면 알 수 있다.

 

 

추상 클래스(Abstract Class)

추상 클래스는 아직 구현되지 않고 선언만 된 추상 메서드를 가지고 있는 클래스이다. 이 추상 클래스는 메서드가 구현되지 않아서 이 클래스를 직접 객체로 만들 수는 없으며 반드시 상속을 받는 자식 클래스(하위 클래스)가 있어야 한다.

추상 클래스는 상속을 통해 생성될 자식 클래스에서 메서드 오버라이딩에 강제성을 부여하기 위해 사용된다.

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}
  • 추상 클래스는 abstract 키워드로 선언할 수 있다. 
  • abstract으로 선언된 멤버는 그 클래스 내에 구현이 존재하지 않는다.
  • abstract 클래스와 함수에는 open을 붙일 필요가 없다.
  • 추상이 아닌 open멤버를 추상 멤버로 오버라이드 할 수 있다.
abstract class Animal{
	fun attack() { //자식 클래스에서 그냥 사용할 수 있는 일반 메서드
		println("Attack")
	}
	abstract fun sound() { //자식 클래스에서 오버라이딩 해야 사용할 수 있는 추상 메서드
		println("o")
	}
}

class Shark: Animal(){
	override fun sound(){
		println("x")
	}
}