Masukkan Password
35 Key points of classes & objects in Dart | Dart Tutorial #34
Transkrip ini membahas 10 poin penting tentang class dan object di Dart. Materinya cukup fundamental karena konsep class/object akan sering sekali kamu temui ketika masuk lebih jauh ke Flutter.
Aku akan jelaskan dari dasar, lalu setiap bagian aku beri contoh supaya tidak sekadar menghafal.
Catatan penting: ada beberapa bagian transkrip yang penyampaiannya kurang tepat secara teknis, terutama tentang hash code, private member, overloading, dan object/reference. Aku akan luruskan sambil menjelaskannya.
1. Apa saja yang bisa ada di dalam sebuah class?
Secara sederhana, class bisa kita anggap sebagai cetakan/blueprint untuk membuat object.
Misalnya:
class Student {
String? name;
int? age;
Student(this.name, this.age);
void showData() {
print("Name: $name");
print("Age: $age");
}
}
Di dalam class Student terdapat beberapa hal:
1. Field / variable
String? name;
int? age;
Ini adalah data yang dimiliki object.
Disebut juga:
- field
- instance variable
- property/member variable
2. Constructor
Student(this.name, this.age);
Constructor digunakan ketika kita membuat object.
Contoh:
Student student = Student("Budi", 20);
3. Method
void showData() {
print("Name: $name");
print("Age: $age");
}
Method adalah function yang berada di dalam class.
4. Getter dan setter
Contohnya:
class Student {
String _name = "";
String get name {
return _name;
}
set name(String value) {
_name = value;
}
}
Getter:
student.name
digunakan untuk membaca data.
Setter:
student.name = "Budi";
digunakan untuk mengubah data.
Instance vs static
Field dan method juga bisa berupa instance atau static.
Instance member
Milik masing-masing object.
class Student {
String name = "";
}
Kalau:
Student s1 = Student();
Student s2 = Student();
s1.name = "Budi";
s2.name = "Andi";
Maka:
s1 → name = Budi
s2 → name = Andi
Masing-masing object punya data sendiri.
Static member
Milik class, bukan masing-masing object.
class Student {
static int totalStudent = 0;
}
Aksesnya:
print(Student.totalStudent);
bukan:
student.totalStudent;
2. Di mana kita boleh menulis kode di dalam class?
Ini bagian yang sering membingungkan pemula.
Misalnya:
class Student {
String name = "Budi";
print("Hello"); // ERROR
}
Ini tidak boleh.
Kenapa?
Karena isi langsung dari body class digunakan untuk mendeklarasikan member, seperti:
class Student {
String name = "Budi";
int age = 20;
Student() {
print("Constructor");
}
void showData() {
print("Hello");
}
}
Kode yang berupa statement seperti:
print("Hello");
if (...) {}
for (...) {}
harus berada di dalam konteks yang memang menjalankan statement tersebut, misalnya:
- constructor
- method
- getter
- setter
- initializer expression tertentu
Contoh:
class Student {
String name = "Budi";
Student() {
print("Object dibuat");
}
void showData() {
print("Hello");
print(name);
}
}
Ini valid.
3. Apakah di dalam class perlu object untuk mengakses member?
Ini adalah konsep instance member vs akses dari luar class.
Perhatikan:
class Person {
String name = "Sam";
int age = 24;
void showData() {
print(name);
print(age);
}
}
Di dalam showData() kita bisa langsung menulis:
print(name);
print(age);
Tidak perlu:
print(person.name);
Kenapa?
Karena showData() sedang dijalankan dalam konteks object Person tertentu.
Misalnya:
Person p1 = Person();
Kemudian:
p1.showData();
Saat showData() dijalankan, Dart tahu bahwa name dan age yang dimaksud adalah milik object yang sedang menjalankan method tersebut.
Secara konsep:
p1
↓
Person
├── name = "Sam"
├── age = 24
└── showData()
Jadi di dalam method:
print(name);
kurang lebih berarti:
print(this.name);
this mengacu pada object yang sedang digunakan.
Tetapi kalau dari luar class?
Misalnya:
void main() {
print(name);
}
Ini error.
Karena main() bukan bagian dari class Person.
Kita harus membuat object:
void main() {
Person p = Person();
print(p.name);
}
Sekarang valid.
Jadi pola sederhananya:
DARI DALAM CLASS
→ bisa langsung mengakses instance member
DARI LUAR CLASS
→ gunakan object/reference
Contoh:
class Person {
String name = "Sam";
void showData() {
print(name); // OK
print(this.name); // OK
}
}
void main() {
Person p = Person();
print(p.name); // OK
p.showData(); // OK
}
4. Setiap object punya instance variable masing-masing
Ini konsep yang sangat penting.
Misalnya:
class Person {
String name = "";
int age = 0;
void showData() {
print("$name - $age");
}
}
Kemudian:
void main() {
Person p1 = Person();
Person p2 = Person();
p1.name = "Sam";
p1.age = 24;
p2.name = "Mark";
p2.age = 32;
p1.showData();
p2.showData();
}
Output:
Sam - 24
Mark - 32
Kenapa p1.name dan p2.name bisa berbeda?
Karena mereka adalah dua object yang berbeda.
Secara konsep:
p1
↓
┌────────────────┐
│ name = "Sam" │
│ age = 24 │
└────────────────┘
p2
↓
┌────────────────┐
│ name = "Mark" │
│ age = 32 │
└────────────────┘
Jadi:
p1.name
dan:
p2.name
merupakan field pada object yang berbeda.
5. Apakah setiap object punya salinan method sendiri?
Di sini transkrip mengatakan bahwa secara konsep method tidak disalin sebagai data instance untuk setiap object dan dibagikan oleh semua object.
Untuk pemahaman pemula, kamu bisa membayangkannya seperti ini:
Person class
│
└── showData()
↑
digunakan oleh
↙ ↘
p1 p2
Sedangkan data instance:
p1
├── name
└── age
p2
├── name
└── age
Jadi yang penting kamu pahami:
Instance field
Data dapat berbeda untuk setiap object.
p1.name = "Sam";
p2.name = "Mark";
Method
Method didefinisikan pada class dan dapat dipanggil oleh banyak instance.
p1.showData();
p2.showData();
Jangan terlalu fokus pada detail implementasi memori seperti “satu copy method secara fisik”, karena itu bukan model bahasa Dart yang perlu kamu hafalkan sebagai programmer pemula. Yang penting adalah field instance punya state masing-masing, sedangkan method adalah behavior yang didefinisikan pada class.
6. Object itu unik, tetapi hashCode bukan alamat memori
Ini salah satu bagian transkrip yang perlu dikoreksi.
Transkrip mengatakan:
hash code basically an address in memory
Ini tidak tepat.
Di Dart:
object.hashCode
adalah sebuah nilai integer yang berkaitan dengan identitas/hash object.
Contoh:
class Person {}
void main() {
Person p1 = Person();
Person p2 = Person();
print(p1.hashCode);
print(p2.hashCode);
}
Kamu biasanya akan melihat angka berbeda.
Misalnya:
123456789
987654321
Tetapi jangan menganggap angka tersebut sebagai alamat RAM object.
hashCode bukan berarti:
alamat memori = hashCode
Itu adalah penyederhanaan yang menyesatkan.
Lalu bagaimana membuktikan dua reference menunjuk object yang sama?
Gunakan operator:
==
Tetapi ada catatan: == di Dart bisa dioverride oleh class.
Untuk memeriksa apakah dua reference menunjuk object instance yang sama, Dart menyediakan:
identical(a, b)
Contoh:
class Person {}
void main() {
Person p1 = Person();
Person p2 = p1;
print(identical(p1, p2));
}
Output:
true
Karena:
p1 ─────┐
↓
Person object
↑
p2 ─────┘
Sedangkan:
Person p1 = Person();
Person p2 = Person();
print(identical(p1, p2));
Output:
false
Karena:
p1 → Person object #1
p2 → Person object #2
Ini cara yang lebih tepat untuk memahami identitas object daripada menggunakan hashCode.
7. Dart tidak menggunakan public, private, dan protected seperti Java/C++
Dart mempunyai mekanisme visibility yang cukup sederhana.
Tidak ada keyword seperti:
public
private
protected
seperti yang mungkin kamu kenal dari bahasa lain.
Dart menggunakan underscore _.
Contoh:
class Test {
int x = 10;
int _y = 20;
void method1() {
print("Method 1");
}
void _method2() {
print("Method 2");
}
}
Secara konsep:
x → public
_y → private
method1() → public
_method2() → private
Tetapi ada hal yang sangat penting:
Private di Dart bersifat library-private
Ini berbeda dengan konsep “private hanya di dalam class” yang mungkin kamu kenal dari Java.
Misalnya file:
student.dart
berisi:
class Student {
String name = "Budi";
String _secret = "123";
}
Di file/library yang sama, _secret dapat diakses.
Tetapi dari library/file lain yang mengimpor student.dart, _secret tidak dapat diakses secara normal.
Jadi lebih tepat mengatakan:
Member yang diawali
_bersifat private terhadap library tempat member tersebut didefinisikan.
Bukan sekadar “private terhadap class”.
8. Nama member tidak boleh sembarangan sama
Transkrip membahas beberapa aturan penamaan.
Misalnya:
class Demo {
String Demo = "Hello";
}
Ini bermasalah karena Demo digunakan sebagai nama class sekaligus nama member.
Nama class:
Demo
dan constructor:
Demo()
memang bisa menggunakan nama tersebut karena constructor memang memiliki nama yang sama dengan class.
Contoh:
class Demo {
Demo() {
print("Constructor");
}
}
Valid.
Bagaimana dengan field dan method?
Tidak boleh ada dua member dengan nama yang sama dalam class.
Misalnya:
class Test {
String name = "Budi";
void name() {
print("Hello");
}
}
Ini tidak boleh.
Karena name sudah digunakan sebagai field dan kemudian digunakan lagi sebagai method.
Getter dan setter juga punya namespace nama yang sama
Misalnya:
class Student {
String _name = "";
String get name => _name;
}
Getter ini memperkenalkan property bernama:
name
Kalau kemudian kamu mencoba membuat member lain dengan nama yang bentrok, akan bermasalah.
Contoh sederhananya:
class Student {
String name = "";
String get name => "Budi";
}
Ini jelas konflik.
Jadi jangan berpikir:
field
method
getter
setter
selalu memiliki nama yang benar-benar terpisah.
Ada aturan namespace/member declaration yang membuat nama-nama tersebut tidak bisa digunakan sembarangan secara bersamaan.
9. Object dan reference adalah dua hal berbeda
Ini konsep yang sangat penting.
Perhatikan:
Person p1 = Person();
Ada beberapa hal yang terjadi.
Person
Ini adalah type/class.
Person()
Ini adalah pemanggilan constructor yang menghasilkan object.
p1
Ini adalah variable yang menyimpan reference ke object tersebut.
Jadi:
Person p1 = Person();
│ │ │
│ │ └── membuat/menghasilkan object
│ └──────── reference variable
└─────────────── type
Contoh paling penting
Person p1 = Person();
Person p2 = p1;
Banyak pemula salah memahami baris kedua.
Mereka mengira:
Person p2 = p1;
membuat object kedua.
Tidak.
Object kedua tidak dibuat.
Yang terjadi:
┌────────────────┐
p1 ──────────→│ Person object │
│ │
p2 ──────────→│ │
└────────────────┘
Ada:
- 1 object
- 2 reference
p1 dan p2 sama-sama menunjuk object yang sama.
Buktinya
class Person {
String name = "";
}
void main() {
Person p1 = Person();
p1.name = "Sam";
Person p2 = p1;
print(p2.name);
}
Output:
Sam
Karena p1 dan p2 menunjuk object yang sama.
Kalau:
p2.name = "Mark";
maka:
print(p1.name);
akan menghasilkan:
Mark
Karena object-nya sama.
10. Dart tidak mendukung function/method overloading seperti Java/C++
Ini juga bagian penting.
Di beberapa bahasa seperti Java, kita bisa membuat:
void demo(int x)
void demo(int x, int y)
Nama method sama, tetapi parameter berbeda.
Ini disebut method overloading.
Namun Dart tidak mendukung traditional method overloading seperti itu.
Misalnya kamu mencoba:
class Test {
void demo(int x) {
print(x);
}
void demo(int x, int y) {
print(x + y);
}
}
Ini tidak diperbolehkan.
Karena dalam satu class tidak bisa ada dua method demo seperti itu hanya dengan membedakan parameter.
Lalu bagaimana biasanya menangani kebutuhan seperti itu?
Dart punya beberapa fitur yang bisa digunakan.
Optional positional parameter
void demo(int x, [int? y]) {
if (y == null) {
print(x);
} else {
print(x + y);
}
}
Bisa dipanggil:
demo(10);
atau:
demo(10, 20);
Named parameter
void demo(int x, {int? y}) {
print(y == null ? x : x + y);
}
Pemanggilan:
demo(10);
atau:
demo(10, y: 20);
Default value
void demo(int x, {int y = 0}) {
print(x + y);
}
Bisa:
demo(10);
hasil:
10
atau:
demo(10, y: 20);
hasil:
30
Jadi Dart lebih mengandalkan optional/named parameters, default values, dan fitur bahasa lainnya daripada traditional overloading.
11. Semua class Dart pada akhirnya memiliki Object
Transkrip mengatakan:
each and every class in Dart inherits the Object class
Untuk pemahaman dasar, ini benar.
Misalnya:
class Person {}
Kamu tidak menulis:
class Person extends Object {}
tetapi class tersebut tetap memiliki Object sebagai superclass paling atas dalam hierarchy-nya.
Karena itu object memiliki member dasar seperti:
hashCode
runtimeType
toString()
noSuchMethod()
Contoh:
class Person {}
void main() {
Person p = Person();
print(p.hashCode);
print(p.runtimeType);
print(p.toString());
}
Misalnya output bisa seperti:
123456789
Person
Instance of 'Person'
12. Kenapa kita bisa memanggil toString()?
Karena class kita pada akhirnya berada dalam hierarchy Object.
Secara sederhana:
Object
↑
Person
↑
object p
Jadi object Person mendapatkan behavior dasar dari Object.
Kita juga bisa menulis:
class Person extends Object {
}
Tetapi biasanya tidak perlu.
Cukup:
class Person {
}
Lebih bersih dan idiomatis.
13. Gambaran besar seluruh materi
Sekarang kita gabungkan semuanya.
Misalnya:
class Student {
String _name;
int age;
Student(this._name, this.age);
void showData() {
print("Name: $_name");
print("Age: $age");
}
String get name {
return _name;
}
set name(String value) {
_name = value;
}
static int totalStudent = 0;
}
Class tersebut mempunyai:
Student
│
├── _name
│ └── instance field/private
│
├── age
│ └── instance field/public
│
├── Student(...)
│ └── constructor
│
├── showData()
│ └── instance method
│
├── name getter
│ └── getter
│
├── name setter
│ └── setter
│
└── totalStudent
└── static field
Kemudian:
void main() {
Student s1 = Student("Budi", 20);
Student s2 = Student("Andi", 22);
s1.showData();
s2.showData();
}
Ada dua object:
s1 ─────→ Student Object #1
_name = Budi
age = 20
s2 ─────→ Student Object #2
_name = Andi
age = 22
Tetapi keduanya berasal dari blueprint/class yang sama:
Student class
/ \
/ \
↓ ↓
Student Object 1 Student Object 2
14. Ringkasan 10 poin versi mudah
Kalau materi video ini mau kamu hafalkan, jangan hafalkan seluruh penjelasan panjangnya. Pegang 10 inti berikut:
1. Class memiliki member
Member bisa berupa:
field
method
constructor
getter
setter
static member
2. Kode executable tidak ditulis sembarangan langsung di body class
Misalnya:
class Person {
print("Hello"); // ❌
}
Statement harus berada dalam konteks yang sesuai seperti method/constructor/getter/setter.
3. Instance member berkaitan dengan object
p1.name
p1.showData()
Dari luar class, kita biasanya menggunakan reference/object untuk mengakses instance member.
4. Setiap object memiliki state instance sendiri
p1 → name = Budi
p2 → name = Andi
Data bisa berbeda.
5. Jangan anggap hashCode sebagai alamat memori
Gunakan:
identical(a, b)
untuk mengecek apakah dua reference menunjuk object instance yang sama.
6. _ membuat member private terhadap library
String _name;
bukan berarti private hanya terhadap class, tetapi library-private.
7. Nama member tidak boleh bentrok sembarangan
Misalnya field dan method tidak bisa sama-sama menggunakan nama yang sama dalam konteks yang konflik.
Constructor justru memang menggunakan nama class:
class Person {
Person() {}
}
8. Object dan reference berbeda
Person p1 = Person();
Person() → object dibuat.
p1 → reference yang mengacu pada object.
Dan:
Person p2 = p1;
tidak membuat object baru.
9. Dart tidak mendukung traditional method overloading
Tidak boleh:
void demo(int x) {}
void demo(int x, int y) {} // ❌
Gunakan optional/named parameter dan default value sebagai alternatif.
10. Class Dart berada di bawah Object
class Person {}
memiliki hierarchy dasar:
Object
↑
Person
Itulah sebabnya object memiliki hal seperti:
p.hashCode
p.runtimeType
p.toString()
Yang paling penting untuk Flutter
Materi ini sebenarnya sedang menyiapkan kamu untuk memahami Flutter.
Ketika nanti kamu menulis:
Text("Hello")
atau:
Container(
width: 100,
height: 100,
)
atau:
Scaffold(
appBar: AppBar(),
)
kamu akan sering berhadapan dengan class, constructor, object, instance member, named parameter, getter/setter, inheritance, dan konsep OOP lainnya.
Misalnya:
Container(
width: 100,
height: 100,
child: Text("Hello"),
)
Cara berpikir sederhananya:
Container → class
Container(...) → constructor invocation
hasilnya → object/widget instance
Text → class
Text("Hello") → constructor invocation
hasilnya → object/widget instance
Jadi pemahaman class → constructor → object → reference → member ini benar-benar merupakan fondasi penting sebelum kamu semakin jauh masuk ke Flutter.