Masukkan Password
67 6.16 What is Generic in Dart OOP | Generic Class & Generic Function in Dart OOP | Learn Dart OOP
Pada materi ini, kata “DOT” di transkrip sebenarnya maksudnya adalah Dart. Topik yang dibahas adalah Generic pada Dart.
Kalau sebelumnya kita belajar abstract class, interface, mixin, dan factory constructor, sekarang kita masuk ke konsep yang sangat sering dipakai dalam Dart/Flutter: Generic.
1. Apa itu Generic?
Sederhananya:
Generic adalah cara membuat class atau function yang bisa bekerja dengan berbagai tipe data, tetapi tipe datanya tetap bisa ditentukan secara spesifik.
Contohnya kita punya sebuah class:
class Data {
int value;
Data(this.value);
}
Kita bisa membuat:
var data = Data(100);
Karena value bertipe int, kita hanya bisa memberikan angka integer.
Data(100); // ✅
Data(200); // ✅
Data("Hello"); // ❌
Data(2.5); // ❌
Masalahnya:
Bagaimana kalau kita ingin class Data bisa menyimpan:
intdoubleString- bahkan object dari class lain?
Kita sebenarnya bisa mengganti int menjadi dynamic.
class Data {
dynamic value;
Data(this.value);
}
Sekarang semuanya bisa:
Data(100);
Data(2.5);
Data("Hello");
Tetapi dynamic mempunyai kelemahan: tipe datanya menjadi kurang terkontrol.
Nah, Generic memberikan solusi yang lebih aman dan fleksibel.
2. Contoh Generic yang paling mudah: List
Sebenarnya tanpa sadar kita sudah sering menggunakan Generic di Dart.
Contohnya:
List<int> numbers = [10, 20, 30];
Artinya:
Saya mempunyai
Listyang hanya boleh berisiint.
Kita juga bisa:
List<String> names = ["Budi", "Andi", "Siti"];
Artinya:
Saya mempunyai
Listyang hanya boleh berisiString.
Atau:
List<double> prices = [10.5, 20.5, 30.5];
Artinya:
Saya mempunyai
Listyang hanya boleh berisidouble.
Jadi:
List<int>
List<String>
List<double>
Semuanya menggunakan class List yang sama.
Yang berbeda adalah tipe data yang dimasukkan ke dalam Generic-nya.
3. Kenapa List disebut Generic?
Perhatikan:
List<int>
Bagian:
<int>
adalah tipe yang diberikan kepada Generic.
Kita bisa membayangkan List seperti sebuah wadah.
List
│
▼
┌─────────────┐
│ 10 │
│ 20 │
│ 30 │
└─────────────┘
Kalau:
List<int>
maka wadah tersebut hanya menerima int.
Kalau:
List<String>
maka wadah tersebut hanya menerima String.
4. Membuat Generic Class sendiri
Sekarang kita buat class seperti contoh di materi.
Tanpa Generic:
class Data {
int value;
Data(this.value);
}
Kita membuat object:
var data = Data(100);
Kemudian:
print(data.value);
Output:
100
Tetapi kita tidak bisa:
var data = Data("Hello");
karena constructor membutuhkan int.
5. Menggunakan Generic T
Sekarang kita ubah class tersebut:
class Data<T> {
T value;
Data(this.value);
}
Nah, di sini muncul:
<T>
T adalah type parameter.
Artinya:
“Nanti tipe datanya akan ditentukan oleh orang yang menggunakan class ini.”
6. Apa arti T?
Ini bagian yang sangat penting.
T sebenarnya bukan tipe data konkret seperti:
int
String
double
T adalah placeholder untuk sebuah tipe data.
Bayangkan:
class Data<T>
seperti kita mengatakan:
“Saya belum tahu tipe datanya apa. Nanti kamu yang menentukan.”
Misalnya:
Data<int>
Maka:
T → int
Sehingga secara konsep:
class Data<int> {
int value;
}
Sedangkan kalau:
Data<String>
maka:
T → String
Sehingga secara konsep:
class Data<String> {
String value;
}
Jadi T seperti variabel untuk tipe data.
7. Contoh lengkap Generic Class
Kita punya:
class Data<T> {
T value;
Data(this.value);
}
Sekarang kita buat:
var data = Data<int>(100);
Di sini kita mengatakan:
Data menggunakan T = int
Maka value menjadi:
int value;
Kemudian:
print(data.value);
hasilnya:
100
8. Kalau ingin String?
Kita bisa membuat:
var data = Data<String>("Hello");
Sekarang:
T → String
Sehingga:
T value;
secara konsep menjadi:
String value;
Maka:
print(data.value);
menghasilkan:
Hello
9. Bisa juga double
Misalnya:
var data = Data<double>(2.5);
Sekarang:
T → double
Jadi:
value
bertipe:
double
10. Gambaran paling sederhana
Perhatikan pola ini:
Data<int>(100)
berarti:
Data
│
├── T = int
│
└── value = 100
Sedangkan:
Data<String>("Hello")
berarti:
Data
│
├── T = String
│
└── value = "Hello"
Class-nya tetap satu:
class Data<T>
Tetapi tipe datanya bisa berbeda-beda.
11. Kenapa tidak langsung pakai dynamic?
Pertanyaan ini penting.
Kita bisa saja:
class Data {
dynamic value;
Data(this.value);
}
Lalu:
Data(100);
Data("Hello");
Data(2.5);
Memang fleksibel.
Tetapi Generic memberikan informasi tipe yang lebih jelas.
Dengan:
Data<int>
kita tahu bahwa object tersebut bekerja dengan int.
Dengan:
Data<String>
kita tahu bahwa object tersebut bekerja dengan String.
Jadi Generic memberikan kombinasi:
fleksibel + tetap memiliki informasi tipe
12. Generic pada Function
Generic bukan hanya untuk class.
Kita juga bisa menggunakannya pada function.
Misalnya function biasa:
void myFunction(String value) {
print(value);
}
Function tersebut hanya menerima String.
myFunction("Hello"); // ✅
myFunction(100); // ❌
Kalau kita ingin function tersebut bisa menerima berbagai tipe data, kita bisa menggunakan Generic.
void myFunction<T>(T value) {
print(value);
}
Sekarang:
myFunction<String>("Hello");
atau:
myFunction<int>(100);
atau:
myFunction<double>(2.5);
Satu function bisa bekerja dengan banyak tipe.
13. Apa yang terjadi dengan T pada function?
Misalnya:
void myFunction<T>(T value) {
print(value);
}
Kemudian:
myFunction<int>(100);
Maka:
T = int
Sehingga parameter:
T value
secara konsep menjadi:
int value
Kalau:
myFunction<String>("Hello");
maka:
T = String
Sehingga:
T value
menjadi:
String value
14. Dart bisa mendeteksi tipe secara otomatis
Menariknya, kita sering tidak perlu menulis:
myFunction<String>("Hello");
Kita bisa cukup:
myFunction("Hello");
Dart bisa mengetahui bahwa:
"Hello" → String
Sehingga Dart bisa menyimpulkan:
T = String
Kalau:
myFunction(200);
Dart menyimpulkan:
T = int
Jadi ini:
myFunction("Hello");
secara konsep:
myFunction<String>("Hello")
Sedangkan:
myFunction(200);
secara konsep:
myFunction<int>(200)
15. Generic dengan Return Type
Generic juga bisa digunakan pada return type.
Misalnya:
T myFunction<T>(T value) {
return value;
}
Artinya:
Function ini menerima tipe
Tdan mengembalikan tipeTjuga.
Misalnya:
int result = myFunction(100);
Dart mengetahui:
T = int
Maka function tersebut mengembalikan:
int
Sehingga:
int result
valid.
16. Contoh dengan String
String result = myFunction("Hello");
Dart mengetahui:
T = String
Maka return type-nya:
String
Jadi valid.
17. Perhatikan pola ini
T myFunction<T>(T value) {
return value;
}
Ada dua T penting:
T myFunction<T>(T value)
^ ^ ^
| | |
return type parameter
type parameter
Lebih mudah dibaca:
T
│
├── T setelah nama function → mendeklarasikan Generic
│
├── T sebelum nama function → return type
│
└── T pada parameter → tipe parameter
Jadi:
T myFunction<T>(T value)
berarti:
“Saya punya function Generic bernama
myFunction. Function ini menerima data bertipeTdan mengembalikan data bertipeT.”
18. Generic bisa menggunakan nama lain selain T
Dalam materi disebutkan bahwa kita bisa menggunakan:
T
tetapi sebenarnya nama tersebut bukan keyword khusus.
Kita bisa saja:
class Data<A> {
A value;
Data(this.value);
}
Ini valid secara konsep.
Kemudian:
Data<int>(100);
Sekarang:
A → int
Bisa juga:
class Data<X> {
X value;
Data(this.value);
}
atau:
class Data<Type> {
Type value;
Data(this.value);
}
Namun, konvensi yang paling umum adalah menggunakan huruf seperti:
T
E
K
V
R
Untuk pemula, yang paling penting adalah:
Jangan menganggap
Tsebagai keyword.Thanyalah nama type parameter.
19. Kenapa biasanya menggunakan T?
Karena T biasanya dibaca sebagai:
Type
Misalnya:
class Data<T>
kita bisa membacanya:
Data dengan Type
T.
Tetapi Dart tidak mewajibkan namanya T.
20. Generic dengan lebih dari satu tipe
Sekarang kita masuk ke bagian terakhir dari materi.
Misalnya kita mempunyai function:
void myFunction<T>(T value) {
print(value);
}
Function tersebut hanya mempunyai satu Generic:
T
Bagaimana kalau kita ingin dua tipe?
Misalnya kita ingin menerima:
String
int
secara bersamaan.
Kita bisa:
void myFunction<T, B>(T value, B value2) {
print(value);
print(value2);
}
Sekarang ada dua type parameter:
<T, B>
21. Contoh penggunaannya
myFunction("Hello", 200);
Dart akan mendeteksi:
T = String
B = int
Jadi secara konsep:
void myFunction<String, int>(
String value,
int value2
)
22. Bisa tipe yang berbeda lagi
Misalnya:
myFunction(100, true);
Maka:
T = int
B = bool
Atau:
myFunction(2.5, "Hello");
maka:
T = double
B = String
Jadi satu function dapat bekerja dengan banyak kombinasi tipe.
23. Generic dengan dua parameter pada Class
Hal yang sama juga bisa dilakukan pada class.
Misalnya kita membuat pasangan data:
class Pair<T, U> {
T first;
U second;
Pair(this.first, this.second);
}
Kemudian:
var pair = Pair<String, int>("Age", 20);
Artinya:
T = String
U = int
Sehingga:
first → String
second → int
Kita bisa:
print(pair.first);
print(pair.second);
Output:
Age
20
Ini pola yang sangat berguna ketika sebuah object menyimpan dua jenis data yang berbeda.
24. Generic dengan batasan / restriction
Sekarang bagian yang cukup penting dari materi.
Generic memang membuat tipe menjadi fleksibel.
Tetapi kadang kita tidak ingin semua tipe diterima.
Contohnya kita ingin hanya menerima:
int
double
Kita tahu bahwa Dart mempunyai tipe:
num
num adalah superclass untuk angka seperti:
int
double
Maka kita bisa memberikan batasan:
T extends num
Artinya:
Tharus merupakan tipe yang sesuai dengan batasnum.
25. Generic class dengan extends num
Contohnya:
class Data<T extends num> {
T value;
Data(this.value);
}
Sekarang T tidak boleh sembarangan.
Yang memenuhi batas num bisa digunakan.
Contohnya:
Data<int>(100);
✅ boleh.
Karena:
int → num
Kemudian:
Data<double>(2.5);
✅ boleh.
Karena:
double → num
Tetapi:
Data<String>("Hello");
❌ tidak boleh.
Karena:
String
↓
bukan turunan/implementasi yang memenuhi bound num
26. Apa maksud extends di sini?
Ini perlu diperhatikan karena berbeda dengan penggunaan extends yang sebelumnya kita pelajari dalam inheritance.
Pada:
class Student extends Person
extends berarti:
Student mewarisi Person.
Sedangkan pada:
class Data<T extends num>
extends berarti:
T harus memenuhi batas/constraint berupa
num.
Jadi konteksnya berbeda.
Dalam Generic, extends digunakan untuk membuat constraint/bound.
27. Kenapa kita membutuhkan restriction?
Bayangkan kita punya function:
T add<T>(T a, T b) {
// ...
}
Kita ingin melakukan operasi matematika.
Masalahnya:
T bisa String
T bisa bool
T bisa object lain
Padahal kita mungkin hanya ingin angka.
Maka kita bisa memberikan batas:
T extends num
Dengan begitu Generic tidak lagi bebas menerima semua tipe.
28. Generic function dengan restriction
Contoh sesuai materi:
void myFunction<T extends num>(T value) {
print(value);
}
Sekarang:
myFunction(100);
✅
myFunction(5.5);
✅
Tetapi:
myFunction("Hello");
❌
Karena String tidak memenuhi:
T extends num
29. Analogi Generic yang sangat mudah
Bayangkan kita mempunyai kotak.
Tanpa Generic:
┌─────────────┐
│ BOX │
│ │
│ ??? │
└─────────────┘
Kita tidak terlalu tahu isi kotaknya.
Dengan Generic:
┌─────────────┐
│ BOX<int> │
│ │
│ 100 │
└─────────────┘
Artinya:
Kotak ini khusus untuk
int.
Atau:
┌─────────────┐
│ BOX<String> │
│ │
│ "Hello" │
└─────────────┘
Artinya:
Kotak ini khusus untuk
String.
Generic memungkinkan kita membuat desain kotak yang sama, tetapi jenis isinya dapat ditentukan.
30. Bedanya Generic dengan dynamic
Ini sangat penting untuk dipahami.
dynamic
class Data {
dynamic value;
Data(this.value);
}
Bisa menerima apa saja:
Data(100);
Data("Hello");
Data(true);
Data(2.5);
Tetapi tipe datanya tidak dibatasi secara jelas.
Generic
class Data<T> {
T value;
Data(this.value);
}
Kita menentukan:
Data<int>(100);
atau:
Data<String>("Hello");
Jadi Generic lebih terstruktur.
Sederhananya:
dynamic
↓
"terserah, tipe apa saja"
Generic
↓
"kamu tentukan tipenya, dan tipe itu akan digunakan secara konsisten"
31. Generic bukan berarti satu object bisa berubah-ubah tipe
Ini kesalahan pemahaman yang cukup umum.
Misalnya:
var data = Data<int>(100);
Di sini:
T = int
Object tersebut tidak kemudian berubah menjadi String hanya karena kita ingin memasukkan String.
Generic menentukan tipe untuk penggunaan object tersebut.
Kalau ingin String, buat penggunaan Generic yang lain:
var dataInt = Data<int>(100);
var dataString = Data<String>("Hello");
Sekarang:
dataInt
T = int
dataString
T = String
32. Hubungannya dengan List
Sekarang kita bisa melihat mengapa Generic sangat penting di Dart.
Ketika kita menulis:
List<String> names = [];
sebenarnya kita mengatakan:
List
↓
T = String
Sehingga list tersebut khusus menyimpan String.
Contoh:
names.add("Budi");
names.add("Andi");
✅
Sedangkan:
names.add(100);
❌
Karena:
List<String>
berarti elemen-elemennya harus String.
33. Generic sangat berguna dalam Flutter/API
Ini salah satu alasan kamu akan sering menemukan Generic ketika belajar Flutter.
Misalnya kita mendapatkan data dari API.
Kita mungkin mempunyai model:
class Post {
String title;
Post(this.title);
}
Kemudian API mengembalikan banyak Post.
Kita bisa mempunyai:
List<Post> posts = [];
Artinya:
Saya mempunyai List yang isinya object
Post.
Bukan:
List<dynamic>
yang kurang spesifik.
Dengan:
List<Post>
kode kita menjadi lebih mudah dipahami dan lebih aman dari kesalahan tipe.
34. Contoh konsep API + Generic
Misalnya server memberikan:
Post 1
Post 2
Post 3
Kita ubah menjadi:
List<Post>
Secara konsep:
API
│
▼
Data mentah
│
▼
Post.fromJson()
│
▼
Post object
│
▼
List<Post>
Jadi Generic dan model class sering bekerja bersama-sama dalam aplikasi Flutter.
35. Generic juga bisa digunakan dengan object class lain
Misalnya:
class User {
String name;
User(this.name);
}
Kita bisa:
Data<User> data = Data(User("Budi"));
Sekarang:
T = User
Jadi Generic tidak hanya untuk tipe bawaan seperti:
int
double
String
bool
Tetapi juga bisa untuk:
User
Post
Product
Order
Student
dan class lainnya.
36. Contoh lengkap
Mari kita gabungkan semuanya.
class Data<T> {
T value;
Data(this.value);
T getValue() {
return value;
}
}
Kita bisa:
var number = Data<int>(100);
print(number.value);
print(number.getValue());
Output:
100
100
Atau:
var text = Data<String>("Hello");
print(text.value);
print(text.getValue());
Output:
Hello
Hello
37. Generic dengan restriction
Sekarang:
class NumberData<T extends num> {
T value;
NumberData(this.value);
}
Kita bisa:
var a = NumberData<int>(100);
var b = NumberData<double>(2.5);
Tetapi:
var c = NumberData<String>("Hello");
akan error.
Karena:
T harus memenuhi num
38. Generic dengan dua tipe
Misalnya:
class Pair<T, U> {
T first;
U second;
Pair(this.first, this.second);
}
Penggunaan:
var data = Pair<String, int>("Age", 20);
Maka:
T = String
U = int
Kita mendapatkan:
data.first
bertipe:
String
dan:
data.second
bertipe:
int
39. Bagian yang paling penting untuk dipahami
Kalau kamu masih merasa Generic agak abstrak, fokus pada kalimat ini:
Generic adalah cara membuat kode yang bisa digunakan untuk banyak tipe data tanpa kehilangan informasi tentang tipe data tersebut.
Misalnya kita punya:
class Data<T> {
T value;
Data(this.value);
}
T berarti:
“Tipe data akan ditentukan nanti.”
Kemudian:
Data<int>
berarti:
“Sekarang T adalah int.”
Sedangkan:
Data<String>
berarti:
“Sekarang T adalah String.”
40. Pola yang perlu kamu hafalkan
Generic Class
class Data<T> {
T value;
Data(this.value);
}
Penggunaan:
Data<int>(100);
Data<String>("Hello");
Generic Function
void printData<T>(T value) {
print(value);
}
Penggunaan:
printData<int>(100);
printData<String>("Hello");
atau sering cukup:
printData(100);
printData("Hello");
karena Dart dapat melakukan type inference.
Generic Return Type
T getData<T>(T value) {
return value;
}
Generic dengan batasan
class Data<T extends num> {
T value;
Data(this.value);
}
atau:
void printNumber<T extends num>(T value) {
print(value);
}
Sekarang T hanya boleh memenuhi batas num.
Banyak Generic Type
class Pair<T, U> {
T first;
U second;
Pair(this.first, this.second);
}
41. Perbandingan keseluruhan
| Konsep | Artinya |
|---|---|
T | Placeholder untuk tipe |
Data<T> | Generic class |
myFunction<T>() | Generic function |
Data<int> | T menjadi int |
Data<String> | T menjadi String |
T extends num | T dibatasi oleh num |
<T, U> | Memiliki dua type parameter |
List<int> | List dengan elemen int |
List<String> | List dengan elemen String |
dynamic | Bisa berisi berbagai tipe tanpa generic constraint |
42. Generic vs extends vs implements vs with
Karena sebelumnya kamu belajar konsep-konsep tersebut, jangan sampai tertukar.
Generic
Data<int>
Fokusnya:
Tipe data apa yang digunakan?
extends
class Student extends Person
Fokusnya:
Class ini mewarisi class apa?
implements
class Student implements Person
Fokusnya:
Class ini memenuhi contract/interface apa?
with
class Student with CanRun
Fokusnya:
Class ini menggunakan behavior/mixin apa?
Jadi:
<T> → tipe data
extends → inheritance / atau bound pada generic
implements→ contract/interface
with → mixin
43. Kesimpulan besar
Materi ini sebenarnya ingin mengajarkan satu ide utama:
GENERIC
│
┌─────────┴─────────┐
│ │
CLASS FUNCTION
│ │
Data<T> myFunction<T>()
│ │
┌─────┼─────┐ ┌────┼────┐
│ │ │ │ │ │
int String double int String double
Generic memungkinkan kita membuat satu desain kode yang dapat digunakan dengan berbagai tipe data.
Contohnya:
class Data<T> {
T value;
Data(this.value);
}
Bisa digunakan sebagai:
Data<int>(100);
Data<String>("Hello");
Data<double>(2.5);
Tanpa harus membuat tiga class berbeda seperti:
IntData
StringData
DoubleData
Itulah kekuatan Generic.
🧠 Cara paling gampang mengingatnya
Bayangkan T sebagai kotak kosong untuk tipe:
<T>
Awalnya:
T = ?
Ketika kita menulis:
Data<int>
menjadi:
T = int
Ketika:
Data<String>
menjadi:
T = String
Dan kalau:
T extends num
artinya:
T = ?
↓
harus memenuhi aturan num
Jadi kalau ada satu kalimat yang perlu kamu ingat dari materi ini:
Generic = membuat class/function yang fleksibel terhadap tipe data, tetapi tipe tersebut tetap bisa ditentukan dan dikontrol.