news

Tampilkan postingan dengan label android. Tampilkan semua postingan
Tampilkan postingan dengan label android. Tampilkan semua postingan

Sabtu, 13 Juni 2020

Apa itu Coroutines di Kotlin

201904251634218988bc767d2f00065866774ee4b6838b.png
Coroutines merupakan fitur unggulan pada Kotlin yang diperkenalkan pada Kotlin versi 1.1. Saat ini coroutines sudah mencapai versi 1.3.2. Coroutines adalah cara baru untuk menulis kode asynchronous dan non-blocking. Seperti tagline-nya “Don’t block, Keep moving” yang dikenalkan pada saat rilis Kotlin versi 1.3. 

Kenapa coroutines sering disebut sebagai thread yang ringan? Coroutines juga mendefinisikan eksekusi dari sekumpulan instruksi untuk dieksekusi oleh prosesor. Selain itu, coroutines juga memiliki life cycle yang sama dengan thread.
Walaupun coroutines dan threads bekerja dengan cara sama, coroutines jauh lebih ringan dan efisien. 
Jutaan coroutines dapat berjalan pada beberapa threads. Jika dibandingkan dengan threads, coroutines tidak hanya mudah diterapkan, melainkan juga jauh lebih powerful. Kelebihan tersebut terutama berlaku pada lingkungan mobile, di mana setiap milliseconds kenaikan kinerja sangat diperhitungkan. Selain itu, perbedaan lainnya adalah coroutines dikelola oleh pengguna, sedangkan threads dikelola oleh sistem operasi.
Coroutines dijalankan di dalam threads. Satu thread dapat memiliki banyak coroutine di dalamnya. 
Namun, seperti yang sudah disebutkan, hanya satu instruksi yang dapat dijalankan dalam thread pada waktu tertentu. 
Artinya, jika Anda memiliki sepuluh coroutines di thread yang sama, hanya satu dari sepuluh coroutines tersebut yang akan berjalan pada titik waktu tertentu.
Walaupun coroutines dijalankan dalam sebuah thread, perlu diperhatikan bahwa keduanya tak saling terikat. 
Menariknya, kita juga bisa menjalankan bagian dari coroutine dalam sebuah thread, menundanya, kemudian melanjutkannya pada thread yang berbeda. Coroutines cukup fleksibel untuk kita menentukan- Apakah suatu thread akan menjalankan sebuah coroutine atau justru menahannya?

Permasalahan Pada Concurrency


Permasalahan Pada Concurrency

Membuat concurrent program ? Banyak developer sering mengeluhkan itu sulit. Selain kodenya bisa dibilang sulit dituliskan, terdapat juga beberapa tantangan yang perlu dihadapi. Ada beberapa permasalahan yang wajib bisa kita tangani pada concurrency, yaitu deadlockslivelocksstarvation dan juga race conditions.


Deadlocks

Untuk menjamin bahwa kode concurrent bisa disinkronkan dengan benar, apa salah satu hal yang tidak bisa dihindari ? Kita perlu menunda/memblokir eksekusi saat sebuah perintah diselesaikan di thread yang berbeda. Hal tersebut disebabkan oleh deadlocks, yaitu sebuah kondisi di mana dua proses atau lebih saling menunggu proses yang lain untuk melepaskan resource yang sedang digunakan.

Deadlocks mengakibatkan proses-proses yang sedang berjalan, tak kunjung selesai. Kasus ini merupakan umum terjadi ketika banyak proses yang saling berbagi sumber daya. Dalam situasi yang bisa dibilang cukup kompleks itu, tak jarang salah satu proses harus terpaksa diberhentikan.
Kasus deadlocks sebenarnya bisa kita amati pada kehidupan nyata. Sebagai contoh, perhatikan ilustrasi berikut ini:
20190429101516ed87b7c003a11f07d0a5e1f3e2041ea6.png
Ilustrasi di atas menggambarkan antrian dua mobil yang sama-sama akan menyeberangi sebuah jembatan. 
Jembatan tersebut bisa kita analogikan sebagai sebuah resource yang dibutuhkan oleh kedua antrian kendaraan. Karena keduanya saling membutuhkan jembatan dalam waktu yang sama, maka yang terjadi adalah saling menunggu. Alhasil, tak ada kemajuan pada proses antrian tersebut. Mau tidak mau, salah satu kendaraan harus ada yang mengalah atau dikalahkan.
Dalam programming situasi seperti itu juga umum terjadi. Misal ada 2 (dua) proses yang sama-sama menunggu proses satunya selesai, baru proses tersebut bisa selesai. Sama seperti ilustrasi mobil. Karena keduanya menunggu satu sama lain, tidak ada dari kedua proses tersebut yang akan selesai. Tugas selanjutnya pun tidak akan pernah bisa dijalankan.
Dalam sebuah sistem jaringan kerap jadi masalah pemicu deadlocks. Terlebih jika problem tersebut dibarengi race condition. Alhasil, apa yang terjadi? Muncullah kondisi-kondisi tak terduga yang membuat deadlocks jauh lebih rumit dibandingkan dengan masalah antrian proses.

