educative - kotlin - 8

48725 ワード

Class Hierarchies and Inheritance


Creating interfaces


いくつかの抽象的な方法からなるインタフェースを作成します!
cottlinインタフェースはinterfaceキーワード定義を使用します.
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-- }
}
TVRemoteクラスは、リモートインタフェースで定義された抽象メソッドを強制的に上書きする必要があります.しかし、ここでは、上記のdoubleUp()メソッドは例外である.コトリンのインタフェースは抽象メソッドだけでなくdefaultメソッドも実現できるからである.ここで、up(), down()メソッドは抽象メソッドと見なされ、上書きの義務があり、```doubleUp()メソッドはdefaultメソッドと見なされ、上書きの義務はない.
上記のリモートインタフェースは、Java 8からのdefaultキーワードを使用して実現される.
public interface Remote {
	void up(); // abstract
    void down(); // abstract
    
    default void 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) //Remote 인터페이스를 구현한 TVRemote 인스턴스 생성

println("Volume: ${tv.volume}") //Volume: 0
remote.up()
println("After increasing: ${tv.volume}") //After increasing: 1
remote.doubleUp()
println("After doubleUp: ${tv.volume}") //After doubleUp: 3
Javaでは、インタフェースは静的メソッドであってもよいが、cottinではない.Cottlinでインタフェースの静的メソッドを実装する場合は、パートナーオブジェクトを使用します.リモートインタフェースを継承し、combine()メソッドによって2つのRemoteクラスの演算を実行します.
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()
        }
      }
  }

// 사용
val tv = TV()
val remote: Remote = TVRemote(tv)

val anotherTV = TV()
val anotherRemote: Remote = TVRemote(anotherTV)

val combinedRemote = Remote.combine(remote, anotherRemote)

combinedRemote.up()
println(tv.volume) //1
println(anotherTV.volume) //1

Creating abstract classes

abstractキーワードをクラスの前に貼り付け、抽象クラスを実現できます.
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)

println(ma.name) // Yo-Yo Ma
println(ma.instrumentType()) // String
インタフェースと抽象クラスの違いは?
  • インタフェースで定義されたプロフェッショナルにはフィールドがありません.そのため、抽象的な方法でPropertyにアクセスする必要があります.しかし抽象クラスは
  • である.
  • インタフェースは多重継承できますが、クラスを抽象化することはできません.
  • では、どんな場合に使うのが適当なのでしょうか?->複数の継承を切り替える
    複数のクラスで
  • を使用する必要がある場合は、抽象クラス->を作成して複数のクラスに配布するだけでよい
  • 複数のクラスを継承して実装する必要がある場合->当然、複数の中間高速インタフェース
  • である.

    Nested and Inner Classes


    内部classを使用して、より効率的な構造を実現しましょう.
    class TV {
      private var volume = 0
      
      val remote: Remote
        get() = TVRemote()
      
      override fun toString(): String = "Volume: ${volume}"
      // TVRemote를 TV의 inner class로 옮김
      inner class TVRemote : Remote {
        override fun up() { volume++ } //바로 outer class property 접근 가능
        override fun down() { volume-- }
                               
        override fun toString() = "Remote: ${this@TV.toString()}"
      }                    
    }
    
    val tv = TV()
    val remote = tv.remote
    
    println("$tv") //Volume: 0
    remote.up()
    println("After increasing: $tv") //After increasing: Volume: 1
    remote.doubleUp()
    println("After doubleUp: $tv") //After doubleUp: Volume: 3
    TVRemote Inner Classを使用すると、up()down()メソッドからTVのプライベート番組に自由にアクセスできます.this@式を使用してouterclassのメンバーにアクセスできます.

    Anonymous inner class


    anonymous内部クラスでも同様に実現できる.innerキーワードはなく、クラス名がない以外はネストされたクラスを使用するのは同じです.
    class TV {
        private var volume = 0
        val remote: Remote get() = object: Remote { 
            override fun up() { volume++ } 
            override fun down() { volume-- }
            override fun toString() = "Remote: ${this@TV.toString()}" 
        }
        override fun toString(): String = "Volume: ${volume}" 
    }

    Inheritance


    コトリンの等級と方法は基本的にfinalで、継承できません.したがって、継承できるようにするには、openキーワードを使用する必要があります.さらに、親クラスのval propertyは、子クラスからvalまたはvarに移行できますが、親クラスのvar propertyは、子クラスからvalに移行できません.通常、valにはgetterがあり、varにはgetterとsetterがあります.したがって、親クラスのvarが子クラスからvalに移行した場合、定義したsetterは親クラスから削除できません.

    Creating a base class

    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
      }
    }
  • ベースクラスの最初のpropertyは上書きできないvalであり、2番目のpropertyは上書き可能なvar propertyである.
  • 上位クラスanyで定義されたtoString()メソッドを超えています.
  • の継承を禁止するrepaint()の方法があります.
  • Creating a derived class

    
    ```kotlin
    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
      }  
    }
    サブクラスCarジェネレータのパラメータは、親クラスVerhicleに移動します.ここで、子は親のkm%を上書きします.val -> varはカスタム設定器を実現した.
    val car = Car(2019, "Orange")
    println(car.year)  // 2019
    println(car.color) // Orange
    
    car.drive(10)
    println(car) // year: 2019, Color: Orange, KM: 10 (부모 클래스 toString())
    
    try {
      car.drive(-30)
    } catch(ex: RuntimeException) {
      println(ex.message) // can't set negative value (커스텀 setter)
    }

    Extending the class


    今回はCarを継承するFamilyCarクラスを作成します
    class FamilyCar(year: Int, color: String) : Car(year, color) {
      override var color: String
        get() = super.color
        set(value) {
          if (value.isEmpty()) {
            throw RuntimeException("Color required")
          }
          
          super.color = value
        }
    }
    FamilyCarクラスは親クラスVerhicleクラスのcolor propertyをgetterとsetterとして使用します.
    val familyCar = FamilyCar(2019, "Green")
    
    println(familyCar.color) //Green
    
    try {
      familyCar.repaint("") //Custom Setter 발동
    } catch(ex: RuntimeException) {
      println(ex.message) // Color required
    }
    上書き時のアクセス制限者の注意事項
  • public -> private or protected (0)
  • protected -> public (x)
  • Sealed class


    Sealed classは、同じファイルで親の子クラスタイプを継承することを制限する特性を持つクラスです.
    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 between 2 and 10")
        }
      }
    }
    
    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}"
    }
    
    // when() 구문에서 else는 넣지 않는다 -> 새로운 sealed 클래스가 추가되었을때 오류가 생길 수 있다
    
    fun main() {
        println(process(Ace("Diamond")))    // 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 class開いている変更者を自動的に追加->自動継承可能
  • プライベートジェネレータがあるため、オブジェクト
  • を直接作成することはできません.
  • などのファイルでサブクラスを継承して実装できます->他のファイルからsealed classを継承しよう->コンパイルエラー
  • enum classes


    enum classを使用すると、コードを簡略化し、可読性を向上させることができます.
    enum class Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
    
    sealed class Card(val suit: Suit)
    
    class Ace(suit: Suit) : Card(suit)
    
    class King(suit: Suit) : Card(suit) {
      override fun toString() = "King of $suit"
    }
    
    // UseCardWithEnum.kt
    println(process(Ace(Suit.DIAMONDS)))    // Ace of DIAMONDS
    println(process(Queen(Suit.CLUBS)))    // Queen of CLUBS
    println(process(Pip(Suit.SPADES, 2)))   // 2 of SPADES
    println(process(Pip(Suit.HEARTS, 6)))  // 6 of HEARTS
    Suit.DIAMONDSは、Suitクラスの例のstaticである.

    Customizing enums


    Enumクラスはカスタマイズと反復が可能です.
    // iteration
    for (suit in Suit.values()) {
      println("${suit.name} -- ${suit.ordinal}") 
    }
    
    """
    CLUBS -- 0
    DIAMONDS -- 1
    HEARTS -- 2
    SPADES -- 3
    """
    // Customizing
    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"
    }
    
    for (suit in Suit.values()) {
      println(suit.display())
    }
    
    ♣ CLUBS
    ♦ DIAMONDS 
    ♥ HEARTS ♥ 
    ♠ SPADES