final và const trong dart
final
và const
đều được dùng để ngăn giá trị của biến thay đổi sau khi khởi tạo. Tuy nhiên, đó là điểm giống nhau duy nhất giữa chúng, và ngay cả điểm chung này cũng có một vài khác biệt. Cùng mình tìm hiểu nhé.
Khởi tạo
Bạn không phải cung cấp giá trị khởi tạo cho các biến final
, nhưng điều này là bắt buộc với const
//Compiler sẽ báo lỗi và yêu cầu gán giá trị cho name
const name; //❌
//Code chạy thành công
final name; //✅
Có sự khác nhau này là bởi const
được dùng cho các biến có giá trị lúc compile. Nói cách khác, khi code được biên dịch để tạo ra app, hệ thống đã phải biết chắc chắn giá trị của biến const
.
Đối với biến final
, chỉ cần nó được gán giá trị trước lần sử dụng đầu tiên thì sẽ không có bất cứ vấn đề gì
final name;
//Sẽ xảy ra lỗi vì name chưa có giá trị
print(name); //❌
//Chạy thành công vì name đã được gán giá trị trước lần sử dụng đầu tiên
name = "Linh Ta"
print(name); //✅
Cập nhật
1. const
Biến const
và object gán cho nó không được phép thay đổi giá trị
//1. Không thể gán lại giá trị cho language
const language = "Dart";
language = "Javascript"; //❌
//2. Không thể thay đổi giá trị của object được gán cho biến const
const numbers = [0];
numbers.add(1); //❌
//Trong ví dụ 2, hệ thống sẽ tự động biên dịch thành
//chính vì vậy bạn không thể thay đổi list [0]
const numbers = const [0];
//3.
const primaryColors = [Color("red", [255, 0, 0])];
//được biên dịch thành
const primaryColors = const [const Color("red", const [255, 0, 0])];
Khi bạn sử dụng const
, biến đó, object gán cho nó, và mọi thứ liên quan đến object đó đều phải là const
. Quy tắc này áp dụng một cách đệ quy với toàn bộ thuộc tính của object và các thuộc tính con cháu của các thuộc tính đó. Bạn có thể hiểu const
như huy hiệu thuần chủng vậy. Để được coi là người việt nam thuần chủng thì bạn phải mang 100% dòng máu việt. Điều này có nghĩa bố mẹ, ông bà, cụ kị, và tổ tiên bạn cũng phải như vậy.
2. final
Trái với const
, object gán cho biến final
vẫn được phép cập nhật giá trị. Tính chất này của final
rất giống keyword let
trong swift, const
trong javascript, hay final
trong java
final numbers = [0];
numbers.add(1); //✅ numbers = [0,1]
Kết hợp với class
1. final
Khi khai báo các trường trong class, final
hoạt động như bình thường
class Animal {
final type; //✅
int age; //✅
Animal(this.type, { required this.age }); //✅
}
2. const
Khác với final
, const
phải được dùng với static
và phải có giá trị khởi tạo
class Human {
//Phải được dùng với static
const String name; //❌
//Phải có giá trị khởi tạo
static const int age; //❌
static const canThink = true; //✅
}
Mọi object đều chỉ được khởi tạo khi app chạy và điều này vi phạm quy tắc ở phần đầu bài viết rằng hệ thống phải biết trước giá trị của const
tại thời điểm biên dịch code. Tuy nhiên, khi kết hợp với static
, const
trở thành thuộc tính của class và được “coi như” có giá trị lúc biên dịch. Mình dùng “coi như” bởi các biến static
sẽ chỉ khởi tạo khi sử dụng lần đầu tiên. Đây là một cách tối ưu hiệu năng của dart vì nếu một biến static
không được dùng thì bạn sẽ không phải trả phí tạo ra nó.
Nếu mọi biến trong class đều là final
hoặc const
, bạn có thể đánh dấu hàm khởi tạo với const
(được gọi là constant constructor) để tạo ra object bất biến có tính chất như đã nêu ở phần Cập nhật
class Capital {
final String name;
const Capital(this.name);
}
class Country {
final Capital capital;
final String name;
const Country(this.name, { required this.capital });
}
const hanoi = const Capital("Hanoi");
const vietnam = const Country("Viet Nam", capital: hanoi);
Nếu các object có giá trị giống nhau được khởi tạo nhiều lần, constant constructor không sinh ra object mới mà chỉ tạo ra một object duy nhất, được gọi là canonical instance. Việc biết chắc chắn rằng object và các thuộc tính của nó không thể thay đổi giúp dart cache và tái sử dụng object để tối ưu hệ thống.
var vietnam = const Country("Viet Nam", capital: hanoi);
var vietnam2 = const Country("Viet Nam", capital: hanoi);
var vietnam3 = const Country("Viet Nam", capital: hanoi);
//Cả 3 object vietnam, vietnam2, vietnam3 đều là một
assert(identical(vietnam, vietnam2)); //✅
assert(identical(vietnam2, vietnam3)); //✅
//Nếu biến là const và object có constant constructor
//có thể lược giản thành
const vietnam = Country("Viet Nam", capital: hanoi);
Flutter khuyến khích dùng const
widget bất cứ khi nào có thể bởi khi hàm build
được gọi, các const
widget sẽ không bị rebuild, giúp cải thiện hiệu năng đáng kể
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: const Text("Widget Text này sẽ không build lại")
),
);
}
Lưu ý: constant constructor sẽ được dùng như hàm khởi tạo bình thường nếu biến không phải là
const
và bạn không trực tiếp dùngconst
khi khởi tạo object
var vietnam = const Country("Viet Nam", capital: hanoi);
//fakeVietnam không được khởi tạo bằng constant constructor
var fakeVietnam = Country("Viet Nam", capital: hanoi);
//Hai object vietnam và fakeVietname không còn là một
assert(identical(vietnam, fakeVietnam)); //❌
//Nếu muốn dùng constant construct hãy trực tiếp thêm const cho hàm khởi tạo
var fakeVietnam = const Country("Viet Nam", capital: hanoi);
//hoặc thêm const cho biến
const fakeVietnam = Country("Viet Nam", capital: hanoi);
assert(identical(vietnam, fakeVietnam)); //✅
Tổng kết
Qua bài viết ngày hôm nay, chúng ta đã phân biệt được sự khác nhau giữa final
và const
. Hai ý quan trọng nhất bạn cần nhớ là:
const
phải có giá trị khi biên dịch code, trong khifinal
cần có giá trị trước lần sử dụng đầu tiên- Với mỗi tập giá trị, constant constructor chỉ tạo ra một object duy nhất và tái sử dụng nó cho các object khởi tạo sau nếu chúng có chung giá trị với object ban đầu. Việc này mang lại rất nhiều lợi ích về mặt hiệu năng
Mong rằng bài viết này sẽ có ích cho bạn khi làm việc với Flutter, và đừng quên rằng hãy luôn ưu tiên const
nếu có thể nhé ;)