
Artikel ini akan berbicara tentang penggunaan statika di Kotlin.
Mari kita mulai.
Kotlin tidak memiliki statis!
Ini dinyatakan dalam dokumentasi resmi.
Dan sepertinya ini bisa menyelesaikan artikel. Tapi biarkan saya, bagaimana caranya? Lagipula, jika di Android Studio Anda memasukkan kode Java ke file Kotlin, maka konverter pintar akan melakukan keajaiban, ubah semuanya menjadi kode dalam bahasa yang benar dan itu akan berfungsi! Tetapi bagaimana dengan kompatibilitas penuh dengan Java?
Pada titik ini, pengembang mana pun, yang belajar tentang kurangnya statis di Kotlin, akan masuk ke dokumentasi dan forum untuk mengatasi masalah ini. Mari kita berkumpul bersama, penuh perhatian dan susah payah. Saya akan mencoba menyimpan pertanyaan sesedikit mungkin pada akhir artikel ini.
Apa itu statis di Jawa? Ada:
- bidang statis kelas
- metode kelas statis
- kelas bersarang statis
Mari kita lakukan percobaan (ini adalah hal pertama yang terlintas dalam pikiran).
Buat kelas Java sederhana:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } }
Semuanya mudah di sini: di kelas kita membuat bidang statis dan metode statis. Kami melakukan segalanya secara publik untuk percobaan dengan akses dari luar. Kami menghubungkan bidang dan metode secara logis.
Sekarang buat kelas Kotlin kosong dan coba salin semua konten dari kelas SimpleClassJava1 ke dalamnya. Kami menjawab "ya" untuk pertanyaan yang dihasilkan tentang konversi dan melihat apa yang terjadi:
class SimpleClassKotlin1 { var staticField = "Hello, static!" fun setStaticValue(value: String) { staticField = value } }
Tampaknya ini bukan yang kita butuhkan ... Untuk memastikan hal ini, kita akan mengkonversi bytecode kelas ini ke kode Java dan melihat apa yang terjadi:
public final class SimpleClassKotlin1 { @NotNull private String staticField = "Hello, static!"; @NotNull public final String getStaticField() { return this.staticField; } public final void setStaticField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.staticField = var1; } public final void setStaticValue(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); this.staticField = value; } }
Ya Semuanya persis seperti yang terlihat. Tidak statis di sini. Konverter hanya memotong pengubah statis di tanda tangan, seolah-olah tidak ada. Untuk jaga-jaga, kami akan segera membuat kesimpulan: jangan membabi buta pada konverter, kadang-kadang dapat membawa kejutan yang tidak menyenangkan.
Ngomong-ngomong, sekitar enam bulan yang lalu, mengonversi kode Java yang sama ke Kotlin akan menunjukkan hasil yang sedikit berbeda. Jadi sekali lagi: berhati-hatilah dengan konversi otomatis!
Kami bereksperimen lebih lanjut.
Kami pergi ke kelas mana saja di Kotlin dan mencoba memanggil elemen statis dari kelas Java di dalamnya:
SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!"
Inilah caranya! Semuanya dipanggil dengan sempurna, bahkan pelengkapan otomatis kode memberi tahu kita segalanya! Cukup penasaran.
Sekarang mari kita beralih ke bagian yang lebih penting. Memang, pencipta Kotlin memutuskan untuk menjauh dari statis dalam bentuk di mana kita terbiasa menggunakannya. Mengapa kami melakukan hal itu dan kami tidak akan membahas sebaliknya - ada banyak perselisihan dan pendapat tentang hal ini di jaringan. Kami hanya akan mencari cara untuk hidup dengannya. Secara alami, kami tidak hanya kekurangan statika. Kotlin memberi kita satu set alat yang dengannya kita dapat mengkompensasi yang hilang. Mereka cocok untuk penggunaan di dalam ruangan. Dan kompatibilitas penuh yang dijanjikan dengan kode Java. Ayo pergi!
Hal tercepat dan termudah yang dapat Anda sadari dan mulai gunakan adalah alternatif yang kami tawarkan alih-alih metode statis - fungsi tingkat paket. Apa ini Ini adalah fungsi yang bukan milik kelas mana pun. Yaitu, jenis logika yang ada di ruang hampa di suatu tempat di ruang paket. Kami dapat menjelaskannya di file apa pun di dalam paket yang menarik minat kami. Misalnya, beri nama file ini JustFun.kt dan letakkan di paket
com.example.mytestapplication
package com.example.mytestapplication fun testFun(){
Konversi bytecode dari file ini di Java dan lihat ke dalam:
public final class JustFunKt { public static final void testFun() {
Kita melihat bahwa di Jawa suatu kelas dibuat yang namanya memperhitungkan nama file di mana fungsi dijelaskan, dan fungsi itu sendiri berubah menjadi metode statis.
Sekarang jika kita ingin memanggil fungsi
testFun
di Kotlin dari kelas (atau fungsi yang sama) yang terletak di
package com.example.mytestapplication
(yaitu, paket yang sama dengan fungsi), maka kita dapat mengaksesnya tanpa trik tambahan. Jika kita memanggilnya dari paket lain, maka kita harus mengimpor, yang akrab bagi kita dan biasanya berlaku untuk kelas:
import com.example.pavka.mytestapplication.testFun
Jika kita berbicara tentang memanggil fungsi t
estFun
dari kode Java, maka kita selalu perlu mengimpor fungsi, terlepas dari paket mana kita memanggilnya:
import static com.example.pavka.mytestapplication.ForFunKt.testFun;
Dokumentasi mengatakan bahwa dalam kebanyakan kasus, alih-alih metode statis, cukup bagi kita untuk menggunakan fungsi tingkat paket. Namun, menurut pendapat pribadi saya (yang tidak harus bertepatan dengan pendapat orang lain), metode penerapan statika ini hanya cocok untuk proyek-proyek kecil.
Ternyata fungsi-fungsi ini bukan milik kelas mana pun secara eksplisit. Secara visual, panggilan mereka tampak seperti panggilan ke metode kelas (atau induknya) di mana kita berada, yang kadang-kadang bisa membingungkan. Nah dan yang utama - hanya ada satu fungsi dengan nama itu di paket. Bahkan jika kita mencoba membuat fungsi dengan nama yang sama di file lain, sistem akan memberi kita kesalahan. Jika kita berbicara tentang proyek besar, maka cukup sering kita memiliki, misalnya, pabrik yang berbeda memiliki metode statis dengan nama yang sama.
Mari kita lihat alternatif lain untuk menerapkan metode dan bidang statis.
Ingat apa bidang statis suatu kelas. Bidang kelas ini milik kelas yang dideklarasikan, tetapi bukan milik turunan spesifik kelas, yaitu dibuat dalam satu instance untuk seluruh kelas.
Kotlin menawarkan kepada kami untuk tujuan ini menggunakan beberapa entitas tambahan, yang juga ada dalam satu salinan. Dengan kata lain, singleton.
Kotlin memiliki kata kunci objek untuk mendeklarasikan singletones.
object MySingltoneClass {
Objek seperti itu diinisialisasi dengan malas, yaitu, pada saat panggilan pertama kepada mereka.
Ok, ada juga singletones di Jawa, di mana statistiknya?
Untuk kelas apa pun di Kotlin, kita dapat membuat objek pendamping atau pendamping. Seorang lajang terikat pada kelas tertentu. Ini dapat dilakukan dengan menggunakan 2 kata kunci
companion object
bersamaan:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){
Di sini kita memiliki kelas
SimpleClassKotlin1
, di dalamnya kita mendeklarasikan singleton dengan kata kunci objek dan mengikatnya ke objek di dalamnya yang dideklarasikan dengan kata kunci pendamping. Di sini Anda dapat memperhatikan fakta bahwa, tidak seperti deklarasi singleton sebelumnya (MySingltoneClass), nama kelas singleton tidak diindikasikan. Jika objek tersebut dinyatakan sebagai pendamping, maka objek tersebut tidak boleh disebutkan namanya. Maka secara otomatis akan dinamai
Companion
. Jika perlu, kita bisa mendapatkan instance kelas pendamping dengan cara ini:
val companionInstance = SimpleClassKotlin1.Companion
Namun, panggilan ke properti dan metode kelas pendamping dapat dilakukan secara langsung, melalui panggilan ke kelas yang dilampirkan:
SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!")
Itu sudah terlihat seperti memanggil bidang dan kelas statis, kan?
Jika perlu, kita dapat memberi nama kelas pendamping, tetapi dalam praktiknya hal ini sangat jarang dilakukan. Dari fitur-fitur menarik dari kelas yang menyertainya, dapat dicatat bahwa, seperti kelas biasa, ia dapat mengimplementasikan antarmuka, yang terkadang dapat membantu kami menambahkan sedikit lebih banyak urutan ke kode:
interface FactoryInterface<T> { fun factoryMethod(): T } class SimpleClassKotlin1 { companion object : FactoryInterface<MyClass> { override fun factoryMethod(): MyClass = MyClass() } }
Kelas pendamping hanya dapat memiliki satu kelas. Namun, tidak ada yang melarang kita untuk mendeklarasikan sejumlah objek tunggal di dalam kelas, tetapi dalam hal ini kita harus secara eksplisit menentukan nama kelas ini dan, dengan demikian, menunjukkan nama ini ketika merujuk ke bidang dan metode kelas ini.
Berbicara tentang kelas yang dideklarasikan sebagai objek, kita dapat mengatakan bahwa kita juga dapat mendeklarasikan objek bersarang di dalamnya, tetapi kita tidak dapat mendeklarasikan objek pendamping di dalamnya.
Sudah waktunya untuk melihat "di bawah tenda." Ambil kelas sederhana kami:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ } } object OneMoreObject { var value = 1 fun function(){ } }
Sekarang dekompilasi bytecode-nya di Java:
public final class SimpleClassKotlin1 { @NotNull private static String companionField = "Hello!"; public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); public static final class OneMoreObject { private static int value; public static final SimpleClassKotlin1.OneMoreObject INSTANCE; public final int getValue() { return value; } public final void setValue(int var1) { value = var1; } public final void function() { } static { SimpleClassKotlin1.OneMoreObject var0 = new SimpleClassKotlin1.OneMoreObject(); INSTANCE = var0; value = 1; } } public static final class Companion { @NotNull public final String getCompanionField() { return SimpleClassKotlin1.companionField; } public final void setCompanionField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); SimpleClassKotlin1.companionField = var1; } public final void companionFun(@NotNull String vaue) { Intrinsics.checkParameterIsNotNull(vaue, "vaue"); } private Companion() { }
Kami melihat apa yang terjadi.
Properti objek pendamping direpresentasikan sebagai bidang statis kelas kami:
private static String companionField = "Hello!";
Ini tampaknya persis seperti yang kita inginkan. Namun, bidang ini bersifat pribadi dan diakses melalui pengambil dan penyetel kelas pendamping kami, yang disajikan di sini sebagai
public static final class
, dan turunannya disajikan sebagai konstanta:
public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null);
Fungsi companionFun tidak menjadi metode statis kelas kami (mungkin seharusnya tidak). Tetap fungsi singleton diinisialisasi di kelas SimpleClassKotlin1. Namun, jika Anda memikirkannya, maka secara logis ini adalah tentang hal yang sama.
Dengan kelas
OneMoreObject
situasinya sangat mirip. Perlu dicatat hanya bahwa di sini, tidak seperti pendampingnya, bidang kelas nilai tidak pindah ke kelas
SimpleClassKotlin1
, tetapi tetap di
OneMoreObject
, tetapi juga menjadi statis dan menerima pengambil dan penyetel yang dihasilkan.
Mari kita coba memahami semua hal di atas.
Jika kita ingin menerapkan bidang statis atau metode kelas di Kotlin, maka untuk ini kita harus menggunakan objek pengiring yang dideklarasikan di dalam kelas ini.
Memanggil bidang dan fungsi ini dari Kotlin akan terlihat persis sama dengan memanggil statika di Jawa. Tetapi bagaimana jika kita mencoba memanggil bidang dan fungsi ini di Jawa?
Autocomplete memberi tahu kami bahwa panggilan berikut tersedia:
SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField();
Artinya, di sini kita tidak akan pergi dari mana pun langsung menunjukkan nama pendamping. Dengan demikian, nama yang ditugaskan ke objek pengiring default digunakan di sini. Sangat tidak nyaman, bukan?
Namun demikian, pencipta Kotlin memungkinkan untuk membuatnya terlihat lebih akrab di Jawa. Dan ada beberapa cara untuk melakukan ini.
@JvmField var companionField = "Hello!"
Jika kita menerapkan anotasi ini ke bidang
companionField
objek
companionField
kita, maka ketika mengkonversi bytecode ke Java, kita melihat bahwa field statis
companionField
SimpleClassKotlin1 tidak lagi pribadi, tetapi publik, dan pengambil dan penyetel untuk pendampingField hilang dalam kelas
Companion
statis. Sekarang kita dapat mengakses
companionField
dari kode Java dengan cara biasa.
Cara kedua adalah menentukan pengubah
lateinit
untuk properti
lateinit
pendamping, properti dengan inisialisasi terlambat. Jangan lupa bahwa ini hanya berlaku untuk properti-var, dan tipenya harus non-nol dan tidak boleh primitif. Yah, jangan lupa tentang aturan untuk berurusan dengan properti seperti itu.
lateinit var lateinitField: String
Dan satu cara lagi: kita dapat mendeklarasikan properti dari objek pendamping dengan menetapkan konstanta pengubah. Mudah ditebak bahwa ini harus menjadi properti-val.
const val myConstant = "CONSTANT"
Dalam setiap kasus ini, kode Java yang dihasilkan akan berisi bidang statis publik biasa, dalam kasus const, bidang ini juga akan final. Tentu saja, perlu dipahami bahwa masing-masing dari 3 kasing ini memiliki tujuan logisnya sendiri, dan hanya yang pertama dirancang khusus untuk kemudahan penggunaan dengan Java, sisanya mendapatkan "roti" ini seolah-olah dalam sebuah beban.
Harus dicatat secara terpisah bahwa pengubah const dapat digunakan untuk properti objek, objek pendamping, dan untuk properti tingkat paket. Dalam kasus terakhir, kita mendapatkan yang sama dengan menggunakan fungsi level paket dan dengan batasan yang sama. Kode Java dihasilkan dengan bidang publik statis di kelas, yang namanya memperhitungkan nama file yang kami gambarkan konstanta. Paket hanya dapat memiliki satu konstanta dengan nama yang ditentukan.
Jika kita ingin fungsi objek pengiring juga dikonversi ke metode statis saat membuat kode Java, maka untuk ini kita perlu menerapkan penjelasan
@JvmStatic
ke fungsi ini.
Juga diperbolehkan untuk menerapkan anotasi
@JvmStatic
pada properti objek pendamping (dan hanya objek tunggal). Dalam hal ini, properti tidak akan berubah menjadi bidang statis, tetapi pengambil dan penyetel statis untuk properti ini akan dihasilkan. Untuk pemahaman yang lebih baik, lihat kelas Kotlin ini:
class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } }
Dalam hal ini, panggilan berikut ini valid dari Jawa:
int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10);
Panggilan berikut ini valid dari Kotlin:
SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField
Jelas bahwa untuk Java Anda harus menggunakan 3 yang pertama, dan untuk Kotlin yang 2. yang kedua. Sisa panggilan hanya valid.
Sekarang tinggal mengklarifikasi yang terakhir. Bagaimana dengan kelas bersarang statis? Semuanya sederhana di sini - analog dari kelas semacam itu di Kotlin adalah kelas bersarang reguler tanpa pengubah:
class SimpleClassKotlin1 { class LooksLikeNestedStatic { } }
Setelah mengonversi bytecode ke Java, kita melihat:
public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } }
Memang inilah yang kami butuhkan. Jika kita tidak ingin kelasnya final, maka dalam kode Kotlin kita tentukan pengubah terbuka untuknya. Saya ingat ini untuk berjaga-jaga.
Saya pikir Anda bisa meringkas. Memang, di Kotlin sendiri, seperti yang dikatakan, tidak ada bentuk statis di mana kita terbiasa melihatnya. Tetapi seperangkat alat yang diusulkan memungkinkan kita untuk menerapkan semua jenis statika dalam kode Java yang dihasilkan. Kompatibilitas penuh dengan Java juga disediakan, dan kami dapat langsung memanggil bidang statis dan metode kelas Java dari Kotlin.
Dalam kebanyakan kasus, menerapkan stat di Kotlin memerlukan beberapa baris kode lagi. Mungkin ini adalah satu dari sedikit, atau mungkin satu-satunya kasus ketika Anda perlu menulis lebih banyak di Kotlin. Namun, Anda dengan cepat terbiasa dengannya.
Saya pikir dalam proyek di mana Kotlin dan kode Java dibagikan, Anda dapat secara fleksibel mendekati pilihan bahasa yang digunakan. Sebagai contoh, menurut saya Java lebih cocok untuk menyimpan konstanta. Tetapi di sini, seperti dalam banyak hal lain, ada baiknya dipandu oleh akal sehat dan aturan untuk menulis kode dalam proyek.
Dan di akhir artikel, inilah informasi semacam itu. Mungkin di masa depan, Kotlin masih akan memiliki pengubah statis yang menghilangkan banyak masalah dan membuat kehidupan pengembang lebih mudah, dan kode lebih pendek. Saya membuat asumsi ini dengan menemukan teks yang sesuai dalam paragraf 17 dari
deskripsi fitur Kotlin .
Benar, dokumen ini berasal dari Mei 2017, dan di halaman sudah akhir 2018.
Itu semua untuk saya. Saya pikir topiknya sudah dipilah secara mendetail. Pertanyaan tulis di komentar.