1. 序章

Kotlinは、拡張機能の概念を導入します。これは、拡張機能を定義した後、継承やデコレータパターンの形式を使用せずに、既存のクラスを新しい機能で拡張する便利な方法です。 元のAPIの一部であったため、基本的に使用できます。

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

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

String escaped = escapeStringForXml(input);

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

val escaped = input.escapeForXml()

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

2. 標準ライブラリ拡張関数

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

2.1. コンテキスト調整拡張機能

いくつかの汎用拡張機能が存在し、アプリケーションのすべてのタイプに適用できます。 これらは、コードが適切なコンテキストで実行されるようにするため、および場合によっては変数がnullでないことを確認するために使用できます。

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

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

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

これは、オプションまたはストリームクラスの map()メソッドに似ています。この場合、特定のを変換するアクションを表す関数を渡します。文字列を大文字で表現します。

変数名は、拡張機能が作用している変数であるため、呼び出しの受信者として知られています

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

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

この場合、let()に渡されるブロックは、変数名がnull以外の場合にのみ評価されます。 これは、ブロック内で、値nがnull以外であることが保証されていることを意味します。 この詳細はこちら

let()には他にも便利なものがありますが、必要に応じて便利です。

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

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

apply()はrun()と同じように機能しますが、提供されたブロックから値を返す代わりに、レシーバーを返します。

apply()を利用して、関連する呼び出しを連鎖させましょう。

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

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

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

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

takeIf()拡張機能には、レシーバーに作用する述語が提供されます。この述語がtrueを返す場合は、レシーバーまたは null を返します。それ以外の場合、これは、一般的なmap()および filter()メソッド:

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

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

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

2.2. コレクションの拡張機能

Kotlinは、標準のJavaコレクションに多数の拡張関数を追加して、コードの操作を容易にします

これらのメソッドは、 _Collections.kt、 _Ranges.kt _Sequences.kt 、および同等の_Arrays.kt内にあります。代わりに配列に適用するメソッド。 (Kotlinでは、配列コレクションと同じように扱うことができることに注意してください)

これらの拡張方法は多すぎてここで説明できません。これらのファイルを参照して、何が利用できるかを確認してください。

コレクションに加えて、Kotlinは、_Strings.ktで定義されているStringクラスに多数の拡張関数を追加します。 これらにより、文字列を文字のコレクションのように扱うことができます。

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

3. 拡張関数の記述

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

拡張関数は他の関数と同じように記述されますが、レシーバークラスは関数名の一部としてピリオドで区切られて提供されます。

例えば:

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

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

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

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

3.1. 一般的な拡張関数の記述

一般的に、複数の型に適用されることを意図した拡張関数を記述したい場合はどうなりますか? Anyタイプを拡張することもできます。これはJavaのObjectクラスと同等ですが、もっと良い方法があります。

拡張機能は、一般的な受信機だけでなく、具体的な受信機にも適用できます

fun <T> 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. 中置拡張関数の記述

中置メソッドは、ピリオドや角かっこなしでメソッドを呼び出すことができるため、DSLスタイルのコードを作成するのに役立ちます。

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

これで、他のインフィックスメソッドと同じように呼び出すことができます。

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

3.3. 演算子拡張関数の記述

拡張機能として演算子関数を作成することもできます。

演算子メソッドは、完全なメソッド名の代わりに演算子の省略形を利用できるようにするものです。たとえば、plus演算子メソッドは+を使用して呼び出すことができます。 ]演算子:

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

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

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

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

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

一般に、Kotlinで定義するすべての拡張関数は、Javaで使用できます。ただし、infixメソッドはドットで呼び出す必要があることに注意してください。と括弧。 演算子拡張と同じ—プラス文字(+)のみを使用することはできません。 これらの機能は、Kotlinでのみ利用できます。

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

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

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

ここで、Javaから拡張メソッドを呼び出す必要がある場合は、クラス名 StringUtilKtを使用する必要があります。Ktサフィックスを追加する必要があることに注意してください:

String xml = "<a>hi</a>";

String escapedXml = StringUtilKt.escapeForXml(xml);

assertEquals("&lt;a&gt;hi&lt;/a&gt;", escapedXml);

最初のescapeForXmlパラメーターに注意してください。 この追加の引数は、拡張関数レシーバータイプです。 トップレベルの拡張機能を備えたKotlinは、静的メソッドを備えた純粋なJavaクラスです。 だから、どういうわけかオリジナルを渡す必要があります弦。

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

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

4.2. 組み込みのKotlin拡張関数の呼び出し

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

String name = "john";

String capitalizedName = StringsKt.capitalize(name);

assertEquals("John", capitalizedName);

ただし、 Javaから@InlineOnlyでマークされた拡張メソッドを呼び出すことはできません。例:

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

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

Kotlin拡張関数が静的なJavaメソッドであることはすでに知っています。 生成されたJavaクラスの名前を、アノテーション @file:JvmName(name:String)で変更しましょう。

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

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

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

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

Strings.escapeForXml(xml);

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

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

5. 概要

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

ここでは、システムですぐに使用できる拡張機能をいくつか見てきました。 さらに、拡張機能のさまざまな可能性を検討しました。

この機能のいくつかの例は、GitHubにあります。