Livelocks

Sama halnya dengan deadlocks, livelocks juga merupakan kondisi di mana sebuah proses tidak bisa melanjutkan tugasnya. Namun yang membedakannya adalah bahwa selama livelocks terjadi, keadaan dari aplikasi tetap bisa berubah. Walaupun perubahan keadaan tersebut menyebabkan proses berjalan dengan tidak semestinya.
20190503083939c23b6d87e2844f05506e39191440351d.gif
Pernahkah suatu ketika Anda berjalan di trotoar dan secara tidak langsung berhadapan dengan orang lain yang berjalan lurus tepat ke arah Anda? Ya, situasi ini pasti sering terjadi dan kadang bisa menjadi awkward moment
Secara spontan pasti Anda dan orang tersebut akan berusaha menghindari satu sama lain dengan bergerak ke satu sisi. Tak jarang, Anda bergerak ke kiri sementara orang di depan Anda bergerak ke kanan. 
Dan karena kalian berdua berjalan di arah yang berlawanan, tentunya malah menghalangi jalan masing-masing. Apakah yang akan terjadi selanjutnya? 
Bisa jadi, Anda akan bergerak ke kanan, dan pada saat yang sama orang itu bergerak ke kiri. Sekali lagi kalian tidak dapat melanjutkan perjalanan. Kejadian tersebut bisa terjadi berulang kali sampai waktu yang tidak diketahui.
Livelock bisa terjadi ketika beberapa proses bisa bereaksi saat mengalami deadlocks. Proses tersebut mencoba keluar dari situasi deadlock, namun waktu yang tidak tepat, menghalanginya.

Starvation

Starvation merupakan sebuah kondisi yang biasanya terjadi setelah deadlock. Kondisi deadlock sering kali menyebabkan sebuah proses kekurangan sumber daya sehingga mengalami starvation atau kelaparan. Pada kondisi seperti ini, thread tak dapat akses reguler ke sumber daya bersama dan membuat proses terhenti.
Selain deadlock, hal lain yang bisa mengakibatkan starvation adalah ketika terjadi kesalahan sistem. Akibatnya, ada ketimpangan dalam pembagian sumber daya. Sebagai contoh, ketika satu proses bisa mendapat sumber daya, namun tidak dengan proses lain. Biasanya, kesalahan seperti itu disebabkan oleh algoritma penjadwalan (scheduling algorithm) yang kurang tepat atau bisa juga karena resource leak atau kebocoran sumber daya.
Salah satu contoh kesalahan algoritma penjadwalan bisa dilihat ketika sebuah sistem multitasking dirancang dengan tidak baik. Apa akibatnya? Dua tugas pertama selalu beralilh, sementara yang ketiga tidak pernah berjalan. Tugas ketiga itulah yang bisa dikatakan menderita starvation.
Salah satu solusi untuk starvation adalah dengan menerapkan algoritma penjadwalan dengan antrian prioritas (process priority) dan juga menerapkan teknik aging atau penuaan. Aging merupakan sebuah teknik yang secara bertahap meningkatkan prioritas sebuah proses yang menunggu dalam sistem pada waktu yang cukup lama.

Race Conditions

