Kotlin Programming Intermediate - 7
5편에 이어서 클래스의 상속과 오버라이드, 오버로드에 대해 살펴보겠습니다.
클래스 상속
상속
우선 상속이라는 것은 부모 클래스를 기준으로 자식 클래스가 부모클래스의 특징을 이어 받는 것입니다.
유전학의 개념으로 보면 부모와 자식이 닮아있다는 경우가 되겠지요.
그래서 이전 포스팅에서 A라는 부모 클래스를 B라는 자식 클래스가 상속받고자 할 때 클래스 A에 open
이라는 키워드가 붙었습니다.
open class Base (var firstName:String="길동", var lastName:String="홍", var age:Int=24) {
//class Base {
/*
var firstName:String
var lastName:String
var age:Int
constructor(firstName:String, lastName:String, age:Int) {
this.firstName = firstName
this.lastName = lastName
this.age = age
}
*/
open val fullName:String
get() = "$firstName $lastName"
}
class Child(firstName:String, lastName:String, age:Int, var address:String) : Base(firstName, lastName, age) {
// 또는 var address:String
override val fullName:String // 오버라이딩하려면 오버리이드 받을 변수, 클래스, 메서드 모두 open이 들어가야 함.
get() = "$firstName $age $address"
}
우선 생성자는 주석처리된 부분처럼 멤버변수를 따로 선언해서 constructor
를 통해 만들어 줄 수도 있지만 클래스명 옆에 바로 선언해주는 것만으로도 생성자를 만들어준다는 것을 5번째 포스팅에서 살펴봤습니다.
여기에서는 Child
라는 클래스가 Base
라는 클래스를 상속받고 있으며, Child
클래스만의 멤버변수인 address
라는 문자열 변수를 갖습니다.
때문에 Child
클래스에서 생성자로 address
를 추가해주었고, 부모 클래스인 Base
에서 받아오는 변수 또한 선언해주었습니다.
그리고 부모 클래스의 fullName
이라는 변수를 오버라이드 했는데 이 때 부모 클래스는 상속을 해주는 입장이기 때문에 open
을 붙였고, 자식 클래스는 상속을 받아 재정의하는 입장이기 때문에 상속받은 변수에 override
를 붙여주었습니다.
fullName
은 별도의 값을 저장해둔 것이 아니라 get()
이라는 것을 보면 알 수 있듯이 getter
로 지정되어 있는 것입니다.
또 다른 예시를 살펴봅시다.
open class Bird(var name:String, var wing:Int, var color:String) {
fun fly() = println("fly wing: $wing")
override fun toString(): String {
return "Bird(name='$name', wing=$wing, color='$color')"
}
open fun allData() = print("$name $wing $color ")
}
Bird
라는 클래스는 open
을 보면 알 수 있듯이 다른 클래스에 상속해 줄 부모 클래스입니다.
생성자에는 세개의 변수가 존재하고 클래스 내부에는 두개의 멤버 메서드와 오버라이드된 toString
이 있습니다.
이 때 allData
라는 open
이 붙어 있기 때문에 이 멤버 메서드는 다른 클래스가 상속을 받아 오버라이드 할 수 있는 것이지요.
그래서 아래와 같이 두개의 클래스가 상속을 받습니다.
class Lark(name:String, wing:Int, color:String) : Bird(name, wing, color) {
fun singHitOne() = println("짹짹")
}
// 이렇게도 상속을 받을 수 있다.
class Parrot : Bird {
var volume:Int
constructor(name:String, wing:Int, color:String, volume:Int):super(name, wing, color) {
this.volume = volume
}
override fun toString(): String {
return super.toString() + "Parrot(volume=$volume)"
}
override fun allData() {
super.allData()
println("$volume")
}
}
Lark
는 코틀린 문법 대로 생성자를 클래스명 옆에 선언해주었고, Parrot
은 자바 문법대로 생성자를 클래스 내부에 만들어주었습니다.
여기에서 Parrot
클래스를 보면 생성자에 super
를 사용해주었는데 자바에서 마찬가지로 super
는 부모 클래스의 생성자를 호출해줍니다.
다시 말해서 constructor(name:String, wing:Int, color:String, volume:Int):super(name, wing, color)
는 부모클래스의 생성자까지 상속받아서 초기화 해주겠다는 의미가 됩니다.
부모 클래스의 allData
를 오버라이드해서 부모클래스의 allData
를 super
로 호출하였으며 추가적으로 volume
값을 출력하게 해주었습니다.
오버로드
메서드 오버로드는 이름은 갖지만 서로 다른 매개변수를 갖는 여러개의 메서드를 정의하는 것입니다.
어떤 클래스에 다음과 같이 메서드를 선언해주었다고 가정해봅시다.
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:Double, y:Double, z:Double):Double = x + y + z
}
add
라는 함수의 이름은 갖지만 매개변수로 받아오는 값이 서로 다릅니다. 이것을 오버로드라고 합니다.
이 클래스를 메인함수에서 객체로 생성하여 아래와 같이 사용할 수 있습니다.
fun main(args: Array<String>) {
val calc = Calc()
println(cal.add(3, 4))
println(cal.add(3.12, 4.34))
println(cal.add(3, 4, 5))
println(cal.add(3.12, 4.34, 7.54))
}
추상 클래스와 인터페이스
추상 클래스
추상 클래스는 abstract class로 보통의 클래스와 마찬가지로 멤버변수를 가질 수 있고 멤버 메서드도 가질 수 있습니다.
그러나 유일한 차이점은 메서드에서 일반 메서드가 아닌 추상 메서드가 선언되어야 한다는 것이고, 추상 메서드는 return
값이 없는 다시 말해서 프로토타입만 선언된 형태여야 합니다.
간단하게 예를 들어보면
abstract class Printer {
abstract fun print()
fun method() = println("Printer Method()")
}
class MyPrinter : Printer() {
override fun print() {
println("출력합니다!!")
}
}
이렇게 선언되어 있다고 할 때, MyPrinter
클래스는 추상 클래스인 Printer
를 상속받아 추상 클래스의 print
메서드를 오버라이딩합니다.
또한 추상 클래스는 자바와 마찬가지로 abstract
라는 추상키워드임을 명시하는 키워드를 기록해주어야 한다는 것입니다.
추상클래스를 상속받은 MyPrinter
클래스를 객체로 생성하여 사용해보면
fun main(args: Array<String>) {
val prt = MyPrinter()
prt.print()
}
처럼 사용할 수 있습니다.
또한 추상 클래스는 추상 메서드 뿐만 아니라 일반 메서드도 선언하여 사용할 수 있습니다.
abstract class Vehicle(val name:String, val color:String, val weight:Double) {
abstract var maxSpeed:Double // 추상 property
var year:Int = 2019
abstract fun start() // 추상 method
abstract fun stop()
fun displaySpecs() {
println("Name: $name, Color: $color, Weight: $weight, Year: $year, MaxSpeed: $maxSpeed")
}
}
여기에서 displaySpecs
메서드는 위에서 선언한 start
, stop
메서드와는 다르게 함수 내부가 이미 지정되어 있으므로 추상 메서드라고 볼 수 없으며 이는 일반 메서드로 보아야 합니다.
이처럼 추상 클래스는 일반 메서드, 추상 메서드, 멤버 변수를 가질 수 있으며 멤버 변수에 값을 할당해 줄 수도 있습니다.
최상위 객체 object
사용하여 상속받기
자바에서 Object
라는 최상위 객체가 있었습니다. 이를 사용하면 어떤 객체든 접근할 수 있었습니다.
코틀린에서도 이러한 방법이 허용되는데, 이를 변수에 넣어서 메인함수에서 바로 사용하는 방법도 있습니다.
추상 클래스인 Printer
와 이를 상속받는 클래스를 만들어서 변수 myPrinter
에 저장해 보겠습니다.
abstract class Printer {
abstract fun print()
fun method() = println("Printer Method()")
}
val myPrinter = object : Printer() {
override fun print() {
println("myPrinter print()")
}
}
이렇게 object
로 상속받아서 변수에 저장한 것을 메인 함수에서 바로 사용할 수 있습니다.
fun main(args: Array<String>) {
myPrinter.print()
}
인터페이스
인터페이스는 추상 메서드와는 또 다른 개념입니다.
자바에서의 인터페이스는 추상 메서드만을 포함할 수 있는 형태였으나 코틀린의 인터페이스는 멤버변수를 가질 수 있습니다.
예를 들어서 아래와 같은 인터페이스가 있다고 가정하면
interface Foo {
var bar:Int
fun method(str:String)
}
이를 클래스로 구현 했을 때
class CreateFoo(val _bar:Int) : Foo {
override var bar: Int = _bar
override fun method(str: String) {
println("$bar $str")
}
}
이와 같은 형태를 취하고 내부에 들어있는 변수 bar
와 메서드 method
는 인터페이스로부터 상속받아 구현된 것이므로 override
를 붙여주었습니다.
인터페이스로 다중 상속하기
클래스끼리는 다중 상속을 할 수 없지만 클래스가 인터페이스를 다중 상속 받는 것은 가능합니다.
interface Bird {
var wings:Int
fun fly()
fun jump() { // 메서드를 정의할 수 있다?
println("Bird jump")
}
}
interface Horse {
var maxSpeed:Int
fun run()
fun jump() { // 메서드를 정의할 수 있다?
println("Horse jump & maxSpeed ")
}
}
두개의 인터페이스가 준비되어 있습니다. 각각의 인터페이스에는 하나의 변수와 두개의 메서드가 존재합니다.
두개의 인터페이스를 상속받는 Pegasus
클래스를 만들어보겠습니다.
class Pegasus : Bird, Horse {
override var wings: Int = 2
override var maxSpeed: Int = 100
override fun fly() {
println("Fly Sky~~")
}
override fun run() {
println("Run~~")
}
override fun jump() {
super<Bird>.jump() // 그냥 jump를 호출하면 상속받는 인터페이스에 모두 jump가 있어서 에러가 발생하므로 제네릭 타입으로 상위 타입을 지정해주자!
println("Pegasus Jump~~!!")
}
}
모든 변수와 메서드는 인터페이스로부터 구현되었으므로 모두 override
를 붙여주었습니다.
중요한 사실은 jump
라는 메서드에 주목해야 하는데 이 메서드는 두개의 인터페이스에 각각 존재하므로 중복되어 충돌을 발생시킬 수 있습니다.
따라서 상위타입을 지정해야 하는데 super
를 붙여 제네릭으로 <Bird>
에 있는 메서드를 가져다 쓰는 것으로 지정해주었습니다.
이를 메인 함수에서 사용해보면
fun main(args: Array<String>) {
val pega = Pegasus()
pega.fly()
pega.run()
pega.jump()
}
이와 같이 사용할 수 있겠습니다.
오버라이드
오버라이드는 앞서 살펴본 것처럼 상속을 받는 클래스(Child
)에서 상속을 해주는 클래스(Parent
)의 메서드나 변수를 재정의 하기 위해 사용하는 것으로 정리했습니다.
오버라이드의 정의를 알아보기 위한 실습
오버라이드가 과연 어떻게 해서 되는 것인지 실습하기 위해 다음과 같은 클래스를 구상하겠습니다.
우선 Animal
이라는 클래스와 Pet
이라는 인터페이스의 상속을 받는 Cat
과 Dog
클래스를 만들어 줍니다.
그 다음으로 Cat
객체나 Dog
객체를 매개변수로 받아서 어떤 문장을 출력해주는 메서드를 가진 Master
클래스를 만들 것입니다.
상속해 줄 클래스와 인터페이스 작성하기
우선 상속해 줄 클래스를 작성해줍니다.
class Animal(val name:String) {}
이 클래스는 멤버변수 하나와 그에 따른 생성자 하나만 존재하기 때문에 위와 같이 지정해줍니다.
이 클래스는 상속해 줄 클래스 이기 때문에 앞에 open
을 붙여줍니다.
open class Animal(val name:String) {}
그 다음으로 인터페이스를 작성합니다.
인터페이스 내부에는 category
변수와 메시지를 꺼내 쓸 수 있는getter
를 만들어 줄 것이고 species
라는 문자열 변수와 메서드 feeding()
, patting()
을 만들어 줄 것입니다. 이 때 patting
은 "Keep patting"을 출력하게 해 줄 것입니다.
interface Pet {
var category:String
val msgTags:String
get() = "I love my pet!"
var species:String
fun feeding()
fun patting() {
println("Keep patting")
}
}
상속 받는 클래스 작성하기
Cat
, Dog
클래스는 Pet
인터페이스와 Animal
클래스를 상속받습니다.
이 때 Animal
클래스에서 name
을 받아와야 합니다. 그리고 Pet
인터페이스에서 변수 species
와 메서드 feeding
을 구현할 것이기 때문에 오버라이드 해줍니다. 또한 생성자로 인터페이스에 있는 category
또한 구현되어야 하기 때문에 생성자에서 오버라이드 해줍니다.
class Cat(name:String, override var category: String) : Pet, Animal(name) {
override var species: String = "Cat"
override fun feeding() {
println("Feeding Cat")
println("Cat Name: $name")
}
}
class Dog(name:String, override var category: String) : Pet, Animal(name) {
override var species: String = "Dog"
override fun feeding() {
println("Feeding Dog")
println("Dog Name: $name")
}
}
Master
클래스 작성하기
Master
클래스는 객체로 생성되었을 때 Cat
이나 Dog
클래스를 매개변수로 받아서 각 클래스 내부에 선언된 species
변수의 값을 출력해주고, 인터페이스의 feeding
이 각 클래스에서 구현된 형태를 보여줄 것입니다.
class Master {
fun playWithPet(pet:Pet) {
println(pet.species)
pet.feeding()
}
}
이 클래스는 내부에 메서드만 존재하고 이 메서드는 Pet
이라는 인터페이스 형태를 매개변수로 받아서 species
에 할당된 값과 feeding
메서드의 호출 결과를 보여줍니다.
그래서 main
함수에서 각각의 객체를 생성하고 이를 Master
객체 내부의 메서드에 Cat
과 Dog
객체를 Arguments로 하여 실행해보면
fun main(args: Array<String>) {
val master = Master()
val dog = Dog("바둑이", "Small")
master.playWithPet(dog)
val cat = Cat("야옹이", "Middle")
master.playWithPet(cat)
}
이와 같은 결과를 얻을 수 있습니다.
Author And Source
이 문제에 관하여(Kotlin Programming Intermediate - 7), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@htwenty-1/Kotlin-Programming-Intermediate-7저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)