null
เบื้องต้นค่า null
ที่ใช้ใน Dart เป็นค่าจาก Null class ซึ่ง class นี้จะพิเศษกว่า class อื่น ๆ คือ มันไม่ได้ implement Object class เหมือน class อื่น ๆ และเป็นค่าที่พิเศษตรงที่ตัวแปรที่ไม่ได้ทำการเก็บค่า Instance จะเริ่มต้นเป็นค่า null
เสมอ และถ้าหากไปเรียกใช้โดยที่ยังเป็นค่า null
ก็จะเกิด Exception error ได้
DateTime? myBirthday;
print(myBirthday); // output → null
print(myBirthday!.add(Duration(days: 1))); // exception error → Null check operator used on a `null` value
null
ถือว่าเป็นค่าที่ทำให้เกิดความปั่นป่วนในโลกคอมพิวเตอร์ OOP เป็นอย่างมาก เนื่องจากหากผู้เขียนโปรแกรมลืมตรวจสอบค่าตัวแปรว่าเป็น null
หรือไม่ และเข้าถึงตัวแปรดังกล่าว ก็จะทำให้แอปหยุดทำงาน เกิด NullPointerException error และในยุคเริ่มต้นของภาษา OOP เช่น Java C++ ต่างก็ไม่มีวิธีในการตรวจสอบค่า null
ที่ผู้เขียนโปรแกรมลืมกำหนดค่า Instance ของ Object ที่จะใช้งาน แต่ภาษายุคใหม่ ๆ ได้ทำการแก้ไขปัญหาแล้ว รวมถึง Dart 3.0 ที่บังคับใช้ความปลอดภัยกับค่า null
แบบเต็มรูปแบบ
null
null
ผ่านตัว compiler optimizationsสำหรับข้อ 2 นั้น ตัว compiler จะพยายามแจ้งเตือนผู้เขียนโปรแกรมว่าอาจมีจุดไหนบ้างที่อาจไปเรียกใช้งาน null
โดยไม่ตั้งใจ และให้เขียนคำสั่งตรวจสอบเพิ่มเติมก่อนใช้งาน ถึงแม้จะไม่สามารถแก้ไขปัญหาได้ 100% แต่ก็แก้ไข bugs ที่เกิดจากความไม่ตั้งใจได้เยอะมาก
null
ใน Dart การประกาศ type annotation จะเป็นแบบ non-null คือเก็บค่า null
ไม่ได้ ยกเว้นว่าผู้เขียนโปรแกรมจะระบุว่าต้องการเก็บค่า null
ด้วย โดยการใส่เครื่องหมาย ?
ต่อท้าย
DateTime? nullDate; // nullDate can contain `null` value
print(nullDate); // output → null
// comiler error: The method 'toLocal' can't be unconditionally invoked because the receiver can be 'null'.
print(nullDate.toLocal());
DateTime myBirthday; // myBirthday can't contain `null` value
// compiler error: Error: Non-nullable variable 'myBirthday' must be assigned before it can be used.
print(myBirthday);
จากตัวอย่าง หากพยายามจะใช้งานตัวแปรที่ยังเป็น null
เพราะในโปรแกรมไม่มีคำสั่งสร้าง Instance ของ DateTime class ให้กับตัวแปร nullDate จะทำให้ compile error ส่วนตัวแปรรที่ผูู้ใช้งานประกาศ type annotation โดยไม่ใส่ ? ต่อท้าย ตัว compiler จะแจ้งปัญหาก่อนที่จะใช้งานตัวแปรว่าไม่มีการกำหนดค่าก่อนใช้งาน
DateTime? nullDate; // nullDate can contain `null` value
print(nullDate); // output → null
nullDate = DateTime(2024);
print(nullDate.toLocal());
nullDate = null;
if(nullDate != null) {
print(nullDate.toLocal());
}
จากตัวอย่าง หากตัวแปรถูกกำหนดค่า null
ตัว compiler จะแจ้งปัญหาว่ามีการใช้งาน null
แต่หากมีการเขียนตรวจสอบค่า null
ก่อน ก็จะปล่อยให้ run ได้เพราะเห็นว่ามีการตรวจสอบและไม่ได้ใช้งาน null
แล้ว
null
เนื่องจากค่า null
เป็น instance จาก Null class ดังนั้น ค่า null
ทุกที่จะมาจาก instance เดียวกัน สามารถใช้ ==
และ !=
ในการตรวจสอบได้
DateTime? getBirthDay() {
return null;
}
void main() {
DateTime? nullDate; // nullDate can contain `null` value
nullDate = getBirthDay();
if (nullDate == null) {
print("nullDate is null");
} else {
print(nullDate);
}
}
Dart มี operator หลายตัวที่ช่วยจัดการเรื่องการตรวจสอบค่า null
ทำให้การเขียนโปรแกรมสั้นลง และอ่านได้รวดเร็วยิ่งขึ้น
null
ด้วย ??
ทำงานเหมือน ?:
แต่ย่อรูปให้ลดลง ใช้ตรวจสอบค่าของตัวแปรว่าเป็น null
หรือไม่ ถ้าไม่ใช่ null
จะคืนค่าตัวมันเองกลับไป แต่ถ้าเป็น null
จะคืนค่าที่อยู่ทางขวาให้
int? getX() {
return null;
}
void main() {
int? x = getX();
int y = x ?? 0;
int z = (x != null) ? x : 0; // same as ??
print(y); // output → 0
print(z); // output → 0
}
null
ให้กำหนดค่าใหม่เป็นค่าทางขวาแทน ด้วย ??=
ใช้ตรวจสอบค่าของตัวแปรว่าเป็น null
หรือไม่ ถ้าเป็น null
จะกำหนดค่าใหม่ที่อยู่ทางขวาแทน
int? getX() {
return null;
}
void main() {
int? x = getX();
x ??= 0;
x = x ?? 0; // same as ??=
x = (x != null) ? x : 0; // same as ??=
print(x);
}
?.
?.
ใช้ตรวจสอบว่า object ที่จะใช้คำสั่ง properties หรือ methods เป็นค่า null
หรือไม่
null
จะทำการเรียกใช้ properties หรือ methods ตามปกติnull
จะไม่มีการเรียกคำสั่ง properties หรือ methods ดังกล่าว และคืนค่า null
กลับไปList? getList() {
return null;
}
void main() {
List? myList = getList();
myList?.add(0); // do nothing because myList == null
if (myList != null) { // same as ?.
myList.add(0);
}
}
null
หรือไม่ด้วย ...?
การใช้ ...
สำหรับแตก List ใน List อื่น หากต้องการทดสอบว่า List ที่จะแตกเป็น null หรือไม่ ให้เพิ่ม ?
ต่อท้าย ...
หาก List เป็น null
ก็จะข้ามไป
List? x, y = [1, 2, 3];
// compile error → A nullable expression can't be used in a spread.
List z = [...x, ...y];
// use null-aware spreads
List ok = [...?x, ...?y];
print(ok); // output → [1, 2, 3]
null
ด้วย !.
(Casting away nullability)!.
ใช้สำหรับยืนยันว่า ตัวแปรไม่มีค่าเป็น null
แน่ ๆ (Casting away nullability) เป็นการบอก compiler ให้ข้ามการตรวจสอบ null
ไป การใช้ !.
ต้องเข้าใจว่าเป็นการยืนยันของผู้เขียนโปรแกรมเท่านั้น แต่หากสุดท้ายมันมีค่าเป็น null
ก็จะอาจเกิด Exception error หรือกลายเป็น bugs ในแอปก็ได้
List? getList() {
return null;
}
void main() {
List? myList = getList();
myList!.add(0); // ignore `null` test, compile pass but runtime error
}
ในการใช้งานทั่วไป !.
จะถูกใช้บ่อยในการอ้างถึง key ใน Map เพราะว่ามันสามารถคืนค่า null
กลับมาได้หาก key นั้นไม่มีใน Map
Map<String, String> test = {'a': 'Ant', 'b': 'Boy'};
// compile error → The method 'toUpperCase' can't be unconditionally invoked because the receiver can be 'null'
print(test['a'].toUpperCase());
// I'm sure key 'a' exists.
print(test['a']!.toUpperCase()); // output → ANT
// I'm sure key 'c' exists, but I was wrong
print(test['c']!.toUpperCase()); // exception error → Null check operator used on a `null` value
เนื่องจากข้อจำกัดของ compiler ในบางครั้งถึงเขียนคำสั่งตรวจค่า null
ไปแล้ว แต่โปรแกรมอาจมีรูปแบบซับซ้อนจนตัว compiler ไม่สามารถตรวจสอบได้ เพื่อข้ามข้อจำกัดดังกล่าวจึงจำเป็นต้องมี !.
ไว้บอก compiler ว่าสามารถข้ามไปได้ โปรดใช้ !.
อย่างระมัดระวัง
null
คนที่ออกแบบให้มีการใช้ null
เพื่อใช้บอกถึงตัวแปรที่ไม่มีการอ้างไปที่ Instance ใด ๆ คือ Sir Charles Antony Richard Hoare โดยในปี ค.ศ.2009 เขาได้ไปพูดในงาน software conference และได้ขอโทษที่การออกแบบของเขา ทำให้เกิดความเสียหายระดับพันล้านดอลลาร์ จากการที่เขาได้ออกแบบในภาษา ALGOL W ให้ใช้ Null และมีการนำเอา Null มาใช้หลังจากนั้นในภาษาอื่น ๆ เรื่อยมา ซึ่งความเสียหายที่เกิดขึ้น ก็น่าจะเป็นปัญหาที่แอปหยุดทำงานเพราะ NullPointerException ที่หลุดจากการตรวจสอบของผู้พัฒนาโปรแกรม รวมถึงต้องเสียเวลามาแก้ไขปัญหาที่เกิดขึ้น การเขียนโปรแกรมที่ต้องมาคอยเขียนคำสั่งตรวจสอบ Null จำนวนมาก
ส่วนตัวคิดว่าปัญหาจริง ๆ มันน่าจะมาจากการจัดการค่า null ในโปรแกรมที่เขียนขึ้นเป็นหน้าที่ของคนเขียนโปรแกรม แทนที่จะเป็นตัว compiler ที่จะช่วยเหลือตรงนี้ เพราะยิ่งโปรแกรมมีความซับซ้อนมากขึ้น จำนวนการสร้าง class มากขึ้น มันยากมากที่จะให้คนเขียนโปรแกรมจัดการเรื่องตรวจสอบ null ก่อนใช้ได้ครบทุกจุด (คิดดูว่าต้องมีคำสั่ง if เพิ่มขึ้นเยอะแค่ไหน) และการออกแบบภาษาโปรแกรมที่ไม่สามารถระบุเป็น non-null ได้นั้นเอง