Pada pembahasan sebelumnya, kita sudah menyinggung istilah race conditions. Kondisi seperti apakah itu? 
Race condition merupakan masalah umum pada concurrency, yaitu kondisi di mana terdapat banyak thread yang mengakses data yang dibagikan bersama dan mencoba mengubahnya secara bersamaan. 
Ini bisa terjadi ketika kode concurrent yang dituliskan untuk berjalan secara sekuensial. Artinya, sebuah perintah akan selalu dijalankan dalam urutan tertentu.
Ketika race condition terjadi, maka sistem bisa saja melakukan proses yang tidak semestinya. 
Pada saat itu bug akan muncul. Race condition dikenal sebagai masalah yang sulit untuk direproduksi dan di-debug, karena hasil akhirnya tidak deterministik dan tergantung pada waktu relatif dari thread yang menghalangi. Masalah yang muncul pada production bisa jadi tidak ditemukan pada saat debug
Oleh karena itu, lebih baik menghindari race condition dengan cara berhati-hati saat merencanakan sebuah sistem. Ini lebih baik daripada harus berusaha memperbaikinya setelah itu. Lebih repot. 
Contoh sederhana dari race condition bisa kita lihat pada aplikasi perbelanjaan online. Katakanlah Anda menemukan barang yang ingin Anda beli di sebuah toko online. Pada halaman deskripsi, tampil informasi bahwa stok barang hanya sisa 1 (satu). 
Lalu Anda langsung mengambil keputusan dengan menekan tombol beli. Pada saat yang sama, ada pengguna lain yang ternyata lebih dahulu membelinya. 
Kondisi seperti inilah yang mengakibatkan sebuah state atau keadaan, berubah tanpa Anda sadari. Jika sistem tidak dirancang sedemikian rupa, maka masalah tak terduga, bisa mengemuka

Mengenal Process, Thread, I/O-Bound di Kotlin


Process, Thread, I/O-Bound

Saat kita mulai menjalankan sebuah aplikasi, sebenarnya sistem operasi akan membuat sebuah proses, kemudian melampirkan sebuah thread padanya, dan setelah itu mulai menjalankan thread tersebut. Semua aktivitas tersebut akan bergantung pada perangkat yang digunakan, terutama perangkat perangkat input dan output (I/O).

Untuk bisa memahami dan juga menerapkan concurrency dengan benar, developer perlu mempelajari beberapa konsep dasar seperti ProcessThread dan I/O bound. Ketiga konsep tersebut saling berhubungan. Oleh karena itu, kita akan mencoba mengulas semua konsep tersebut. 

Process

Sebuah proses (process) merupakan bagian dari aplikasi yang sedang dijalankan. Setiap kali aplikasi dijalankan, maka saat itu juga proses dijalankan. Tergantung pada sistem operasi yang digunakan, suatu proses dapat terdiri dari beberapa thread yang menjalankan instruksi secara bersamaan.
Proses sering dianggap identik dengan program atau aplikasi. Namun, sebenarnya sebuah aplikasi adalah serangkaian proses yang saling bekerja sama. 
Untuk memfasilitasi komunikasi antar proses, sebagian besar sistem operasi mendukung sumber daya Inter Process Communication (IPC), seperti pipes dan soket. 
Biasanya sistem operasi modern sudah mendukung IPC. IPC digunakan tidak hanya untuk komunikasi antar proses pada sistem yang sama, melainkan juga untuk proses pada sistem yang berbeda.
Kita pasti mengenal dengan sebuah konsep yang bernama multitasking atau melakukan banyak tugas secara bersamaan. 
Saat multitasking, sebenarnya sistem operasi hanya beralih di antara berbagai proses dengan sangat cepat untuk memberikan kesan bahwa proses ini sedang dijalankan secara bersamaan. Sebaliknya, multiprocessing adalah metode untuk menggunakan lebih dari satu CPU dalam menjalankan tugas.
Dalam concurrency dan parallelism, multiprocessing mengacu pada pelaksanaan berbagai proses bersamaan dalam suatu sistem operasi, di mana setiap proses dieksekusi pada CPU terpisah. Oleh karena itu, multiprocessing merupakan tantangan tersendiri bagi developer dalam mengembangkan sebuah aplikasi.

Thread

Thread biasa dikenal dengan proses yang ringan. Membuat thread baru membutuhkan lebih sedikit sumber daya daripada membuat proses baru. Sebuah thread mencakup serangkaian instruksi untuk dijalankan oleh prosesor. 
Sehingga suatu proses akan memiliki setidaknya satu thread, yang dibuat untuk mengeksekusi fungsi utama dari sebuah aplikasi. Secara umum, thread tersebut disebut dengan main thread, dan life cycle dari sebuah proses akan terikat padanya.
Lebih dari satu thread dapat diimplementasikan dalam proses yang sama, dan dapat dieksekusi secara bersamaan. 
Perbedaan utama antara proses dan thread adalah bahwa thread biasanya merupakan komponen dari suatu proses. Selain itu, thread biasanya memungkinkan untuk berbagi sumber daya seperti memori dan data. Dimana 2 (dua) hal tersebut jarang dilakukan oleh sebuah proses.
Setiap thread dapat mengakses dan memodifikasi sumber daya yang terkandung dalam proses yang dilampirkan, tetapi juga memiliki penyimpanan lokal sendiri, yang biasa disebut dengan thread-local storage.
Hanya satu dari instruksi dalam sebuah thread yang dapat dijalankan pada waktu tertentu. Jadi, jika sebuah thread terblokir, instruksi lain dalam thread yang sama tidak akan dijalankan sampai pemblokiran tersebut berakhir. 
Namun demikian, banyak thread dapat dibuat untuk proses yang sama, dan mereka dapat berkomunikasi satu sama lain. Jadi diharapkan aplikasi tidak akan pernah memblokir thread yang dapat mempengaruhi pengalaman pengguna secara negatif.
Selain main thread, terdapat juga thread lain yang dikenal dengan UI thread. Thread ini berfungsi untuk memperbarui user interface (antarmuka) dan juga merespon aksi yang diberikan pada aplikasi. 
Jika thread ini diblokir, maka semua tugasnya akan terhalangi. Oleh karena itu, jangan sampai kita memblokir UI thread agar aplikasi tetap berjalan dengan semestinya.

