[kotlin] class, object, interface -3


kotlin in action을 보고 정리한 글입니다.

The "object" keyword: declaring a class and creating an instance, combined


objectキーワードの使用状況は以下の通りです.
  • 単一オブジェクトの作成時
  • パートナ・オブジェクトがファクトリ・メソッドを有する場合、またはクラス・インスタンスが呼び出されず、クラス名でメンバーにアクセスする場合、
  • .
  • object式を使用する場合、
  • Object declarations: singletons made easy


    Cottlinではclass declaration+singleinstance declarationでobject宣言を行い、この方法でsingletonを作成します.
    object Payroll {
      val allEmployees = arrayListOf<Person>()
      
      fun calculateSalary() {
        for (person in allEmployees) {
         ...
        }
      }
    }
    給料は唯一のはずだ.objectキーワードでobject宣言を行うことができます.
    objectは、properties、メソッド、およびinitializer blockを他のクラスのように宣言することができる.クラスとの違いは、コンストラクション関数を持つことができないことです.(primaryもsecondaryも)コンストラクション関数を呼び出さずに定義して生成することもできます.
    コールは次のとおりです.
    Payroll.allEmployees.add(Person(...))
    
    Payroll.calculateStory()
    object宣言は、クラスと同じ他のクラス、インタフェースを継承することができる.
    フレームワークがインタフェース実装を必要とし、ステータスを持たない場合、容易に実現できます.
    java.util.Comparatorインタフェースの場合、Comparatorにはデータがないため、単一の色調で存在するだけです.このような状況で役に立つ.
    ユーティリティオブジェクトの作成
    object CaseInsensitiveFileComparator: Comparator<File> {
      override fun compare(file1: File, file2: File): Int {
        return file1.path.compareTo(file2.path, ignoreCase = true)
      }
    }
    
    val files = listOf(File("/Z"), File("/a"))
    println(files.sortedWith(CaseInsensitiveFileComparator))
    クラス内でobjectを宣言することもできます.
    data class Person(val name: String) {
       object NameComparator: Comparator<Person> {
         override fun compare(p1: Person, p2: Person): Int = 
           p1.name.compareTo(p2.name)
       }
    }
    
    val persons = listOf(Person("Bob"), Person("Alice"))
    println(persons.sortedWith(Person.NameComparator))
    // [Person(name=Alice), Person(name=Bob)]
    この時点でNameComparatorはPersonの各インスタンスに存在しません.
    println(persons.sortedWith(persons.NameComparator))
    // Unresolved reference: NameComparator

    Companion objects: a place for factory methos and static members


    cottlinクラスにはstatic memberは存在しません.staticというキーワードは存在しません.代わりにpackage-levelの関数とobject宣言が存在します.
    ほとんどの場合、top-level functionsを推奨します.ただし、top-level functionではクラスのprivate memberにアクセスできません.

    p.96 figure 4.5
    また、クラスインスタンスなしでクラス内部にアクセスしたいため、オブジェクト宣言も必要です.factoryメソッドが例です.companion keywordを使用してjavaの静的メソッドと同じ役割を果たすobjectを作成します.
    class A {
      companion object {
        fun bar() {
          println("Companion object called")
        }
      }
    }
    
    A.bar()
    // Companion object called
    privatgeコンストラクション関数を呼び出す良い場所も仲間オブジェクトです.クラス内のすべてのprivateメンバーを呼び出すことができるので、Factory patternを構築するのに理想的な選択です.
    パートナーオブジェクトの内部にファクトリメソッドを作成してみます.まず2つのコンストラクション関数を作成し、factoryメソッドに変更します.
    class User {
      val nickname: String
      
      constructor(email: String) {
        nickname = email.substringBefore("@")
      }
      
      constructor(facebookAccountId: Int) {
        nickname = getFacebookName(facebookAccountId)
      }
    }
    class User private constructor(val nickname: String) {
      companion object {
        fun newSubscribingUser(email: String) = 
          User(email.substringBefore("@"))
        
        fun newFacebookUser(accountId: Int) = 
          User(getFacebookName(accountId))
      }
    }
    プライマリコンストラクション関数はprivate visibilityとして宣言されます.
    factoryメソッドを使用してnewFacebookユーザーを作成しました.
    val subscribingUser = User.newSubscribingUser("[email protected]")
    val facebookuser = User.newFacebookUser(4)
    パートナ・オブジェクトのメンバーは、サブクラスから継承できません.

    Companion objects as regular objects


    パートナー・オブジェクトは、クラスで拡張関数/propertiesを宣言、名前付け、実装、および宣言できます.
    class Person(val name: String) {
      companion object Loader {
        fun fromJSON(jsonText: String): Person = ...
      }
    }
    
    person = Person.Loader.fromJSON("{name: 'Dmitry'}")
    person.name
    // Dmitry
    person2 = Person.fromJSON("{name: 'Brent'}")
    person2.name
    // Brent
    逆シーケンス化jsonの関数を作成しました.Person.Loader.JSONから呼び出し、Person.JSONから呼び出されても、結果は同じです.

    Implementing interfaces in companion objects


    他のobject宣言とタグ基地を使用して、仲間objectもインタフェースを実現することができます.jsonから逆シーケンス化する一般的な方法を提供する場合は、次のように記述できます.
    interface JSONFactory<T> {
      fun fromJSON(jsonText: String): T
    }
    
    class Person(val name: String) {
      companion object: JSONFactory<Person> {
        override fun fromJSON(jsonText: String): Person = ...
      }
    }
    そしてloadFromJSONで逆シーケンス化する.
    fun loadFromJSON<T>(factory: JSONFactory<T>): T {
      ...
    }
    
    loadFromJSON(Person)
    JSOnFactoryの例として、PersonはloadFromJSONのパラメータとして使用される.

    Companion object extensions


    Javaのstatic methodと同じようにクラス呼び出しを使用する方法を作成するにはどうすればいいですか?
    classにパートナーオブジェクトがある場合は、パートナーオブジェクトの拡張関数を作成できます.クラスCに仲間object Companionがあり、そのオブジェクトに拡張関数が作成されている場合、C.func()を呼び出すことができる.
    // business logic module
    class Person(val firstName: String, val lastName: String) {
      companion object { }
    }
    
    // client/server communication module
    fun Person.Companion.fromJSON(json: String): Person {
      ...
    }
    
    val p = Person.fromJSON(json)
    パートナー拡張関数を記述するには、クラス内でパートナーオブジェクトを宣言する必要があります.

    Object expressions: anonymous inner class rephrased


    objectキーワードは、モノトーンオブジェクトだけでなく、anonymous object sにも使用できます.これはjavaのシンボルレス内部クラスに取って代わります.
    Javaのevent listenerをコトリンに変更する方法を見てみましょう.
    window.addMouseListener(
      object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
          // ...
        }
    
        override fun mouseEntered(e: MouseEvent) {
          // ...
        }
      }
    )
    違いは、object宣言と同じですが、名前ではありません.
    object expressionはclassを宣言しclass instanceを作成しますが、名前は指定しません.関数の内部で使用されるため、そうする必要はありません.名前をobjectと命名する必要がある場合は、変数にobjectを割り当てることができます.
    val listener = object : MouseAdapter() {
      override fun mouseClicked(e: MouseEvent) { ... }
      override fun mouseEntered(e: MouseEvent) { ... }
    }
    Javaのannoymous inner classとは異なり、Javaのannoymous inclassが1つのクラスと1つのインタフェースしか拡張/実装できない場合、Cottlinのannoymous objectは複数のインタフェース/noインタフェースを実装することができます.
    object宣言とは異なり、anonymousobjectは単一色調ではありません.object式は毎回実行され、新しいobjectが生成されます.
    Javaと同様に、object式はobject式で作成された関数で宣言された変数にアクセスおよび変更できます.ただし、Javaとは異なり、final variableだけでなくfinalキーワードが一致しない変数にアクセスできます.
    fun countClicks(window: Window) {
      var clickCount = 0
      
      window.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
          clickCount++
        }
      })
    }