1. object
について
object を理解するには、 Singleton というものを知っていなければいけません。
これは、「あるクラスのインスタンスを1つのみにする」という時に出てくるものです。
詳しく知りたい方はこちらがおすすめです。 デザインパターン「Singleton」
Kotlin で実装したい時は object
を使うだけでいいのです。
object Singleton
2. @JvmStatic
について
object に 何かしらの関数があったとしましょう。
object Singleton {
fun nannka(){
}
}
Kotlin であれば、Singleton.nannka()
とアクセスすることができますが
Java から実行するときは Singleton.INSTANCE.nannka()
としなければいけません。
nannka()
というのはシングルトン内の関数であって、staticな関数ではありません。
その為、シングルトンのインスタンスを経由してアクセスする必要があります。
しかし、static fun
というものはありません。
その時に登場するのが、@JvmStatic
です。
staticにしたい関数にアノテーションをつけると、static関数になります。
object Singleton {
@JvmStatic
fun nannka(){
}
}
static関数になれば、Javaでも Singleton.nannka()
のようにアクセスすることができるようになります。
どうでもいい話なので読み飛ばしてもらって構いません。
Singleton.INSTANCE.nannka()
とアクセスできるということは、
INSTANCE
という変数が @JvmStatic
で定義されているということになります。
以下のような感じになっているわけですね。(@JvmField
の方が近いです。)
object Singleton {
@JvmStatic
val INSTANCE = this
fun nannka(){
}
}
ちなみに、これを実行しようとすると、例外が発生します
java.lang.ClassFormatError: Duplicate field name "INSTANCE" with signature "LSingleton;" in class file Singleton
3. 実行時間
@JvmStatic
をつけるかつけないかでどれだけ実行時間に違いがあるのかを検証してみます。
検証1
10億回関数を実行します。関数内の処理は変数の加算処理1回だけです。
ソースコード
fun main(){
val loop = 1000000000L
val timeJvmStatic = block(loop) {
CounterJvmStatic.count()
}
val timeSingleton = block(loop) {
CounterSingleton.count()
}
println("%,d: %,d %,d".format(loop, timeJvmStatic, timeSingleton))
}
fun block(loop: Long, run: () -> Unit): Long {
val begin = System.currentTimeMillis()
for(i in 0 until loop) {
run.invoke()
}
val end = System.currentTimeMillis()
return end - begin
}
object CounterJvmStatic {
var count = 0
@JvmStatic
fun count() {
count ++
}
}
object CounterSingleton {
var count = 0
fun count() {
count ++
}
}
結果
1,000,000,000: 665 1,736
1,000,000,000: 651 1,717
1,000,000,000: 652 1,722
2倍くらい違いますね。しかし、10億回実行してこの数字なわけですから、ほとんど無視できるくらいでしょうか。
検証2
関数内の処理が重かった場合を考えてみましょう。
10万回関数を実行します。関数内の処理は変数の加算処理を1000回です。
ソースコード
fun main(){
val loop = 1000000L
val timeJvmStatic = block(loop) {
CounterJvmStatic.count()
}
val timeSingleton = block(loop) {
CounterSingleton.count()
}
println("%,d: %,d %,d".format(loop, timeJvmStatic, timeSingleton))
}
fun block(loop: Long, run: () -> Unit): Long {
val begin = System.currentTimeMillis()
for(i in 0 until loop) {
run.invoke()
}
val end = System.currentTimeMillis()
return end - begin
}
object CounterJvmStatic {
var count = 0
@JvmStatic
fun count() {
for(i in 0 until 1000) count ++
}
}
object CounterSingleton {
var count = 0
fun count() {
for(i in 0 until 1000) count ++
}
}
結果
1,000,000: 65 80
1,000,000: 55 57
1,000,000: 59 61
やはり、ほとんど変わりませんね。
4. まとめ
実行時間のためだけに @JvmStatic
をつけるべきではないと思います。
Javaとの互換性のためのアノテーションなわけですから、Kotlinのみの開発で使うことはないでしょう。
実行時間が気になるほど、object にアクセスをするような状況なのであれば、一度変数で保持をする方が一般的だと思います。
object Singleton {
fun nannka(){
}
}
fun main(){
val singeleton = Singleton
singeleton.nannka()
}
しかし、ここで実行時間を気にするくらいであれば、他の部分で気にするべきなのでしょう。
余談ですが、object でクラスが定義されていれば、プログラムを読んでいる人からすると
「そのクラスはマルチプルである必要がない」と分かるので、理解しやすくなると思います。
積極的に object は使うべきではないでしょうか。私はそう思います。