I/O-Bound

Bottlenecks atau kemacetan adalah suatu hal yang penting untuk ditangani demi mengoptimalkan kinerja aplikasi. 
Bayangkan saja ketika Anda menggunakan sebuah aplikasi dan terjadi bottleneck di dalamnya, kesal sendiri kan? Perangkat input dan output biasanya sering mempengaruhi sebuah aplikasi mengalami bottlenecks
Sebagai contoh, memori yang terbatas, kecepatan prosesor, dsb. Lalu bagaimanakah cara untuk mengatasinya?
I/O-bound merupakan sebuah algoritma yang bergantung pada perangkat input atau output. Waktu untuk mengeksekusi sebuah I/O-bound tergantung pada kecepatan perangkat yang digunakan. 
Sebagai contoh, suatu algoritma untuk membaca dan menulis sebuah dokumen. Ini adalah operasi I/O yang akan tergantung pada kecepatan di mana berkas tersebut dapat diakses. Berkas yang disimpan pada SSD akan lebih cepat diakses dibandingkan berkas yang disimpan pada HDD.
Algoritma I/O-bound akan selalu menunggu sesuatu yang lain. Penantian terus-menerus ini memungkinkan perangkat yang hanya memiliki satu core untuk menggunakan prosesor demi tugas-tugas bermanfaat lainnya sambil menunggu. 
Jadi algoritma concurrent yang terikat dengan I/O akan melakukan hal yang sama, terlepas dari eksekusi yang terjadi -apakah paralel atau dalam satu core?
Seperti yang telah disebutkan sebelumnya, sangat penting untuk tidak memblokir UI thread dalam sebuah aplikasi. Oleh karena itu, saran kami terapkanlah concurrency jika aplikasi yang Anda kembangkan punya ketergantungan dengan perangkat I/O

Belajar Concurrency di Kotlin


Memasuki modul terakhir ini, kita akan mempelajari dasar concurrency pada Kotlin hingga alasan mengapa developer wajib mencoba Kotlin Coroutines

Concurrency merupakan sebuah topik yang cukup dalam. Jika dibahas secara menyeluruh mungkin tidak akan cukup di akademi ini. Maka dari itu, modul ini adalah pengantarnya. 
Diharapkan setelah memahami materi ini, pembaca dapat mengetahui gambaran apa itu concurrency dan perbedaannya pada Kotlin dibandingkan bahasa pemrograman lainnya.

Apa itu Concurrency?

Concurrency adalah beberapa proses yang terjadi secara bersamaan dalam suatu sistem. 
Concurrency merupakan suatu fenomena alami yang umum terjadi. Seperti halnya di dunia nyata, banyak kegiatan yang dilakukan pada waktu yang bersamaan. 
Dengan demikian, ketika kita ingin mengembangkan sebuah sistem untuk membantu kegiatan nyata, tentunya kita harus peduli dengan yang namanya concurrency.
20190429102422dcee0d5cb3bf291b3e9b6766a42a00e9.png

