Linh Tạ

final và const trong dart

finalconst đề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ùng const 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 finalconst. Hai ý quan trọng nhất bạn cần nhớ là:

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é ;)