Kotlinの拡張メソッド

Kotlinの拡張メソッド

1. 前書き

Kotlinは、拡張機能を定義した後、拡張メソッド(a handy way of extending existing classes with a new functionality without using inheritance or any forms of the Decorator pattern)の概念を導入します。 元のAPIの一部であったため、基本的に使用できます。

これは、コードにアクセスできない場合でも、ニーズに固有のメソッドを追加して元のコードの一部のように見せることができるため、コードを読みやすく、保守しやすくするのに非常に役立ちます。情報源。

たとえば、String.でXMLエスケープを実行する必要がある場合があります。標準のJavaコードでは、これを実行して呼び出すことができるメソッドを作成する必要があります。

String escaped = escapeStringForXml(input);

Kotlinで記述されているのに対し、スニペットは次のように置き換えることができます。

val escaped = input.escapeForXml()

これは読みやすいだけでなく、IDEは、Stringクラスの標準メソッドであるかのように、オートコンプリートオプションとしてメソッドを提供できるようになります。

2. 標準ライブラリ拡張メソッド

Kotlin標準ライブラリには、すぐに使用できるいくつかの拡張メソッドが付属しています。

2.1. 拡張メソッドを調整するコンテキスト

Some generic extensions exist and can be applied to all types in our application。 これらは、コードが適切なコンテキストで実行されることを保証するために、また場合によっては変数がnullでないことを保証するために使用できます。

ほとんどの場合、これに気付かずに拡張機能を活用していることがわかりました。

最も人気のあるものの1つは、おそらくlet()メソッドです。これは、Kotlinの任意のタイプで呼び出すことができます。初期値で実行される関数をメソッドに渡します。

val name = "example"
val uppercase = name
  .let { n -> n.toUpperCase() }

これは、OptionalまたはStreamクラスのmap()メソッドに似ています。この場合、指定されたStringを大文字の表現に変換するアクションを表す関数を渡します。 。

The variable name is known as the receiver of the callは、拡張メソッドが作用している変数だからです。

これは、セーフコールオペレーターでうまく機能します。

val name = maybeGetName()
val uppercase = name?.let { n -> n.toUpperCase() }

In this case, the block passed to let() is only evaluated if the variable name was non-null。 これは、ブロック内で、値nがnull以外であることが保証されていることを意味します。 このhereの詳細。

必要に応じて、let()に代わる他の方法もあります。

run()拡張子はlet()と同じように機能しますが、レシーバーは呼び出されたブロック内のthis値として提供されます。

val name = "example"
val uppercase = name.run { toUpperCase() }

apply() works the same as run(), but it returns a receiver instead of returning the value from the provided block

apply()を利用して、関連する呼び出しをチェーンしましょう。

val languages = mutableListOf()
languages.apply {
    add("Java")
    add("Kotlin")
    add("Groovy")
    add("Python")
}.apply {
    remove("Python")
}

thisまたはitを明示的に使用する必要がなく、コードがより簡潔で表現力豊かになっていることに注目してください。

The also()拡張機能はlet()と同じように機能しますが、apply()と同じ方法でレシーバーを返します。

val languages = mutableListOf()
languages.also { list ->
    list.add("Java")
    list.add("Kotlin")
    list.add("Groovy")
}

それ以外の場合はThe takeIf() extension is provided with a predicate acting on the receiver, and if this predicate returns true then it returns the receiverまたはnull –これは一般的なマップ()メソッドとfilter()メソッドの組み合わせと同様に機能します。

val language = getLanguageUsed()
val coolLanguage = language.takeIf { l -> l == "Kotlin" }

The takeUnless()の拡張子はtakeIf()と同じですが、述語論理が逆になっています。

val language = getLanguageUsed()
val oldLanguage = language.takeUnless { l -> l == "Kotlin" }

2.2. コレクションの拡張メソッド

Kotlin adds a large number of extension methods to the standard Java Collections which can make our code easier to work with

これらのメソッドは、Collections.kt,_ Ranges.kt_およびSequences.kt_, as well as Arrays.kt_内にあり、代わりにArraysに適用される同等のメソッドです。 (Kotlinでは、ArraysCollectionsと同じように扱うことができることに注意してください)

これらの拡張方法の数が多すぎてここで説明できないため、これらのファイルを参照して、利用可能なものを確認してください。

In addition to Collections, Kotlin adds a significant number of extension methods to the String classStrings.kt_. These allow us to treat Strings as if they were collections of characters .__で定義

これらの拡張メソッドはすべて連携して機能するため、使用しているコレクションの種類に関係なく、コードを大幅にクリーンで保守しやすくすることができます。

3. 拡張メソッドの記述

では、JavaまたはKotlin標準ライブラリから、あるいは使用している依存ライブラリから、新しい機能でクラスを拡張する必要がある場合はどうなるでしょうか。

Extension methods are written as any other methodですが、レシーバークラスは関数名の一部としてピリオドで区切られて提供されます。

例えば:

fun String.escapeForXml() : String {
    ....
}