Arus lalu lintas bisa menjadi ilustrasi yang tepat untuk menggambarkan proses concurrency. 
Lalu lintas paralel di jalan yang berbeda hanya akan menimbulkan interaksi dan potensi masalah yang kecil antar kendaraan. Berbeda dengan lalu lintas padat yang biasanya kita jumpai pada persimpangan. 
Pastinya interaksi dan potensi masalah antar kendaraan akan lebih besar dan membutuhkan koordinasi yang lebih. Begitu pula dalam sebuah sistem aplikasi. 
Proses paralel yang tidak saling berinteraksi hanya akan menyebabkan masalah concurrency yang sederhana. Berbeda dengan proses yang saling berinteraksi bahkan berbagi sumber daya. Masalahnya tentu lebih kompleks. 
Penting untuk memperhatikan beberapa aspek saat berurusan dengan concurrency pada semua sistem. Aspek terpenting adalah mampu mendeteksi dan merespon peristiwa eksternal yang terjadi dalam urutan acak. Selain itu juga pastikan bahwa peristiwa tersebut dapat ditanggapi dalam interval waktu minimum yang diwajibkan.
Faktor eksternal sering jadi pendorong dibutuhkannya concurrency. 
Dalam kehidupan sehari-hari, banyak hal yang terjadi secara bersamaan dan harus ditangani secara real-time oleh sistem. Oleh karena itu sistem harus "reactive" alias dituntut untuk menanggapi proses yang dihasilkan secara eksternal. 
Proses tersebut dapat terjadi pada waktu dan urutan yang tak bisa ditentukan. Membangun sistem konvensional untuk mengatasi tugas tersebut, tentunya sangat rumit. 
Di dunia komputer, concurrency umumnya terkait dengan banyaknya core atau inti dari prosesor (CPU). Pada dasarnya, sebuah komputer memiliki mekanisme sequential atau berurutan untuk menjalankan tugas. Prosesor akan menjalankan satu perintah pada satu waktu. 
Dengan concurrency, kita bisa memanfaatkan kinerja prosesor dengan lebih optimal. Concurrency memungkinkan sebuah sistem untuk bisa dikendalikan dengan mudah. Sebagai contoh, suatu fungsi bisa dijalankan, dihentikan, ataupun dipengaruhi oleh fungsi lain yang jalan secara bersamaan

Concurrency vs Parallelism

Jika membahas concurrency, tentunya terkait dengan parallelism. Mungkin ada yang bingung mengenai perbedaan antara keduanya. 
Bagaimanapun, concurrency dan parallelism mempunyai arti yang mirip, yaitu 2 (dua) atau lebih proses yang berjalan pada satu waktu. Namun penting diketahui bahwa concurrency bukanlah parallelism.
Baik concurrency maupun parallelism, biasanya melibatkan pembuatan thread-thread untuk menjalankan tugas. Thread-thread tersebut bisa dijalankan di satu atau lebih core. Lalu apakah perbedaan dari keduanya?
Concurrency terjadi apabila terdapat 2 (dua) atau lebih proses yang tumpang tindih dalam satu waktu. Ini bisa terjadi jika ada 2 (dua) atau lebih thread yang sedang aktif. 
Dan jika thread tersebut dijalankan oleh komputer yang hanya memiliki 1 (satu) core, semua thread tidak akan dijalankan secara paralel. Concurrency memungkinkan sebuah komputer yang hanya memiliki 1 (satu) core tampak seakan mengerjakan banyak tugas sekaligus. Padahal sebenarnya tugas-tugas tersebut dilakukan secara bergantian.
Sedangkan parallelism terjadi ketika 2 (dua) proses dijalankan pada titik waktu yang sama persis. Parallelism bisa dilakukan jika terdapat 2 (dua) atau lebih thread dan komputer juga memiliki 2 (dua) core atau lebih. Sehingga setiap core dapat menjalankan perintah dari masing-masing thread secara bersamaan. 
Perhatikan beberapa ilustrasi berikut agar Anda lebih memahami perbedaan antara concurrency dan parallelism.
201904301035427929db65b1f2975cb30b8bce71a0234c.png

Bayangkan Anda di warung kopi dan melihat antrian seperti yang digambarkan pada ilustrasi di atas. 
Pelanggan dengan masing-masing pesanannya pasti senang jika sang barista bisa melayani dengan tepat dan cepat. Jika pada warung kopi tersebut hanya terdapat seorang barista, otomatis sang barista harus melakukan cara untuk melayani semua pelanggan sekaligus. Pada situasi seperti inilah concurrency dibutuhkan.
20190430104942d5d1552e67b77c9c2e022a3bab7d5cff.png
Karena hanya terdapat seorang barista, maka barista tersebut akan memproses lebih dari satu pesanan secara bersamaan. Hal ini sangat mungkin terjadi karena pembuatan kopi membutuhkan beberapa langkah, dan masing-masing langkah memakan waktu tersendiri. 
Misalnya menyiapkan air panas, menakar kopi, menyiapkan mesin espresso, dll. Barista akan membagi langkah-langkah tersebut sehingga seolah-olah ia bisa mengerjakan pesanan secara bersamaan.
Berbeda jika sang barista punya teman kerja untuk berbagi tugas. Pada situasi ini parallelism bisa dilakukan. Barista 1 hanya akan melayani beberapa pelanggan, dan sisanya akan dilayani oleh barista 2.
20190430105001ed576eb1a3f83e131c24da49381121d2.png
Karena kedua barista tersebut telah berbagi tugas, maka mereka akan bertindak secara paralel sehubungan dengan tugas yang lebih besar dalam melayani pelanggan. 
Bagaimanapun, selama jumlah barista belum sama dengan jumlah pelanggan, concurrency masih tetap diperlukan pada masing-masing barista tersebut. Artinya, parallelism dapat menimbulkan concurrency, tetapi concurrency bisa terjadi tanpa parallelism

