Ghi đè hashCode và ==
Thuộc phần Lập trình hướng đối tượng trong hành trình Dart của Coddy. Bài học 71 trên 110.
Theo mặc định, hai đối tượng trong Dart chỉ bằng nhau khi chúng chính là cùng một instance trong bộ nhớ. Điều này có nghĩa là hai đối tượng có giá trị giống hệt nhau vẫn được xem là khác nhau:
class Point {
int x, y;
Point(this.x, this.y);
}
void main() {
var p1 = Point(3, 4);
var p2 = Point(3, 4);
print(p1 == p2); // false, hai instance khác nhau!
}Để so sánh các đối tượng dựa trên giá trị, bạn cần ghi đè cả toán tử == và hashCode. Bạn luôn phải ghi đè cả hai cùng nhau: nếu hai đối tượng bằng nhau, chúng phải có cùng mã băm:
class Point {
final int x, y;
Point(this.x, this.y);
@override
bool operator ==(Object other) {
if (other is! Point) return false;
return x == other.x && y == other.y;
}
@override
int get hashCode => Object.hash(x, y);
}
void main() {
var p1 = Point(3, 4);
var p2 = Point(3, 4);
print(p1 == p2); // true, các giá trị giống nhau!
}hashCode rất quan trọng với các cấu trúc tập hợp như Set và khóa của Map. Hàm hỗ trợ Object.hash() của Dart kết hợp nhiều giá trị thành một mã băm. Nếu không có hashCode phù hợp, các đối tượng của bạn sẽ không hoạt động chính xác trong các cấu trúc tập hợp dựa trên mã băm, ngay cả khi bạn đã ghi đè ==.
Thử thách
DễHãy xây dựng một hệ tọa độ để so sánh các điểm dựa trên giá trị thay vì địa chỉ bộ nhớ! Bạn sẽ tạo một lớp Coordinate triển khai chính xác phép so sánh bằng theo giá trị, giúp lớp hoạt động đúng với các phép kiểm tra bằng nhau và các tập hợp dựa trên mã băm như Set.
Bạn sẽ sắp xếp code thành hai tệp:
coordinate.dart: Tạo lớpCoordinatebiểu thị một điểm trong không gian 2D với các trườngfinal int xvàfinal int y. Ghi đè cả toán tử==lẫnhashCodeđể hai tọa độ có cùng giá trị x và y được xem là bằng nhau. DùngObject.hash()để tạo mã băm từ cả hai trường.main.dart: Nhập tệp tọa độ và minh họa cách phép so sánh bằng theo giá trị thay đổi hành vi so sánh đối tượng:- Tạo hai đối tượng
Coordinateriêng biệt, cả hai có x:5và y:10 - In ra xem chúng có bằng nhau không bằng cách dùng
== - In ra xem chúng có đồng nhất không (cùng một instance) bằng cách dùng
identical() - Tạo một
Set<Coordinate>rồi thêm cả hai tọa độ vào đó - In ra độ dài của tập hợp (kết quả sẽ là 1 vì chúng bằng nhau!)
- Tạo tọa độ thứ ba với x:
3và y:7, rồi thêm tọa độ đó vào tập hợp - In ra độ dài cuối cùng của tập hợp
- Tạo hai đối tượng
Thử thách này cho thấy vì sao phải luôn ghi đè hashCode và == cùng nhau. Set dùng mã băm để sắp xếp các phần tử, vì vậy nếu không có hashCode phù hợp, ngay cả các đối tượng bằng nhau cũng có thể được lưu thành các bản sao trùng lặp!
Đầu ra dự kiến:
true
false
1
2Tự làm thử
import 'coordinate.dart';
void main() {
// TODO: Tạo hai đối tượng Coordinate riêng biệt, cả hai đều có x: 5 và y: 10
// TODO: Dùng == để in ra kết quả kiểm tra chúng có bằng nhau không
// TODO: Dùng identical() để in ra kết quả kiểm tra chúng có cùng một instance không
// TODO: Tạo một Set<Coordinate> và thêm cả hai tọa độ vào đó
// TODO: In ra số phần tử của tập hợp (phải là 1 vì chúng bằng nhau!)
// TODO: Tạo tọa độ thứ ba với x: 3 và y: 7, rồi thêm vào tập hợp
// TODO: In ra số phần tử cuối cùng của tập hợp
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về lập trình hướng đối tượng
Tệp bên ngoàiThư viện và câu lệnh importGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcBiến thể hiệnKiến thức cơ bản về hàm khởi tạoÔn tập: Máy tính đơn giản4An toàn với giá trị null
Giới thiệu về an toàn với giá trị nullCó thể null và không thể nullToán tử ? và !Từ khóa late và an toàn với giá trị nullToán tử xử lý nullAn toàn với giá trị null trong lớpÔn tập: hệ thống hồ sơ người dùng7Lớp trừu tượng và interface
Lớp trừu tượngPhương thức trừu tượngInterface trong DartInterface ngầm địnhTriển khai và kế thừaNhiều interfaceÔn tập: Máy tính hình10Bộ sưu tập và kiểu tổng quát
Tổng quan về List, Set và MapBộ sưu tập an toàn về kiểuLớp tổng quátPhương thức tổng quátRàng buộc kiểu tổng quátIterable và IteratorÔn tập: Lưu trữ tổng quát2Hàm khởi tạo trong Dart
Hàm khởi tạo mặc địnhHàm khởi tạo có tênDanh sách khởi tạoHàm khởi tạo hằng sốHàm khởi tạo nhà máyHàm khởi tạo chuyển hướngÔn tập: Trình tạo hình dạng5Đóng gói
Thành viên công khai và riêng tưQuy ước tiền tố _Tính riêng tư ở cấp thư việnTìm hiểu sâu về getter và setterChe giấu thông tinÔn tập: hồ sơ học sinh8Mixin
Giới thiệu về MixinTạo MixinSử dụng nhiều MixinTừ khóa on trong MixinMixin và kế thừaMixin và giao diệnÔn tập: hệ thống động vật11Phương thức đặc biệt
Ghi đè toString()Ghi đè hashCode và ==Giao diện ComparablePhương thức call()Ghi đè noSuchMethodÔn tập: bộ sưu tập tùy chỉnh3Thuộc tính của lớp
Thành viên instance và thành viên tĩnhTrường final và constBiến latePhương thức và trường tĩnhGetter và setterÔn tập: trình quản lý tài khoản ngân hàng6Kế thừa
Kế thừa cơ bảnTừ khóa superGhi đè phương thứcChú thích @overrideTừ khóa lớp finalHàm khởi tạo và kế thừaÔn tập: phân cấp nhân viên9Đa hình
Khái niệm cơ bản về đa hìnhĐa hình qua giao diệnKiểm tra kiểu lúc chạyToán tử is và asTừ khóa hiệp biếnÔn tập: Bộ xử lý thanh toánTự luyện tập: Trình biên dịch Dart online