Gắn giá trị vào Java Enum

1. Giới thiệu

Kiểu enum của Java cung cấp một cách được ngôn ngữ hỗ trợ để tạo và sử dụng các giá trị không đổi. Bằng cách xác định một tập giá trị hữu hạn, enum an toàn về kiểu hơn so với các biến dạng hằng chữ như String hoặc int .

Tuy nhiên, giá trị enum bắt buộc phải là số nhận dạng hợp lệ và chúng tôi khuyến khích sử dụng SCREAMING_SNAKE_CASE theo quy ước.

Với những hạn chế này, các enum giá trị không thôi là không phù hợp cho các chuỗi con người có thể đọc được hoặc giá trị không theo chuỗi .

Trong hướng dẫn này, chúng tôi sẽ sử dụng các tính năng của enum như một lớp Java để đính kèm các giá trị mà chúng tôi muốn.

2. Sử dụng Java Enum làm lớp

Chúng ta thường tạo một enum dưới dạng một danh sách các giá trị đơn giản. Ví dụ, đây là hai hàng đầu tiên của bảng tuần hoàn dưới dạng một enum đơn giản :

public enum Element { H, HE, LI, BE, B, C, N, O, F, NE }

Sử dụng cú pháp trên, chúng tôi đã tạo ra mười phiên bản tĩnh, cuối cùng của enum có tên Element . Trong khi điều này rất hiệu quả, chúng tôi chỉ nắm bắt được các ký hiệu phần tử. Và trong khi dạng viết hoa thích hợp cho các hằng số Java, thì đó không phải là cách chúng ta viết các ký hiệu thông thường.

Hơn nữa, chúng ta cũng đang thiếu các thuộc tính khác của các nguyên tố trong bảng tuần hoàn, như tên và trọng lượng nguyên tử.

Mặc dù kiểu enum có hành vi đặc biệt trong Java, chúng ta có thể thêm các hàm tạo, trường và phương thức như chúng ta làm với các lớp khác. Do đó, chúng tôi có thể nâng cao enum của mình để bao gồm các giá trị mà chúng tôi cần.

3. Thêm một hàm tạo và một trường cuối cùng

Hãy bắt đầu bằng cách thêm tên phần tử. Chúng tôi sẽ đặt tên thành một biến cuối cùng bằng cách sử dụng một hàm tạo :