Belajar Lengkap Generics di Kotlin


Pada modul sebelumnya kita sudah belajar tentang Kotlin sebagai bahasa pemrograman yang bisa diklasifikasikan ke dalam OOP beserta konsep-konsep yang terdapat didalamnya. 

Kali ini kita akan mempelajari tentang Generics, yaitu sebuah konsep yang memungkinkan suatu kelas atau interface menjadi tipe parameter yang dapat digunakan untuk berbagai macam tipe data.

Berkenalan Dengan Generics

Seperti yang kita ketahui, Kotlin termasuk dalam bahasa pemrograman statically typed. Ketika menambahkan variabel baru, maka secara otomatis tipe dari variabel tersebut dapat dikenali pada saat kompilasi. 
Secara umum generic merupakan konsep yang digunakan untuk menentukan tipe data yang akan kita gunakan. Pendeklarasiannya ditandai dengan tipe parameter. Kita juga bisa mengganti tipe parameter menjadi tipe yang lebih spesifik dengan menentukan instance dari tipe tersebut.
Sebelum kita mempelajari bagaimana cara kita mendeklarasikan sebuah kelas generic, ada baiknya jika kita melihat contoh bagaimana generic bekerja pada variabel dengan tipe List. Kita perlu menentukan tipe dari nilai yang bisa disimpan di dalam variabel List tersebut:

  1. val contributor = listOf<String>("jasoet", "alfian","nrohmen","dimas","widy")



Perhatikan kode di atas. Tipe parameter yang digunakan dalam pemanggilan fungsi listOf() adalah String maka nilai yang bisa kita masukkan adalah nilai dengan tipe String. 
Kita bisa menyederhanakannya dengan menghapus tipe parameter tersebut. Karena kompiler akan menetapkannya secara otomatis bahwa variabel yang kita buat adalah List.

  1. val contributor = listOf("alfian","nrohmen","dimans","widy")



Berbeda jika kita ingin membuat variabel list tanpa langsung menambahkan nilainya. Maka list tersebut tidak memiliki nilai yang bisa dijadikan acuan untuk kompiler menentukan tipe parameter. 
Alhasil, kita wajib menentukannya secara eksplisit seperti berikut:

  1. val contributor = listOf<String>()



Selain itu, kita juga bisa mendeklarasikan lebih dari satu tipe parameter untuk sebuah kelas. 
Contohnya adalah kelas Map yang memiliki dua tipe parameter yang digunakan sebagai key dan value. Kita bisa menentukannya dengan argumen tertentu, misalnya seperti berikut:

  1. val points = mapOf<String, Int>( "alfian" to 10 , "dimas" to 20 )


Mendeklarasikan Kelas Generic

Setelah mengetahui contoh bagaimana generic bekerja pada sebuah kelas, selanjutnya kita akan mempelajari bagaimana penerapan generic itu sendiri. Kita bisa menerapkannya dengan meletakkan tipe parameter ke dalam angle brackets (<>) seperti berikut:

  1. interface List<T>{

  2.     operator fun get(index: Int) : T

  3. }


Pada kode di atas, tipe parameter T bisa kita gunakan sebagai tipe reguler yang mengembalikan tipe dari sebuah fungsi.
Selanjutnya, jika kita mempunyai sebuah kelas yang mewarisi kelas atau interface generic, maka kita perlu menentukan tipe argumen sebagai tipe dasar dari parameter generic kelas tersebut. Parameternya bisa berupa tipe yang spesifik atau lainnya. Contohnya seperti berikut:

  1. class LongList : List<Long>{

  2.     override fun get(index: Int): Long {

  3.         /* .. */

  4.     }

  5. }

  6.  

  7. class ArrayList<T> : List<T>{

  8.     override fun get(index: Int): T {

  9.         /* .. */

  10.     }

  11. }



