[Kotlin]Kotlin構文:スタティックユーティリティクラスを削除する:最上位関数とProperty-2


🤔 3.拡張関数と拡張プログラムを使用してJavaライブラリを適用する
Javaを主な言語として使用したり、Javaに詳しい人は、すべてのコードをクラスやメソッドとして記述しなければならないことを知っています.また,通常,この構造は아주 잘 작동한다.であるが,コードを記述する際にはこのような考えがある.これは、いくつかのメソッドをクラスに含めるのが難しいコードが多いためです.いくつかの演算では、2つ以上のクラスが似たような重要な役割を果たす可能性があります.または、重要なオブジェクトが1つしかない場合は、その演算をオブジェクトのインスタンスapiに追加しないで、apiを大きくしすぎます.筆者はすべての例を経験したわけではないが、考えてみれば、すべてのクラスに方法を置くことも可能ではないだろうか.このような茫然とした考えが生まれた.
要するに、このような状況を減らすために、Javaは様々な静的メソッドを収集するだけで、特殊な状態やインスタンスメソッドのないクラスがjdkのCollectionsクラスである.これらの例はjavaです.utilレベルを見ればいい
要するに、コトリンでは、上記のいずれかのメソッドを使用するために、メモリを浪費するために未使用のメソッドやクラスを呼び出す必要はありません.逆に、関数はソースファイルの最上位レベル、すべてのクラスの外部に直接配置できます.Kotlin식 문법: 정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티 - 1で作成されたjoinToString()を最上位関数として宣言するには、次のコードを使用してStringsパッケージに直接挿入します.もちろん、ファイル名はjoinToString.ktです.
package strings

fun <T> joinToString(
   collection: Collection<T>,
   separator: String =", ",
   prefix: String = "",
   postfix: String = ""
) : String {
   val result = StringBuilder(prefix)

   for ((idx, ele) in collection.withIndex()) {
       if (idx > 0) result.append(separator)
       result.append(ele)
   }

   result.append(postfix)
   return result.toString()
}
では、この関数はどのように動作しますか?重要なのは、JVMはクラスに含まれるコードのみを実行し、コンパイラはファイルをコンパイルするときに新しいクラスを定義することです.単純に誤針を使うと、そのようなレベルになることを覚えておくだけです.Javaなど他のJVM言語でこの関数を呼び出す場合は、JoinToStringと同じトップレベルの関数を使用するには、コードがどのようにコンパイルされているかを知る必要があります.上のコードをjavaコードに変換するとどうなるか見てみましょう.
package strings;

public class JoinKT {
	public static String joinToString(...) {...}
}
これにより、cottlinコンパイラが生成したclassの名前は、トップレベルの関数を含むcottlinソースファイルの名前に対応します.cottinファイル内のすべてのトップレベル関数がクラスの静的メッセージになります.したがって、JavaでjoinToString()を呼び出すのは簡単です.
import strins.JoinKt
...
JoinKt.joinToString(list, ", ", "", "");
ファイルに対応するクラスの名前を変更する方法もあります.これは.@JvmName注釈を使用すればよい.この部分は後で詳しく記録します.
🔑 最大構成
Propertyの場合は、関数のようにファイルの最上位に配置することもできます.クラス外にデータを配置することは一般的ではありません.(もちろん、アルゴリズムの問題を解くときにグローバル変数を使うのは便利です.筆者はmain関数で他の関数を定義するのはあまり好きではありません.現在も深く考えていますが...)
🤔 4.メソッドを他のクラスに追加する:拡張関数と拡張プログラム
既存のコードとコードを自然に統合することは、コトリンの目標である相互運用性の核心である.完全にcottinで構成されたプロジェクトであっても、JDKまたはAndroidフレームワークまたは他のサードパーティフレームワークのjavaライブラリに基づいて作成されます.したがって、cortlinを既存のjavaプロジェクトに含める場合は、cortlinに変換されていないか、変換できない既存のjavaコードが一緒に処理できることを確認する必要があります.既存のjava apiを書き直す必要がなく、最終的にコトリンが提供するさまざまな便利な機能を使用できるなら、それはいいことではないでしょうか.
この機能は확장 함수によって実現される.확장 함수クラスのメンバー関数を呼び出すようにクラスを呼び出すことができますが、宣言された関数を除きます.
たとえば、次のコードを見てみましょう.
fun String.lastChar(): Char = this[this.length-1]
fun main() = println("Koltin".lastChar())
ここはString은 수신 객체 타입this는 수신객체です.
この方法はStringクラスに新しいメソッドを追加することに似ています.もちろん、Stringクラスを自分で作成したり、String classのソースコードの所有者ではありませんが、必要な方法をString classに追加することはできます.Stringがジャワ語やコトリン語のどの言語で書かれているかさえ重要ではありません.Javaクラスにコンパイルされたクラスファイルがあれば、必要に応じてそのクラスに拡張を追加できます.(この部分kotlinコンパイラとjavaコンパイラのコンパイラのコンパイル方法は少し異なりますが、後でこの部分も紹介します.)
💣 拡張JoinToString関数
fun main() {
    val list = listOf(1, 2, 3)
    println(list.joinToString(separator = "; ", prefix = "(", postfix = ")"))
}

