Menu
Coddy logo textTech

拡張関数

CoddyのKotlinジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 30/57。

拡張関数は、型のソースコードを変更せずに、既存の型へ関数を追加します。その名前はレシーバー型の後に記述し、関数内ではthisがレシーバーを参照します。thisは省略することもできます。

fun String.shout() = uppercase() + "!"
fun Int.isEven() = this % 2 == 0

mainの中では次のようにします。

println("hello".shout())
println(7.isEven())
println(listOf(1, 2, 3, 4).count { it.isEven() })

出力:

HELLO!
false
2

拡張は自分で作成したクラスにも使用でき、拡張プロパティによって計算プロパティを追加できます。拡張から保存された状態を追加することはできず、レシーバーの公開メンバーにのみアクセスできます。

class Temperature(val celsius: Double)

val Temperature.fahrenheit: Double
    get() = celsius * 9 / 5 + 32

fun Temperature.describe() = "$celsius C is $fahrenheit F"

main内で:

println(Temperature(100.0).describe())

出力:

100.0 C is 212.0 F

拡張は静的に解決されます。式の宣言された型によって、オブジェクトのクラスではなく、プログラムのコンパイル時に拡張が選択されます。これは、オーバーライドされたメンバーとの主な違いです。

open class Shape {
    open fun member() = "shape member"
}
class Circle : Shape() {
    override fun member() = "circle member"
}

fun Shape.ext() = "shape extension"
fun Circle.ext() = "circle extension"

mainの内部では次のようになります。

val s: Shape = Circle()
println(s.member())
println(s.ext())

出力:

circle member
shape extension

クラスに同じシグネチャを持つメンバーがすでに存在する場合は、常にメンバーが優先されます。型に属し、オーバーライドされる可能性がある振る舞いにはメンバーを使用します。自分が所有していないクラスの書式設定など、外部から追加するヘルパーには拡張を使用します。

class Robot {
    fun greet() = "member greet"
}

fun Robot.greet() = "extension greet"
fun Robot.wave() = "extension wave"

mainの内部で:

val r = Robot()
println(r.greet())
println(r.wave())

出力:

member greet
extension wave
challenge icon

チャレンジ

簡単

Money(cents) は Money.kt に用意されています。MoneyExtensions.kt でその拡張を記述してください。数値を Money に変換する拡張プロパティ Int.dollars と Int.cents(12.dollars は 1200 セントになります)、$12.50(負の金額の場合は -$3.05)を返す Money.format()、合計を Money として返す List<Money>.total()、および金額を差が最大 1 セントになる parts 個の金額に分割し、大きいものを先に並べる Money.split(parts) を実装します。

用意されているコードは、12 dollars または 50 cents の行と、部数を示す数値を含む最後の行を読み取ります。そして各金額、total: $12.50、split 3: $4.17 $4.17 $4.16 を出力します。

コードは MoneyExtensions.kt と Money.kt に記述します。Main.kt には用意された入力/出力コードが含まれており、編集できません。

自分で試してみよう

fun main() {
    // 提供された入出力コード: そのままにしておく
    val input = generateSequence(::readLine).toList()
    val amounts = input.dropLast(1).map { line ->
        val (n, unit) = line.split(" ")
        if (unit == "dollars") n.toInt().dollars else n.toInt().cents
    }
    for (m in amounts) println(m.format())
    val total = amounts.total()
    println("total: ${total.format()}")
    val parts = input.last().toInt()
    println("split $parts: " + total.split(parts).joinToString(" ") { it.format() })
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Kotlinプレイグラウンド