Pada kelas LongList di atas, Long digunakan sebagai tipe argumen untuk List, sehingga fungsi yang berada di dalamnya akan menggunakan Long sebagai tipe dasarnya. Berbeda dengan kelas ArrayList, di mana tipe argumen untuk kelas List menggunakan T
Dengan demikian ketika kita menggunakan kelas ArrayList, kita perlu menentukan tipe argumen dari kelas tersebut saat diinisialisasi.

  1. fun main() {

  2.     val longArrayList = ArrayList<Long>()

  3.     val firstLong = longArrayList.get(0)

  4. }

  5.  

  6. class ArrayList<T> : List<T> {

  7.     override fun get(index: Int): T {

  8.         /* .. */

  9.     }

  10. }

  11.  

  12. interface List<T> {

  13.     operator fun get(index: Int): T

  14. }



Yang perlu diperhatikan dari kelas ArrayList di atas adalah deklarasi dari tipe parameter T
Tipe parameter tersebut berbeda dengan yang ada pada kelas List, karena T adalah milik kelas ArrayList itu sendiri. Plus sebenarnya Anda pun bisa menggunakan selain misalnya seperti berikut:

  1. class ArrayList<T> : List<T> {

  2.     override fun get(index: Int): T {

  3.         /* .. */

  4.     }

  5. }

  6.  

  7. interface List<P> {

  8.     operator fun get(index: Int): P

  9. }



Mendeklarasikan Fungsi Generic

Setelah deklarasi generic pada sebuah kelas, apa berikutnya? Kita akan belajar bagaimana mendeklarasikan generic pada sebuah fungsi. 
Generic pada sebuah fungsi dibutuhkan ketika kita membuat sebuah fungsi yang berhubungan dengan List. Misalnya, list yang dapat digunakan untuk berbagai tipe dan tidak terpaku pada tipe tertentu. 
Fungsi generic memiliki tipe parameternya sendiri. Tipe argumen dari parameternya ditentukan ketika fungsi tersebut dipanggil. 
Cara mendeklarasikannya sedikit berbeda dengan kelas generic, Tipe parameter yang berada di dalam angle bracket harus ditempatkan sebelum nama dari fungsi yang kita tentukan. Sebagai contoh:

  1. fun <T> run(): T {

  2.     /*...*/

  3. }



Contoh penerapan fungsi generic bisa kita lihat pada deklarasi fungsi slice yang merupakan extensions function dari kelas List berikut:

  1. public fun <T> List<T>.slice(indices: Iterable<Int>): List<T> {

  2.     /*...*/

  3. }



Tipe parameter pada fungsi slice() di atas digunakan sebagai receiver dan return type. Ketika fungsi tersebut dipanggil dari sebuah List dengan tipe tertentu, kita bisa menentukan tipe argumennya secara spesifik seperti berikut:

  1. fun main() {

  2.     val numbers = (1..100).toList()

  3.     print(numbers.slice<Int>(1..10))

  4. }

  5.  

  6. /*

  7.    output : [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

  8. */



Seperti yang telah disebutkan sebelumnya, jika semua nilai yang berada di dalamnya memiliki tipe yang sama, kita bisa menyederhanakan. Caranya, hapus tipe parameter tersebut.

  1. fun main() {

  2.     val numbers = (1..100).toList()

  3.     print(numbers.slice(1..10))

  4. }

  5.  

  6. /*

  7.    output : [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

  8. */



Constraint Type Parameter

Dalam penerapan generic, kita bisa membatasi tipe apa saja yang dapat digunakan sebagai parameter. 
Untuk menentukkan batasan tersebut, bisa dengan menambahkan tanda titik dua (:) setelah tipe parameter yang kemudian diikuti oleh tipe yang akan dijadikan batasan. Contohnya seperti berikut:

  1. class ListNumber<T : Number> : List<T>{

  2.     override fun get(index: Int): T {

  3.         /* .. */

  4.     }

  5. }



Pada kode di atas kita telah menentukan Number sebagai batasan tipe argumen. 
Dengan begitu, kita hanya bisa memasukkan tipe argumen Number pada kelas ListNumber. Dan ketika kita memasukkan selain Number, maka akan terjadi eror seperti berikut:

  1. fun main() {

  2.     val numbers = ListNumber<Long>()

  3.     val numbers2 = ListNumber<Int>()

  4.     val numbers3 = ListNumber<String>() // error : Type argument is not within its bounds

  5. }

  6.  

  7. class ListNumber<T : Number> : List<T>{

  8.     override fun get(index: Int): T {

  9.         /* .. */

  10.     }

  11. }