fun <T> Collection<T>.joinToString(
    separator:String = ", ",
    prefix:String = "",
    postfix: String = ""
) : String {
    val result = StringBuilder(prefix)
    for ((idx, ele) in this.withIndex()) {
        if (idx > 0) result.append(separator)
        result.append(ele)
    }
    result.append(postfix)
    return result.toString()
}
もちろん,拡張関数は静的メソッド呼び出しの構文的利便性にすぎない.したがって、クラスではなく、より具体的なタイプを受信オブジェクトタイプとして指定できます.したがって、文字列セットに対してのみ呼び出されるjoin関数を定義するには、次のようにします.
fun main() {
    val list = listOf("1", "2", "3")
    println(list.join(" "))
}

fun <T> Collection<T>.joinToString(
    separator:String = ", ",
    prefix:String = "",
    postfix: String = ""
) : String {
    val result = StringBuilder(prefix)
    for ((idx, ele) in this.withIndex()) {
        if (idx > 0) result.append(separator)
        result.append(ele)
    }
    result.append(postfix)
    return result.toString()
}

fun Collection<String>.join(
    separator: String = ", ",
    prefix: String = "",
    postfix: String = ""
) = joinToString(separator, prefix, postfix)
💭 拡張関数は上書きできません.
コトリンのメソッドオーバーラップも一般的なオブジェクト向けのメソッドオーバーラップと同じである.ただし、拡張関数は上書きできません.ViewとそのサブクラスButtonがあり、Buttonが親クラスを上書きするClick関数を考慮すると、以下のようになります.
open class View {
    open fun click() = println("View Clicked")
}

class Button: View() {
    override fun click() = println("Button Clicked")
}

fun main() {
    val view: View = Button()
    view.click() // Button Clicked
}
ButtonはViewのサブタイプであるため、Viewタイプ変数を宣言してもButtonタイプ変数を代入することができます.Viewタイプ変数では、clickと同じ一般的なメソッドが呼び出され、clickがButtonクラスのoverlightである場合、実際にはButtonのoverlightが呼び出されます.
しかし、拡張はこのような方法で仕事をすることはできません.重要なのは、확장 함수는 클래스의 일부가 아니다.がどういう意味なのか、拡張関数はクラスの外で宣言されています.すなわち、名前とパラメータがまったく同じ拡張関数に基づくクラスとサブクラスに拡張関数を定義しても、実際には、拡張関数を呼び出すときに受信オブジェクトとして指定された変数の静的タイプがどの拡張関数を呼び出すかを決定し、その変数に格納されているオブジェクトの動的タイプが拡張関数を決定しない.以下の例で説明します.
open class View {
    open fun click() = println("View Clicked")
}

class Button: View() {
    override fun click() = println("Button Clicked")
}

fun View.showOff() = println("I'm a View")
fun Button.showOff() = println("I'm a Button")

fun main() {
    val view: View = Button()
    view.showOff() // I'm a View
}
上記のコードでは、Viewが指すオブジェクトの実際のタイプはButtonであるが、この場合、ViewのタイプはViewであるため、無条件にViewの拡張関数を呼び出す.前述の手順と同じです.
では、なぜこのような過程が行われるのでしょうか.
拡張関数を最初のパラメータが受信オブジェクトである静的javaメソッドにコンパイルすることを覚えている場合は、操作を簡単に理解できます.Javaも同様に、呼び出す静的関数を静的に決定する.
すなわち、코틀린은 호출될 확장 함수를 정적으로 결정한다.は、拡張関数を上書きすることができない.주의クラスを展開する関数がメンバー関数の名前とシーケンスと同じである場合、展開関数ではなくメンバー関数が呼び出されます.(つまり、メンバー関数の優先度が高い)クラスのapiを変更する場合は、この点を考慮する必要があります.したがって、外部クライアントプロジェクトがある場合は、コード所有権を持つクラスの拡張関数を定義します.拡張関数と(名前とフラグ)が同じメンバー関数をクラスに追加すると、クライアントプロジェクトが再コンパイルされた時点から、拡張関数ではなく、新しく追加されたメンバー関数が使用されます.
⅊拡張属性
拡張propertyでは、apiを既存のクラスオブジェクトのpropertyフォーマットとして追加する構文を許可します.Propertyと呼ばれますが、ステータスを保存できません(既存のクラスのインスタンスオブジェクトに追加できません)、拡張Propertyには実際にはステータスがありません.しかし、property構文でより短いコードを書くことができるので、便利な場合もあります.
先ほど作成したlastCharという関数を使用して、次の例を作成します.
val String.lastChar: Char
	get() = get(length-1)
    
fun main() {
    println("Kotlin".lastChar) // n
}
上記のコードでは、拡張propertyは従来のpropertyと同じであり、受信オブジェクトクラスが追加されているだけです.基本オブジェクトインプリメンテーションは提供されないため、getterでは定義する必要があります.まだあります.初期化コードで計算された値を格納できる場所はないので、初期化コードを記述することもできません.
StringBuilderで同じpropertyが定義されている場合、StringBuilderの맨 마지막 문자는 변경 가능はpropertyをvarとして作成できます.
val String.lastChar: Char
    get() = get(length-1)

var StringBuilder.lastChar: Char
    get() = get(length-1)
    set(value: Char) {
        this.setCharAt(length-1, value)
    }

fun main() {
    println("Kotlin".lastChar) // n
    val sb = StringBuilder("kotlin?")
    sb.lastChar = '!'
    println(sb) // kotlin!
}
また、Javaで拡張propertyを使用するには、StringUtilKt.getLastChar("Java")のようにgetterまたはsetterを明示的に呼び出す必要があります.