これにより、Stringクラスの拡張としてescapeForXmlという新しい関数が定義され、上記のように呼び出すことができます。

この関数内では、Stringクラス自体の内部でこれを記述した場合と同じように、thisを使用してレシーバーにアクセスできます。

fun String.escapeForXml() : String {
  return this
    .replace("&", "&")
    .replace("<", "<")
    .replace(">", ">")
}

3.1. ジェネリック拡張メソッドの記述

一般的に、複数の型に適用されることを意図した拡張メソッドを作成したい場合はどうなりますか? Any型を拡張することもできます。これはJavaのObjectクラスに相当しますが、もっと良い方法があります。

Extension methods can be applied to a generic receiver as well as a concrete one

fun  T.concatAsString(b: T) : String {
    return this.toString() + b.toString()
}

これは、一般的な要件を満たす任意のタイプに適用でき、関数内のthis値はタイプセーフです。

たとえば、上記の例を使用します。

5.concatAsString(10) // compiles
"5".concatAsString("10") // compiles
5.concatAsString("10") // doesn't compile

3.2. 中置拡張メソッドの記述

Infixメソッドは、ピリオドや角括弧なしでメソッドを呼び出すことができるため、DSLスタイルのコードを記述するのに便利です。

infix fun Number.toPowerOf(exponent: Number): Double {
    return Math.pow(this.toDouble(), exponent.toDouble())
}

これを他のinfixメソッドと同じように呼び出すことができます:

3 toPowerOf 2 // 9
9 toPowerOf 0.5 // 3

3.3. 演算子拡張メソッドの記述

演算子メソッドを拡張として書くこともできます。

Operator methods are ones that allow us to take advantage of the operator shorthand instead of the full method name –たとえば、plus演算子メソッドは、+演算子を使用して呼び出される場合があります。

operator fun List.times(by: Int): List {
    return this.map { it * by }
}

繰り返しますが、これは他の演算子メソッドと同じように機能します。

listOf(1, 2, 3) * 4 // [4, 8, 12]

4. JavaからKotlin拡張関数を呼び出す

ここで、JavaがKotlin拡張関数でどのように動作するかを見てみましょう。

In general, every extension method we define in Kotlin is available for us to use in Java.ただし、infixメソッドは、ドットと括弧を使用して呼び出す必要があることに注意してください。 演算子の拡張子も同じです。プラス文字(+)のみを使用することはできません。 これらの機能は、Kotlinでのみ使用できます。

ただし、@InlineOnlyでマークされているため、letapplyなどのJavaの標準Kotlinライブラリメソッドの一部を呼び出すことはできません。

4.1. Javaのカスタム拡張機能の可視性

以前に定義した拡張関数の1つであるString.escapeXml()を使用してみましょう。 拡張メソッドを含むファイルはStringUtil.ktと呼ばれます。

ここで、Javaから拡張メソッドを呼び出す必要がある場合は、クラス名StringUtilKtを使用する必要があります。 Note that we have to add the Kt suffix:

String xml = "hi";

String escapedXml = StringUtilKt.escapeForXml(xml);

assertEquals("hi", escapedXml);

最初のescapeForXmlパラメータに注意してください。 この追加の引数は、拡張機能のレシーバタイプです。 Kotlin with top-level extension function is a pure Java class with a static method. そのため、元のString.をなんらかの方法で渡す必要があります。

そしてもちろん、Java,と同様に、静的インポートを使用できます。

import static com.example.kotlin.StringUtilKt.*;

4.2. 組み込みKotlin拡張メソッドの呼び出し

Kotlinは、多くの組み込み拡張関数を提供することにより、コードをより簡単かつ迅速に記述できるようにします。 たとえば、Javaから直接呼び出すことができるString.capitalize()メソッドがあります。

String name = "john";

String capitalizedName = StringsKt.capitalize(name);

assertEquals("John", capitalizedName);

ただし、we can’t call extension methods marked with @InlineOnly from Java, for example:

inline fun  T.let(block: (T) -> R): R

4.3. 生成されたJava静的クラスの名前変更

Kotlin extension関数が静的なJavaメソッドであることはすでに知っています。 生成されたJavaクラスの名前を注釈@file:JvmName(name: String)で変更しましょう。

これは、ファイルの先頭に追加する必要があります。

@file:JvmName("Strings")
package com.example.kotlin

fun String.escapeForXml() : String {
    return this
      .replace("&", "&")
      .replace("<", "<")
      .replace(">", ">")
}

ここで、拡張メソッドを呼び出す場合は、Stringsクラス名を追加するだけです。

Strings.escapeForXml(xml);

また、静的インポートを追加することもできます。

import static com.example.kotlin.Strings.*;

5. 概要

拡張メソッドは、システムにすでに存在する型を拡張するための便利なツールです。必要な機能がないため、または単にコードの特定の領域を管理しやすくするためです。

ここでは、システムですぐに使用できるいくつかの拡張メソッドを見てきました。 さらに、拡張メソッドのさまざまな可能性を調査しました。 この機能のいくつかの例は、over on GitHubにあります。