Contoh lain dari constraint type parameter adalah seperti berikut:

  1. fun <T : Number> List<T>.sumNumber() : T {

  2.     /* .. */

  3. }



Fungsi di atas merupakan extensions function dari kelas List yang mempunyai tipe parameter. Sama seperti deklarasi generic pada sebuah fungsi, tipe parameter T pada fungsi tersebut juga akan digunakan sebagai receiver dan return type
Perbedaannya terletak pada cara memanggilnya. Fungsi tersebut akan tersedia pada variabel List dengan tipe argumen yang memiliki supertype Number.

  1. fun main() {

  2.     val numbers = listOf(1, 2, 3, 4, 5)

  3.     numbers.sumNumber()

  4.     val names = listOf("dicoding", "academy")

  5.     names.sumNumber() // error : inferred type String is not a subtype of Number

  6. }

  7.  

  8. fun <T : Number> List<T>.sumNumber() : T {

  9.     /* .. */

  10. }



Variance

Sebelumnya kita telah mempelajari bagaimana generic bekerja, bagaimana penerapannya, serta bagaimana kita bisa menentukan batasan tipe argumen yang bisa ditentukan terhadap tipe parameter. Selanjutnya kita akan belajar salah satu konsep dari generic yaitu variance.
Apa itu variance? Variance adalah konsep yang menggambarkan bagaimana sebuah tipe yang memiliki subtipe yang sama dan tipe argumen yang berbeda saling berkaitan satu sama lain. Variance dibutuhkan ketika kita ingin membuat kelas atau fungsi generic dengan batasan yang tidak akan mengganggu dalam penggunaannya. Sebagai contoh, mari kita buat beberapa kelas seperti berikut:

  1. abstract class Vehicle(wheel: Int)

  2. class Car(speed: Int) : Vehicle(4)

  3. class MotorCycle(speed: Int) : Vehicle(2)


Kemudian jalankan kode seperti berikut:

  1. fun main() {

  2.     val car = Car(200)

  3.     val motorCycle = MotorCycle(100)

  4.     var vehicle: Vehicle = car

  5.     vehicle = motorCycle

  6. }


Bisa kita perhatikan pada kode di atas, variabel car dan motorcycle merupakan subtipe dari Vehicle sehingga kita bisa melakukan assignment antar dua variabel tersebut. Maka seharusnya kode tersebut akan berhasil dikompilasi.
Selanjutnya mari kita masukkan salah satu kelas yang merupakan subtipe dari kelas Vehicle di atas kedalam generic list:

  1. fun main() {

  2.     val carList = listOf(Car(100) , Car(120))

  3.     val vehicleList = carList

  4. }


Dari contoh di atas, kita melihat bagaimana variance menggambarkan keterkaitan antara carList dan vehicleList di mana Car merupakan subtipe dari Vehicle
Nah, itu adalah contoh sederhana bagaimana variance bekerja. Lalu bagaimana cara membuat kelas generic yang memiliki variance? Caranya sama seperti ketika kita membuat generic kelas pada umumnya. Namun untuk tipe parameternya kita membutuhkan kata kunci out untuk covariant atau kunci in untuk contravariant.

Covariant

Contoh deklarasi generic dengan covariant bisa kita lihat saat kelas List pada Kotlin dideklarasikan seperti berikut:

  1. interface List<out E> : Collection<E> {

  2.     operator fun get(index: Int): E

  3. }


Ketika kita menandai sebuah tipe parameter dengan kata kunci out maka nilai dari tipe parameter tersebut hanya bisa diproduksi seperti menjadikanya sebagai return type. Serta tidak dapat dikonsumsi seperti menjadikannya sebagai tipe argumen untuk setiap fungsi di dalam kelas tersebut. 

Contravariant

Berbanding terbalik dengan saat kita menandainya dengan kata kunci out, bagaimana saat kita menandainya dengan dengan kata kunci in ?  Nilai dari tipe parameter tersebut bisa dikonsumsi dengan menjadikannya sebagai argumen untuk setiap fungsi yang ada di dalam kelas tersebut dan tidak untuk diproduksi. Contoh dari deklarasinya bisa kita lihat pada kelas Comparable pada Kotlin berikut:

  1. interface Comparable<in T> {

  2.     operator fun compareTo(other: T): Int

  3. }