Hiểu sâu về Enum trong Java
Giới thiệu
enum
là một tính năng được giới thiệu trong JDK5.
Trong Java, kiểu được đánh dấu bằng từ khóa enum
được gọi là kiểu enum. Cú pháp như sau:
Lợi ích của enum: Có thể tổ chức các hằng số lại với nhau và quản lý chúng một cách thống nhất.
Ứng dụng điển hình của enum: Mã lỗi, máy trạng thái, v.v.
Bản chất của enum
Lớp java.lang.Enum
được khai báo như sau:
Tạo một tệp ColorEn.java với nội dung sau:
Chạy lệnh javac ColorEn.java
để tạo tệp ColorEn.class.
Sau đó, chạy lệnh javap ColorEn.class
để xem kết quả:
💡 Giải thích:
Từ ví dụ trên, có thể thấy:
Bản chất của enum là một lớp con của
java.lang.Enum
.Mặc dù enum có vẻ như là một kiểu dữ liệu mới, nhưng thực tế, enum là một lớp có giới hạn và có các phương thức riêng của nó. Vì lớp đặc biệt này được đánh dấu là
final
, nên không thể kế thừa từ lớp khác.Các giá trị enum được định nghĩa mặc định sẽ được đánh dấu là
public static final
, từ các từ khóa này, có thể thấy rằng giá trị enum thực chất là hằng số tĩnh.
Phương thức của Enum
Trong enum, cung cấp một số phương thức cơ bản:
values()
: Trả về một mảng các phần tử của enum, và các phần tử trong mảng này được sắp xếp theo thứ tự khai báo trong enum.name()
: Trả về tên của phần tử.ordinal()
: Trả về chỉ số của phần tử khi khai báo, bắt đầu từ 0.getDeclaringClass()
: Trả về kiểu enum mà phần tử thuộc về.equals()
: Kiểm tra xem có phải là cùng một đối tượng hay không.
Có thể sử dụng ==
để so sánh các phần tử của enum.
Ngoài ra, java.lang.Enum
cũng triển khai các giao diện Comparable
và Serializable
, do đó cung cấp phương thức compareTo()
.
Ví dụ: Hiển thị các phương thức cơ bản của enum
Kết quả
=========== Print all Color ===========
RED ordinal: 0
GREEN ordinal: 1
BLUE ordinal: 2
=========== Print all Size ===========
BIG ordinal: 0
MIDDLE ordinal: 1
SMALL ordinal: 2
green name(): GREEN
green getDeclaringClass(): class org.zp.javase.enumeration.EnumDemo$Color
green hashCode(): 460141958
green compareTo Color.GREEN: 0
green equals Color.GREEN: true
green equals Size.MIDDLE: false
green equals 1: false
green == Color.BLUE: false
Tính năng của Enum
Tính năng của enum có thể tóm tắt trong một câu:
Ngoại trừ không thể kế thừa, cơ bản có thể coi enum như một lớp thông thường.
Tuy nhiên, câu này cần được phân tách để hiểu rõ hơn, hãy đi vào chi tiết.
Tính năng cơ bản
Nếu enum không định nghĩa phương thức, có thể thêm dấu phẩy, dấu chấm phẩy hoặc không thêm gì sau phần tử cuối cùng.
Nếu enum không định nghĩa phương thức, giá trị enum mặc định là các số nguyên tuần tự bắt đầu từ 0. Ví dụ với kiểu enum Color, các hằng số enum sẽ là RED: 0, GREEN: 1, BLUE: 2
.
Enum có thể thêm phương thức
Trong phần giới thiệu, đã đề cập đến rằng giá trị enum mặc định là các số nguyên tuần tự bắt đầu từ 0. Vậy câu hỏi đặt ra là: làm thế nào để gán giá trị enum một cách rõ ràng.
(1) Java không cho phép sử dụng =
để gán giá trị cho hằng số enum
Nếu bạn đã làm quen với C/C++, bạn chắc chắn sẽ tự nhiên nghĩ đến dấu =
để gán giá trị cho hằng số enum. Trong ngôn ngữ C/C++, có thể sử dụng dấu =
để gán giá trị cho hằng số enum; nhưng rất tiếc, Java không cho phép sử dụng dấu =
để gán giá trị cho hằng số enum.
Ví dụ: Khai báo enum trong ngôn ngữ C/C++
(2) Enum có thể thêm phương thức thông thường, phương thức tĩnh, phương thức trừu tượng, phương thức khởi tạo
Mặc dù Java không cho phép gán giá trị trực tiếp cho các phần tử enum, nhưng nó có một giải pháp tốt hơn: thêm phương thức cho enum để gián tiếp gán giá trị.
Khi tạo enum, có thể thêm nhiều loại phương thức cho nó, thậm chí có thể thêm phương thức khởi tạo.
Lưu ý một chi tiết: Nếu muốn định nghĩa phương thức cho enum, thì phải thêm dấu chấm phẩy vào cuối phần tử enum cuối cùng. Ngoài ra, trong enum, phải định nghĩa phần tử trước, không thể định nghĩa trường hoặc phương thức trước phần tử. Nếu không, trình biên dịch sẽ báo lỗi.
Ví dụ: Hiển thị đầy đủ cách định nghĩa phương thức thông thường, phương thức tĩnh, phương thức trừu tượng, phương thức khởi tạo trong enum
Chú ý: Ví dụ trên chỉ để minh họa, không phải là một ví dụ tốt. Ví dụ chính xác được trình bày trong phần Mã lỗi.
Enum có thể triển khai giao diện
Enum có thể triển khai giao diện giống như một lớp thông thường.
Cũng giống như lớp thông thường, enum có thể triển khai giao diện. Điều này cho phép ràng buộc các phương thức của enum.
Enum không thể kế thừa
Enum không thể kế thừa từ một lớp khác, và tất nhiên cũng không thể kế thừa từ một enum khác.
Vì thực tế, enum là một lớp con của lớp java.lang.Enum
, và Java không hỗ trợ đa kế thừa, nên enum không thể kế thừa từ lớp khác và tất nhiên cũng không thể kế thừa từ một enum khác.
Ứng dụng của Enum
Tổ chức hằng số
Trước JDK5, trong Java, việc định nghĩa hằng số thường được thực hiện bằng cách sử dụng cú pháp public static final TYPE a;
. Với enum, bạn có thể tổ chức các hằng số có mối quan hệ với nhau, làm cho mã nguồn dễ đọc hơn, an toàn hơn và cũng có thể sử dụng các phương thức được cung cấp bởi enum.
Ba cách khai báo sau đây tương đương:
Switch với trạng thái
Chúng ta thường sử dụng câu lệnh switch để viết máy trạng thái. Từ JDK7 trở đi, câu lệnh switch đã hỗ trợ tham số kiểu int
, char
, String
, enum
. So với các kiểu tham số khác, việc sử dụng enum trong câu lệnh switch có mã nguồn dễ đọc hơn.
Mã lỗi
Enum thường được sử dụng để định nghĩa mã lỗi của chương trình. Dưới đây là một ví dụ đơn giản:
Tổ chức enum
Có thể sử dụng giao diện hoặc lớp để tổ chức các enum có cùng loại, nhưng thường thì sử dụng giao diện để tổ chức.
Lý do là: Java tự động thêm từ khóa public static
cho kiểu enum khi biên dịch; Java tự động thêm từ khóa static
cho kiểu enum khi biên dịch. Bạn có thể thấy sự khác biệt chưa? Đúng rồi, điều đó có nghĩa là nếu bạn tổ chức enum trong một lớp, nếu bạn không đặt quyền truy cập là public
, thì chỉ có thể truy cập trong gói hiện tại.
Ví dụ: Tổ chức enum trong giao diện
Ví dụ: Tổ chức enum trong lớp
Ví dụ này có cùng hiệu quả với ví dụ trước.
Enum chiến lược
Effective Java trình bày một loại enum chiến lược. Enum này sử dụng enum lồng nhau để phân loại các hằng số enum.
Phương pháp này không ngắn gọn như câu lệnh switch, nhưng nó an toàn hơn và linh hoạt hơn.
Ví dụ: Một ví dụ về enum chiến lược từ Effective Java
Kiểm tra
Enum triển khai Singleton Pattern
Singleton Pattern là mẫu thiết kế phổ biến nhất.
Singleton Pattern có vấn đề an toàn đối với luồng trong môi trường đa luồng.
Để giải quyết vấn đề an toàn đối với luồng, có một số phương pháp truyền thống:
- Khởi tạo sẵn (Eager initialization)
- Lười biếng (Lazy initialization) với synchronize và kiểm tra kép
- Tận dụng cơ chế tải tĩnh của Java
So với các phương pháp trên, việc sử dụng enum cũng có thể triển khai Singleton và còn đơn giản hơn:
Công cụ của lớp Enum
Trong Java, có hai lớp tiện ích để làm việc với enum - EnumSet
và EnumMap
.
EnumSet
EnumSet
là một cài đặt Set
hiệu suất cao cho kiểu enum. Nó yêu cầu các hằng số enum được đưa vào nó phải thuộc cùng một kiểu enum.
Giao diện chính:
noneOf
- Tạo một EnumSet rỗng với loại phần tử được chỉ định.allOf
- Tạo một EnumSet với loại phần tử được chỉ định và chứa tất cả các giá trị enum.range
- Tạo một EnumSet chứa các phần tử trong một phạm vi giá trị enum được chỉ định.complementOf
- Tạo một EnumSet chứa phần bù của tập hợp được chỉ định.of
- Tạo một EnumSet chứa tất cả các phần tử được chỉ định.copyOf
- Tạo một EnumSet chứa tất cả các phần tử trong bộ chứa được chỉ định.
Ví dụ:
EnumMap
EnumMap
là một cài đặt Map
đặc biệt dành riêng cho kiểu enum. Mặc dù bạn có thể sử dụng các cài đặt Map
khác (như HashMap) để ánh xạ các phần tử enum thành giá trị, nhưng việc sử dụng EnumMap sẽ hiệu quả hơn: nó chỉ chấp nhận các phần tử enum cùng một kiểu làm khóa và do số lượng phần tử enum tương đối cố định và hạn chế, nên EnumMap sử dụng một mảng để lưu trữ các giá trị tương ứng với các phần tử enum. Điều này làm cho EnumMap rất hiệu quả.
Giao diện chính:
size
- Trả về số cặp key-value.containsValue
- Kiểm tra xem giá trị đã cho có tồn tại trong bản đồ không.containsKey
- Kiểm tra xem khóa đã cho có tồn tại trong bản đồ không.get
- Trả về giá trị tương ứng với khóa đã cho.put
- Đặt một cặp key-value vào bản đồ.remove
- Xóa khóa đã cho khỏi bản đồ.putAll
- Đặt tất cả các cặp key-value từ bản đồ đã cho vào bản đồ hiện tại.clear
- Xóa tất cả các cặp key-value khỏi m.keySet
- Trả về tập hợp các khóa trong bản đồ.values
- Trả về tất cả các giá trị trong bản đồ.
Ví dụ: