2012-03-08 32 views
9

Khi tôi đọc "java hiệu quả", tác giả nói với tôi rằng một kiểu enum phần tử đơn là cách tốt nhất để thực hiện một singleton, bởi vì chúng ta không phải xem xét các cuộc tấn công serialization hoặc phản chiếu tinh vi . Điều này có nghĩa là chúng ta không thể tạo ra một thể hiện của enum bằng cách sử dụng sự phản chiếu, đúng không? Tôi đã thực hiện một số kiểm tra, một lớp enum ở đây:Làm thế nào để tạo ra một thể hiện của enum bằng cách sử dụng sự phản chiếu trong java?

public enum Weekday {} 

Sau đó, tôi đã cố gắng để tạo ra một thể hiện của Weekday:

Class<Weekday> weekdayClass = Weekday.class; 
    Constructor<Weekday> cw = weekdayClass.getConstructor(null); 
    cw.setAccessible(true); 
    cw.newInstance(null); 

Như bạn đã biết, nó không hoạt động. Khi tôi thay đổi từ khóa "enum" thành "class", nó hoạt động. Tôi muốn biết tại sao. Cảm ơn bạn trước.

+0

Bạn có thể tạo một lớp enum một cách nhanh chóng bằng cách sử dụng tạo mã byte. Nhưng trong trường hợp đó, enum sẽ không tồn tại trong thời gian biên dịch, vì vậy bạn sẽ không thể tham khảo nó trong phần còn lại của mã của bạn (bạn chỉ có thể truy cập nó thông qua sự phản chiếu, đó là một hoạt động thời gian và không phải là một cơ chế biên dịch thời gian). –

Trả lời

17

Điều này được tích hợp vào ngôn ngữ. Từ số Java Language Specification (§8.9):

Đây là lỗi biên dịch để cố gắng nhanh chóng khởi tạo loại enum (§15.9.1). Phương thức sao chép cuối cùng trong Enum đảm bảo rằng hằng số enum không bao giờ có thể được nhân bản và điều trị đặc biệt bởi cơ chế tuần tự hóa đảm bảo rằng các cá thể trùng lặp không bao giờ được tạo ra do kết quả của quá trình deserialization. Phản ứng tức thời của các loại enum bị cấm. Cùng với nhau, bốn điều này đảm bảo rằng không có trường hợp nào của một loại enum tồn tại ngoài những thể hiện được xác định bởi hằng số enum.

Toàn bộ mục đích của việc này là để cho phép người sử dụng an toàn của == để so sánh Enum trường.

+0

cảm ơn bạn, tôi biết rõ. –

+0

'enum' thực sự được biên dịch thành một' lớp cuối cùng' mở rộng 'java.lang.Enum' với một hàm tạo' private', và điều đó không thực sự ngăn chặn sự phản chiếu instantiation nếu 'setAccessible (true);' được gọi. Tôi vẫn tự hỏi làm thế nào là ngăn chặn? –

+1

@ AhmadY.Saleh - Xem [câu trả lời của soc] (http://stackoverflow.com/a/14432647/535871). –

0

Enums đã được thiết kế để được coi là đối tượng cố định. Nó ghi đè readObject và ném ngoại lệ đối tượng không hợp lệ để ngăn việc tuần tự hóa mặc định. Ngoài ra nó ghi đè lên bản sao() và ném bản sao không được hỗ trợ ngoại lệ. Theo như phản ánh có liên quan, các nhà xây dựng của Enum được bảo vệ. Nếu bạn sử dụng mã trên nó sẽ ném NoSuchMethodFound.

Thậm chí nếu bạn sử dụng getDeclaredConstructor() thay vì getConstructor, bạn sẽ nhận được cùng một ngoại lệ. Tôi cho rằng nó đã bị hạn chế thông qua SecurityManager trong java.

1

Đúng là các trường hợp mới của một lớp enum không thể được tạo ra một cách tích cực, không phải với sự phản chiếu.

Ví dụ dưới đây này:

val weekdayClass = classOf[Weekday] 
val weekdayConstructor = weekdayClass getDeclaredConstructor (classOf[String], classOf[Int]) 
weekdayConstructor setAccessible true 
weekdayConstructor newInstance ("", Integer.valueOf(0)) 

Thông thường, điều này sẽ làm việc. Nhưng trong trường hợp của sự đếm, đây là đặc biệt-cased trong Constructor#newInstance:

if ((clazz.getModifiers() & Modifier.ENUM) != 0) 
    throw new IllegalArgumentException("Cannot reflectively create enum objects"); 

Do đó, chúng tôi nhận được ngoại lệ sau khi cố gắng để nhanh chóng một trường hợp enum mới:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects 
     at java.lang.reflect.Constructor.newInstance(Constructor.java:520) 
     ... 

Tôi giả định rằng cuối cùng cách tiếp cận (có thể sẽ thành công, vì không có kiểm tra hoặc nhà thầu nào được chạy) liên quan đến sun.misc.Unsafe#allocateInstance.

0

Vì vậy, nếu mục tiêu của bạn là liên tục và sau đó xây dựng lại thông tin enum. Bạn sẽ cần phải tồn tại enumClassName và giá trị của nó.

public enum DaysOfWeek{ Mon, Tue, Wed, Thu, Fri, Sat, Sun } 

DaysOfWeek dow = DaysOfWeek.Tue; 
String value = dow.toString(); 
String enumClassName = dow.getClass().getName(); 

// Persist value and enumClassName 
// ... 

// Reconstitute the data 
Class clz = Class.forName(enumClassName); 
Object o = Enum.valueOf(clz, value); 
DaysOfWeek dow2 = (DaysOfWeek)o; 
System.out.println(dow2); 
4

Điều này có thể đang khôi phục bài đăng đã chết nhưng bạn có thể nhận được một trường hợp của mỗi hằng số được khai báo sử dụng Weekday.class.getEnumConstants(). Điều này trả về một mảng của tất cả các constatants, trong đó nhận được một cá thể đơn là tầm thường, getEnumConstants()[0].

Các vấn đề liên quan