拡張プロパティの基本
拡張関数・拡張プロパティの基本を理解するために、極めてシンプルな class
を定義しておきたいと思います。
下のコードは、自分のプロフィールを管理する class
を想定していますが、ご覧の通り name
プロパティしか持っていません。
class MyProfile {
val name = "Tom"
}
プロフィール情報が名前だけでは物足りないので、年齢を表す age
プロパティを追加したくなったとしましょう。
この時、定義された class
を自由に編集できる状況であれば、下のように class
内に age
プロパティを追加すれば良いでしょう。
class MyProfile {
val name = "Tom"
val age = 32
}
しかし、外部のライブラリで定義された class
など、class
を自由に編集できない場合も少なくありません。
そのような場合、class
の内部にプロパティやメソッドを直接定義することはできませんが、拡張関数・拡張プロパティという形であれば、独自にプロパティやメソッドを追加することが可能です。
もし、MyProfile class
を直接編集することができないシチュエーションならば、次のように拡張プロパティを利用することで、class
にプロパティを追加することができます。
class MyProfile {
val name = "Tom"
}
val MyProfile.age
get () = 32
fun main() {
val myProfile = MyProfile()
println(myProfile.age) // Outputs: 32
}
通常のプロパティにアクセスするのと同じように、拡張プロパティも【 .(ドット)】でアクセスすることができます。
なお、拡張プロパティはカスタムゲッターを用いて定義する必要があり、下のように直接的に値を代入するコードはエラーになります。
class MyProfile {
val name = "Tom"
}
val MyProfile.age = 32 // This code will cause an error
これは拡張プロパティがバッキングフィールドを持てないためで、拡張プロパティは実際にデータを内部に持つのではなく、ゲッターによって毎回計算される仕組みが関係しています。(そのため、拡張プロパティにカスタムセッターを設定することはできません。)
いくら拡張プロパティを利用することで後から class
を拡張できるとは言え、ある程度制限がかかってしまうことは覚えておくと良いと思います。
POINT!
- ・カスタムプロパティを利用することで、既存のclassに後からプロパティを追加できる!
- ・ただし、カスタムプロパティは内部にデータを保持するものではない!
- ・そのため、カスタムゲッターによって設定される必要があり、カスタムセッターは設定できない!
拡張関数の基本
前のステップでは拡張プロパティの例を示したので、このステップでは拡張関数の例を紹介します。
引き続き、MyProfile class
を例にしてみましょう。
下のコードは、拡張関数を利用して、name
プロパティに格納された文字列(名前)を出力するメソッド printName()
を MyProfile
に追加した例です。
class MyProfile {
val name = "Tom"
}
fun MyProfile.printName() = println(this.name)
fun main() {
val myProfile = MyProfile()
myProfile.printName() // Outputs: Tom
}
このように、MyProfile
クラス内に直接メソッドが定義されていなくても、そうであるかのようにメソッドを追加することができます。
なお、サンプルコードに記載されているように、自身の class
のプロパティには、this
を使ってアクセスすることができます。
また、拡張プロパティで定義されたプロパティは、厳密に言えばそのクラスの内部に含まれているものではありませんが、クラス内で定義されたプロパティと同じように this
を使ってアクセスすることが可能です。
下のコードは、拡張関数で拡張プロパティ(age
)にアクセスしている例です。
class MyProfile {
val name = "Tom"
}
val MyProfile.age
get () = 32
fun MyProfile.printAge() {
println(this.age)
}
fun main() {
val myProfile = MyProfile()
myProfile.printAge() // Outputs: 32
}
拡張関数と拡張プロパティを組み合わせることで、より柔軟なカスタマイズが可能となります。
とは言え、自身で作成した class
であれば、直接その class
のコードを編集した方がより柔軟に機能を拡張できるので、そのようなケースでは拡張関数・拡張プロパティの出番はあまりないでしょう。
ということで、次のステップではもう少し実践的・実用的な拡張関数や拡張プロパティの使用例をご紹介したいと思います!
POINT!
- ・拡張関数を利用することで、後からclassにメソッドを追加することができる!
- ・class自身のプロパティには this を使ってアクセスすることができる!
- ・拡張プロパティと拡張関数を組み合わせることも可能!
IntやStringを拡張する
Kotlinでは Int
(整数)や、String
(文字列)といった様々なデータ型が存在しますが、その実態は class
です。
当然、String
や Int
の内部のコードを開発者が勝手に改変することはできませんが、拡張関数や拡張プロパティを利用すれば、独自の機能を追加することができます。
例えば、文字列を★マークで装飾した結果を得たい場合、次のように拡張プロパティを定義することができます。
val String.decorated
get() = "★☆ $this ☆★"
fun main() {
val message = "Hello"
println(message.decorated) // ★☆ Hello ☆★
}
上の例では、どのような文字列に対しても、String
の拡張プロパティである decorated
にアクセスすることで、★マークで装飾した結果を得ることができるようになっています。
このように、拡張プロパティは値の簡易的な装飾や変換などに利用されることが多いです。
そしてもう一つ、次は拡張関数の実用例を考えてみましょう。
Kotlinの Int
型には標準で様々なメソッドが用意されていますが、『特定の整数を二乗する』といったメソッドは意外にも用意されていません。
特定の整数を二乗した値を頻繁に得たい場合、その都度計算するのは面倒なので、拡張関数を定義してしまった方が得策です。
具体的には次のように拡張関数を定義・利用することができます。
fun Int.square() = this * this
val n = 5
val squared = n.square()
fun main() {
println(squared) // Outputs: 25
}
このように、拡張関数を利用することで整数(Int
)に対し、いつでも簡単にその数を二乗した結果を得ることができるようになります。
また、上の例のように特定の整数を二乗するだけであれば引数を渡す必要がないので、拡張プロパティを利用しても同じような機能を追加できます。
val Int.squared: Int
get() = this * this
ただ、数値を足したり掛けたりなどの計算を伴う場合は、どちらかと言うと拡張関数の方が直感的に分かりやすいと言えるかもしれません。(チームや個人の好みによると思います)
いずれにせよ、拡張関数や拡張プロパティを利用することで、String
や Int
といったデータ型にも様々なプロパティやメソッドを追加することができます。
アイデア次第で、コードの可読性を高めたり、コーディングの効率を良くしたりすることができるので、ぜひ拡張関数や拡張プロパティを利用して色々カスタマイズしてみてください!
POINT!
- ・StringやIntといったデータ型(実態はclass)にも拡張関数や拡張プロパティを設定できる!
- ・拡張プロパティは、値の装飾や変換に利用されることが多い!
- ・拡張関数は、何らかの計算や複雑な処理を伴う場合に利用されることが多い!