Kotlinクラスのプロパティを深く理解する


Kotlinクラスの属性はKotlinクラスの最も理解しにくい部分と言える.変化が多く、キーワードが多く、制限が多く、Javaとの差がかなり大きく、多くの人がこの上に多くの穴を植えている.今日はKotlinクラスの属性を深く見てみよう.
Kotlinで除去されたクラス属性キーワード
Kotlinではクラス属性用のキーワードがいくつか除去されており,注釈で実現する必要がある.
  • transient:このキーワードは、この属性が「瞬時」であり、クラスのシーケンス化に関与しないことを示す.Kotlinで@Transientで表記する.
  • volatile:このキーワードはマルチスレッドに使用され、「軽量化されたsynchronized」と見なすことができます.Kotlinで@Volatileで表記します.

  • @JvmFieldの役割
    @JvmFieldはkotlinです.jvm.JvmPlatformAnnotations.ktで提供される注記.
    @JvmFieldの役割を理解するには、まずKotlinクラスのプロパティがコンパイルされた後に何になるかを理解します.私たちは古い友达のPerson類を例に挙げます.
    class Person(val name: String)
    

    属性nameを定義したが、KotlinはPersonクラスがこの属性を外部に(Javaに)暴露することを許可していない.コンパイルされたname属性は実際には次のようになっている.
    @NotNull private final String name;
    @NotNull public final String getName() {
      return name;
    }
    

    外部ではnameプロパティに直接アクセスすることはできません.getName()メソッドで間接的にアクセスする必要があります.これは、最初に述べた「標準化getter/setter」です.
    しかし、実際の使用では、Kotlinクラスのプロパティを直接露出する必要がある場合があります(パッケージ化には不利ですが)、@JvmField注記を使用できます.
    class Person(@JvmField val name: String)
    

    コンパイル後、nameプロパティが直接露出します.
    @JvmField @NotNull
    public final String name;
    

    privateクラス属性の詳細
    privateで修飾されたクラス属性は、このクラスの内部にのみアクセスでき、外部にはアクセスできず、対外インタフェースを提供する必要もありません.詳細は次のとおりです.
  • privateクラス属性はデフォルトでgetter/setterを生成せず、そのアクセスは直接アクセスである.
  • privateプロパティにカスタムgetter/setterがあると、アクセス時にgetter/setterを通過します.
  • @JvmField注記privateクラスのプロパティには使用できません.必要ありません.
  • privateクラスプロパティは、プライマリコンストラクション関数で定義できます.

  • バックグラウンドフィールドとfield
    バックグラウンドフィールドとは?
    この概念を理解するには、getterとsetterは必ずある属性に関連付けなければならないのか、さらに深く探究しなければなりません.答えはもちろん否定的だ.getterはオブジェクトから特定の値を取得します.この値はアクセスするたびに一時的に計算される可能性があります.他のオブジェクトから得られる可能性があります.setterは、他のオブジェクトのプロパティを設定している場合もあります.実際、Kotlinは特定の条件を満たすクラス属性にJVMの意味でのクラス属性を追加するだけです.
    PersonにnameHashプロパティを追加します.
    class Person(val name: String) {
      val nameHash get() = name.hashCode()
    }
    

    このnameHash属性は初期化されず,getterメソッドのみが定義されている.この属性はコンパイル後に消え、getNameHash()メソッドが1つだけ残っています.
    public final int getNameHash() {
      return name.hashCode();
    }
    

    「nameHashはバックグラウンドフィールドのない属性である」と言えるが,クラス属性としてKotlinコードにのみ存在し,真のJVMクラス属性はそれに対応していない.nameHashにバックグラウンドフィールドを追加するにはどうすればいいですか?Kotlinでは手動で追加することはできませんが、条件を満たすクラス属性に自動的にバックグラウンドフィールドが追加されます.
  • デフォルトgetter/setterのプロパティを使用すると、必ずバックグラウンドフィールドがあります.varプロパティの場合、getter/setterにデフォルトのインプリメンテーションがある限り、バックグラウンドフィールドが生成されます.
  • カスタムgetter/setterではfieldのプロパティが使用されており、必ずバックグラウンドフィールドがあります.このfieldは私たちが裏のフィールドにアクセスする「キーワード」で、Lambda式のitと似ていて、本当のキーワードではありません.特定の文内に特殊な意味があるだけで、他の文内ではキーワードではありません.

  • たとえば、次のように書くことができます.
    class Person(val name: String, val gender: Gender, age: Int) {
        val age = age
            get() = when (gender) {
                Gender.FEMALE -> (field * 0.8).toInt()
                Gender.MALE -> field
            }
    }
    
    enum class Gender {
        MALE, FEMALE
    }
    

    ここでは,age属性の性別による挙動をバックグラウンドフィールドで実現した.(´・ω・`)
    ageプロパティのgetterをカスタマイズしましたが、getterでfieldキーワードが使用されているため、Kotlinはprivate final intプロパティをバックグラウンドフィールドとして生成します.このバックグラウンドフィールドは同時に初期化する必要があります.メインコンストラクション関数に入力されたageで初期化します.
    背後の属性が大いに役に立つ
    多くの場合、このようなプロパティを定義したいと考えています.
  • は対外的にval属性として表現され、読むしか書けない.
  • はクラス内部でvar属性として表現され、すなわちクラス内部でのみその値を変更することができる.

  • これはKotlin集合フレームワークに広く応用されている.たとえばCollectionインタフェースで定義されたsize属性はval属性であり、外部では読み取り専用である.しかし、MutableCollectionの場合、sizeの値は内部で変更できるに違いありません.内部で変更することもできます.次のように設計できます.
    override val size get() = _size
    private var _size: Int = 0
    

    私たちが内部で要素を削除したとき、変更したのは_sizeプロパティの値;外部はsizeプロパティにのみアクセスでき、変更できません.sizeの値.ここの_sizeは「裏属性」と呼ばれています.
    kotlin.collections.AbstractMap.ktでも裏属性が使われています.見てみましょう.
    private @Volatile var _keys: Set? = null
    override val keys: Set get() {
      if (_keys == null) {
        _keys = object : AbstractSet() {
          override operator fun contains(element: K): Boolean = containsKey(element)
    
          override operator fun iterator(): Iterator {
            val entryIterator = entries.iterator()
            return object : Iterator {
              override fun hasNext(): Boolean = entryIterator.hasNext()
              override fun next(): K = entryIterator.next().key
            }
          }
    
          override val size: Int get() = [email protected]
        }
      }
      return _keys!!
    }
    

    keysは対外的なAPIであり、読み取り専用で空ではなく、アクセスのたびにアクセスを依頼される.keysプロパティ、keysプロパティは内部で変更したりnullにしたりすることができ、便利さをもたらします.
    クラス属性を設計する際には,バックグラウンド属性により柔軟に機能を実現できる.