public enum Element { H("Hydrogen"), HE("Helium"), // ... NE("Neon"); public final String label; private Element(String label) { this.label = label; } }

Trước hết, chúng ta lưu ý đến cú pháp đặc biệt trong danh sách khai báo. Đây là cách một hàm tạo được gọi cho các kiểu enum . Mặc dù việc sử dụng toán tử mới cho một enum là bất hợp pháp , nhưng chúng ta có thể chuyển các đối số của hàm tạo trong danh sách khai báo.

Sau đó, chúng tôi khai báo một nhãn biến cá thể . Có một số điều cần lưu ý về điều đó.

Đầu tiên, chúng tôi chọn định danh nhãn thay vì tên . Mặc dù tên trường thành viên có sẵn để sử dụng, chúng ta hãy chọn nhãn để tránh nhầm lẫn với phương thức Enum.name () được xác định trước .

Thứ hai, trường nhãn của chúng tôi là cuối cùng . Mặc dù các trường của một enum không nhất thiết phải là cuối cùng , nhưng trong phần lớn các trường hợp, chúng tôi không muốn nhãn của mình thay đổi. Theo tinh thần của các giá trị enum là không đổi, điều này có ý nghĩa.

Cuối cùng, trường nhãn là công khai. Do đó, chúng tôi có thể truy cập trực tiếp vào nhãn:

System.out.println(BE.label);

Mặt khác, trường có thể là riêng tư , được truy cập bằng phương thức getLabel () . Với mục đích ngắn gọn, bài viết này sẽ tiếp tục sử dụng kiểu trường công cộng.

4. Định vị các Giá trị Enum của Java

Java cung cấp một phương thức valueOf (String) cho tất cả các kiểu enum . Do đó, chúng ta luôn có thể nhận được một giá trị enum dựa trên tên đã khai báo:

assertSame(Element.LI, Element.valueOf("LI"));

Tuy nhiên, chúng tôi cũng có thể muốn tra cứu một giá trị enum theo trường nhãn của chúng tôi. Để làm điều đó, chúng ta có thể thêm một phương thức tĩnh :

public static Element valueOfLabel(String label) { for (Element e : values()) { if (e.label.equals(label)) { return e; } } return null; }

Phương thức static valueOfLabel () lặp lại các giá trị Phần tử cho đến khi nó tìm thấy một kết quả phù hợp. Nó trả về null nếu không tìm thấy kết quả phù hợp nào. Ngược lại, một ngoại lệ có thể được ném ra thay vì trả về null .

Hãy xem một ví dụ nhanh bằng cách sử dụng phương thức valueOfLabel () của chúng tôi :

assertSame(Element.LI, Element.valueOfLabel("Lithium"));

5. Lưu vào bộ nhớ đệm các giá trị tra cứu

Chúng ta có thể tránh lặp lại các giá trị enum bằng cách sử dụng Bản đồ để lưu vào bộ nhớ cache các nhãn . Để làm điều này, chúng tôi xác định một Bản đồ cuối cùng tĩnh và điền nó vào khi lớp tải:

public enum Element { // ... enum values private static final Map BY_LABEL = new HashMap(); static { for (Element e: values()) { BY_LABEL.put(e.label, e); } } // ... fields, constructor, methods public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } }

Do được lưu vào bộ nhớ đệm, các giá trị enum chỉ được lặp lại một lần và phương thức valueOfLabel () được đơn giản hóa.

Thay vào đó, chúng ta có thể tạo cache một cách lười biếng khi nó được truy cập lần đầu trong phương thức valueOfLabel () . Trong trường hợp đó, quyền truy cập bản đồ phải được đồng bộ hóa để ngăn ngừa các vấn đề đồng thời.

6. Đính kèm nhiều giá trị

Phương thức khởi tạo Enum có thể chấp nhận nhiều giá trị . Để minh họa, hãy thêm số nguyên tử dưới dạng int và trọng lượng nguyên tử dưới dạng float :

public enum Element { H("Hydrogen", 1, 1.008f), HE("Helium", 2, 4.0026f), // ... NE("Neon", 10, 20.180f); private static final Map BY_LABEL = new HashMap(); private static final Map BY_ATOMIC_NUMBER = new HashMap(); private static final Map BY_ATOMIC_WEIGHT = new HashMap(); static { for (Element e : values()) { BY_LABEL.put(e.label, e); BY_ATOMIC_NUMBER.put(e.atomicNumber, e); BY_ATOMIC_WEIGHT.put(e.atomicWeight, e); } } public final String label; public final int atomicNumber; public final float atomicWeight; private Element(String label, int atomicNumber, float atomicWeight) { this.label = label; this.atomicNumber = atomicNumber; this.atomicWeight = atomicWeight; } public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } public static Element valueOfAtomicNumber(int number) { return BY_ATOMIC_NUMBER.get(number); } public static Element valueOfAtomicWeight(float weight) { return BY_ATOMIC_WEIGHT.get(weight); } }

Tương tự, chúng ta có thể thêm bất kỳ giá trị nào chúng ta muốn vào enum , chẳng hạn như các ký hiệu chữ hoa, “He”, “Li” và “Be” chẳng hạn.

Hơn nữa, chúng ta có thể thêm các giá trị được tính toán vào enum của mình bằng cách thêm các phương thức để thực hiện các phép toán.

7. Kiểm soát giao diện

Do việc thêm các trường và phương thức vào enum , chúng tôi đã thay đổi giao diện công khai của nó. Do đó, mã của chúng tôi, sử dụng các phương thức Enum name ()valueOf () cốt lõi , sẽ không biết về các trường mới của chúng tôi.

Phương thức static valueOf () đã được ngôn ngữ Java định nghĩa cho chúng ta. Do đó, chúng tôi không thể cung cấp triển khai valueOf () của riêng mình .

Tương tự, vì phương thức Enum.name ()cuối cùng, chúng ta cũng không thể ghi đè nó .

Do đó, không có cách nào thực tế để sử dụng các trường bổ sung của chúng tôi bằng cách sử dụng API Enum tiêu chuẩn . Thay vào đó, hãy xem xét một số cách khác nhau để hiển thị các trường của chúng tôi.

7.1. Ghi đè toString ()

Ghi đè toString () có thể là một thay thế cho ghi đè tên () :

@Override public String toString() { return this.label; }

Theo mặc định, Enum.toString () trả về cùng giá trị với Enum.name ().

7.2. Triển khai một giao diện

Kiểu enum trong Java có thể triển khai các giao diện . Mặc dù cách tiếp cận này không chung chung như Enum API, nhưng các giao diện giúp chúng ta tổng quát hóa.

Hãy xem xét giao diện này:

public interface Labeled { String label(); }

Để nhất quán với phương thức Enum.name () , phương thức label () của chúng ta không có tiền tố get .

Và bởi vì phương thức valueOfLabel ()tĩnh nên chúng tôi không đưa nó vào giao diện của mình.

Cuối cùng, chúng ta có thể triển khai giao diện trong enum của mình :

public enum Element implements Labeled { // ... @Override public String label() { return label; } // ... }

Một lợi ích của phương pháp này là giao diện được gắn nhãn có thể được áp dụng cho bất kỳ lớp nào, không chỉ các kiểu enum . Thay vì dựa vào API Enum chung chung , giờ đây chúng tôi có một API dành riêng cho ngữ cảnh hơn.

8. Kết luận

Trong bài viết này, chúng tôi đã khám phá nhiều tính năng của việc triển khai Java Enum . Bằng cách thêm các hàm tạo, trường và phương thức, chúng ta thấy rằng enum có thể làm được nhiều việc hơn là các hằng chữ.

Như mọi khi, mã nguồn đầy đủ cho bài viết này có thể được tìm thấy trên GitHub.