Study/다재다능 코틀린 프로그래밍
Day 8. 클래스 계층과 상속
주지민
2021. 11. 16. 23:48
반응형
본 포스트는 지인들과 스터디한 내용을 정리한 포스트입니다
http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788931463422
다재다능 코틀린 프로그래밍 - 교보문고
다양한 프로그래밍 패러다임으로 살펴보는 코틀린 기본서 | 코틀린은 멀티패러다임 프로그래밍 언어다. 코틀린은 스크립트로 사용할 수도 있고, 객체지향 코드나 함수형 코드, 비동기 프로그램
www.kyobobook.co.kr
1. 인터페이스와 추상 클래스 생성
- 인터페이스 만들기
- 코드
// remote.kts interface Remote { fun up() fun down() fun doubleUp() { up() up() } } class TV { var volume = 0 } class TVRemote(val tv: TV): Remote { override fun up() { tv.volume++ } override fun down() { tv.volume-- } } val tv = TV() val remote: Remote = TVRemote(tv) println("Volume: ${tv.volume}") remote.up() println("After increasing: ${tv.volume}") remote.doubleUp() println("After doubleUp: ${tv.volume}")
- 코틀린의 인터페이스에서 구현된 메서드(코틀린 타입 디폴트 메서드, doubleUp() )은 java 1.6 이상에서 사용 가능
- 인터페이스에서 디폴트 구현부가 없다면 실제 구현하는 클래스에서 전부 구현해줘야한다 ( 자바와 동일 )
- TVRemote 클래스가 Remote 인터페이스를 구현한다는 것을 알리기 위해 ':' 콜론을 사용하고 Remote를 명시해줬다
- 코틀린에서는 static 메서드를 인터페이스 안에 직접 만들 수 없다, 컴패니언 객체를 사용해야한다
// remote.kts interface Remote { fun up() fun down() fun doubleUp() { up() up() } companion object { fun combine(first: Remote, second: Remote): Remote = object: Remote { override fun up() { first.up() second.up() } override fun down() { first.down() second.down() } } } }
- 위처럼 컴패니언 객체는 바로 interface에 작성하면 된다
- 코드
- 추상 클래스 생성하기
- 코틀린 또한 Java처럼 abstract로 선언한 클래스를 추상 클래스라고 한다
- 메서드도 abstract로 선언한 것을 추상 메서드라고 한다
- 예제코드
// abc.kts abstract class Musician(val name: String, val activeFrom: Int) { abstract fun instrumentType(): String } class Cellist(name: String, activeFrom: Int) : Musician(name, activeFrom) { override fun instrumentType() = "String" } val ma = Cellist("Yo-Yo Ma", 1961)
- 자식클래스에서 추상 클래스를 구현할때는 소속된 추상메서드를 override 키워드로 구현해야한다 ( JAVA와 동일 )
- 자식 클래스의 생성자는 베이스 클래스에서는 필요하지 않은 파라미터를 추가적으로 받을 수 있으며, 자식 클래스에서 추가적으로 받은 파라미터를 val 혹은 var로 선언하면 프로퍼티가 된다
- 인터페이스? 추상클래스?
- 추상 클래스 vs 인터페이스
- 인터페이스에 정의된 속성엔 백킹 필드가 없다. 인터페이스는 구현 클래스로부터 속성을 얻는 것을 추상 메소드에 의존한다. 반면에 추상 클래스는 백킹 필드를 가진다
- 인터페이스는 한 번에 여러 개를 구현할 수 있지만, 클래스는 추상 클래스든지 일반 클래스든지 하나만 확장 가능하다
- 구현 클래스에서 여러 개의 인터페이스를 한 번에 구현할 수 있다
- 추상 클래스는 한 번에 하나의 클래스만 확장 가능
- 선택 가이드
- 여러 클래스 사이에서 상태를 다시 사용해야 한다면 추상 클래스
- 각각의 클래스들이 각기 다른 구현 하는 것을 의도한다면 인터페이스
- 추상 클래스 vs 인터페이스
2. 중첩 클래스와 내부 클래스
- 내부 클래스를 사용하면 효율성 저하를 피하면서 분리된 클래스를 사용하는 이점을 얻을 수 있다
- 코틀린 클래스는 다른 클래스에 중첩될 수 있다
- Java와 다르게 코틀린의 중첩 클래스는 외부 클래스의 private 멤버에 접근 할 수 없다, 하지만 inner 키워드를 사용하면 내부 클래스로 변하면서 제약이 사라진다
- 예제코드
// nestedremote.kts interface Remote { fun up() fun down() fun doubleUp() { up() up() } companion object { fun combine(first: Remote, second: Remote): Remote = object : Remote { override fun up() { first.up() second.up() } override fun down() { first.down() second.down() } } } } class TV { private var volumn = 0 val remote: Remote get() = TVRemote() override fun toString() = "Volumn: ${volumn}" inner class TVRemote : Remote { override fun up() { volumn++ } override fun down() { volumn-- } override fun toString() = "Remote: ${this@TV}" } } val tv = TV() val remote = tv.remote println("$tv") // Volumn: 0 remote.up() println("After increasing: $tv") // After increasing: Volumn: 1 remote.doubleUp() println("After doubleUp: $tv") // After doubleUp: Volumn: 3
- 스트링 템플릿안에 this@TV는 외부 클래스인 TV의 인스턴스를 나타낸다
- 부모 클래스(베이스 클래스)로 접근하려면 super를 사용하면 된다
- object: {class} 를 통해서 익명 내부 클래스로 생성할 수 있다
- 중첩 클래스나 내부 클래스에 특벼한 상태가 필요하다면 외부 클래스에서 하듯이 속성을 생성하면 된다
3. 상속
- 코틀린은 클래스가 어쩌다 베이스 클래스로서 동작하는 것을 원하지 않는다
- 개발자는 클래스가 베이스 클래스로서 사용되게 하려면 명시적인 권한을 제공해야한다
- 인터페이스와 다르게 코틀린 클래스는 디폴트가 final이다
- open이라고 명시되어있는 클래스만 상속이 가능하다
- 즉 자식클래스는 open이라고 명시되어있는 부모클래스만 상속이 가능하며 그 중 열려있는 메소드만 오버라이드 가능하다
- 상속하는 클래스에서 재정의를 방지하려면 final override를 명시해주면 된다
- 예제코드
// inheritance.kts import java.lang.RuntimeException open class Vehicle(val year: Int, open var color: String) { open val km = 0 final override fun toString() = "year: $year, Color: $color, KM: $km" fun repaint(newColor: String) { color = newColor } } open class Car(year: Int, color: String) : Vehicle(year, color) { override var km: Int = 0 set(value) { if (value < 1) { throw RuntimeException("can't set negative value") } field = value } fun drive(distance: Int) { km += distance } } val car = Car(2019, "Orange") println(car.year) println(car.color) car.drive(10) println(car) try { car.drive(30) } catch (ex: RuntimeException) { println(ex.message) }
- Java와 다르게 코틀린은 implements와 extends를 구분하지 않는다 ( 그냥 상속이라고 표현 )
- setter를 재정의하기 위해 open된 km 변수를 다시 override했다
- 코틀린은 접근 권한에 관한 제약사항을 좀 더 관대하고 느슨하게 만들 수 있다
- private이나 protected 멤버를 자식 클래스에서는 public으로 만들 수 있다
- 단 베이스 클래스의 public 멤버를 자식 클래스에서 protected로 만들 수는 없다
4. 씰드 클래스
- final 클래스란 open으로 표기되어 있지 않아서 자식클래스가 하나도 없는 클래스를 의미
- open과 abstract 클래스는 그 반대로 여러 자식 클래스를 가질 수 있으며, 어떤 클래스가 상속 받았는지 전혀 알 수 없다
- 이를 개선하고자 코틀린에서는 sealed 클래스라는 것을 지원한다
- 작성자가 지정한 몇몇 클래스에서만 상속할 수 있도록 하는 중간 영역
- 동일한 파일에 작성된 다른 클래스들에 확장이 허용되지만 그 외의 클래스들은 확장할 수 없는 클래스
- 예제코드
// Card.kt package chapter8 import java.lang.RuntimeException sealed class Card(val suit: String) { class Ace(suit: String) : Card(suit) class King(suit: String) : Card(suit) { override fun toString() = "King of $suit" } class Queen(suit: String) : Card(suit) { override fun toString() = "Queen of $suit" } class Jack(suit: String) : Card(suit) { override fun toString() = "Jack of $suit" } class Pip(suit: String, val number: Int) : Card(suit) { init { if (number < 2 || number > 10) { throw RuntimeException("Pip has to be betwwen 2 and 10") } } } }
- sealed 클래스의 생성자는 private으로 취급된다
- 자식 클래스는 sealed 클래스의 싱글톤 객체가 될 수 있다
- 해당 예제에서는 Card의 5개의 자식클래스가 정의되어있고 범위를 벗어나는 다른 클래스에서 Card를 상속하려한다면 컴파일 오류가 난다
- 사용예제
// UseCard.kt package chapter8 import chapter8.Card.Ace import chapter8.Card.King import chapter8.Card.Queen import chapter8.Card.Jack import chapter8.Card.Pip fun process(card: Card) = when (card) { is Ace -> "${card.javaClass.name} of ${card.suit}" is King, is Queen, is Jack -> "$card" is Pip -> "${card.number} of ${card.suit}" } fun main() { println(process(Ace("Diamond"))) // chapter8.Card$Ace of Diamond println(process(Queen("Clubs"))) // Queen of Clubs println(process(Pip("Spades", 2))) // 2 of Spades println(process(Pip("Hearts", 6))) // 6 of Hearts }
- sealed 클래스와 when 조합을 사용할땐 else를 사용하면 안된다 ( 절대 사용해서는 안된다는 경고가 뜬다 )
5. Enum의 생성과 사용
- 예제코드
// CardWithEnum.kt package chapter8 enum class Suit { CLUBS, DIAMONDS, HEARTS, SPADES } ...
- valueOf() 메서드를 통해 String이 주어지면 같은 naming을 갖는 enum 인스턴스를 얻을 수 있다 ( 매치되는 값이 없다면 실행 시간 예외가 발생 )
- values() 메서드를 통해 enum이 가진 값들을 반복할 수 있다 ( name과 순서가 반환 )
- 상태를 갖는 예제코드
// initlizaeenum.kts enum class Suit(val symbol: Char) { CLUBS('\u2663'), DIAMONDS('\u2666'), HEARTS('\u2665') { override fun display() = "${super.display()} $symbol" }, SPADES('\u2660'); open fun display() = "$symbol $name" }
- 코틀린 컴파일러는 가능한 경우 enum 클래스의 인스턴스를 최소한으로 생성해 주고, 익명 내부 클래스가 필요한 경우엔 이 역시 생성 가능하다
728x90
반응형