Lỗi khi đặt giá trị null mặc định cho trường của chú thích


79

Tại sao tôi gặp lỗi "Giá trị thuộc tính phải không đổi". Null không phải là hằng số sao ???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}

Tại sao ai đó sẽ cần điều này (để biên dịch hoặc giải pháp thay thế)? Các giống "chức năng" đã được cấp bằng Class<? extends Foo> bar();.
xerx593

3
Bởi vì như không có mặc định nữa, lĩnh vực này trở thành bắt buộc trong việc sử dụng chú thích
Donatello

Câu trả lời:


64

Tôi không biết tại sao, nhưng JLS rất rõ ràng:

 Discussion

 Note that null is not a legal element value for any element type. 

Và định nghĩa của một phần tử mặc định là:

     DefaultValue:
         default ElementValue

Rất tiếc, tôi tiếp tục nhận thấy rằng các tính năng ngôn ngữ mới (Enums và bây giờ là Annotations) có thông báo lỗi trình biên dịch rất vô ích khi bạn không đáp ứng thông số ngôn ngữ.

CHỈNH SỬA: Một chút googleing đã tìm thấy điều sau trong JSR-308, nơi họ tranh luận về việc cho phép null trong tình huống này:

Chúng tôi lưu ý một số phản đối có thể có đối với đề xuất này.

Đề xuất không biến bất cứ điều gì có thể xảy ra trước đây.

Giá trị đặc biệt do lập trình viên xác định cung cấp tài liệu tốt hơn null, có thể có nghĩa là “không có”, “chưa được khởi tạo”, chính nó là null, v.v.

Đề xuất dễ xảy ra lỗi hơn. Việc quên kiểm tra null dễ dàng hơn nhiều so với quên kiểm tra một giá trị rõ ràng.

Đề xuất có thể làm cho thành ngữ tiêu chuẩn dài dòng hơn. Hiện tại, chỉ những người dùng chú thích mới cần kiểm tra các giá trị đặc biệt của chú thích. Với đề xuất này, nhiều công cụ xử lý chú thích sẽ phải kiểm tra xem giá trị của một trường có phải là null hay không để tránh chúng ném ra một ngoại lệ con trỏ null.

Tôi nghĩ chỉ có hai điểm cuối cùng liên quan đến việc "tại sao không làm điều đó ngay từ đầu." Điểm cuối cùng chắc chắn mang lại một điểm tốt - một bộ xử lý chú thích không bao giờ phải lo lắng rằng họ sẽ nhận được giá trị chú thích rỗng. Tôi có xu hướng thấy rằng công việc của bộ xử lý chú thích và các mã khuôn khổ khác như vậy phải thực hiện loại kiểm tra đó để làm cho mã của nhà phát triển rõ ràng hơn thay vì ngược lại, nhưng chắc chắn sẽ rất khó để biện minh cho việc thay đổi nó.


67

Thử cái này

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class bar() default void.class;
}

Nó không yêu cầu một lớp mới và nó đã là một từ khóa trong Java không có nghĩa là gì.


8
Tôi thích nó. Vấn đề duy nhất - trong ngữ cảnh của câu hỏi ban đầu - là câu trả lời này không hỗ trợ tham số kiểu: Class<? extends Foo> bar() default void.null không biên dịch.
Kariem

3
Không thể áp dụng chung void.class: public @interface NoNullDefault {String value () default void.class; }
wjohnson

Đối với Groovy nó hoạt động tốt, ngay cả trong trường hợp nêu trong các ý kiến
ma cà rồng

LOL: Điều đó có nghĩa là không có gì so với điều đó có nghĩa là "không có gì"
Andreas

55

Có vẻ như điều này là bất hợp pháp, mặc dù JLS rất mờ nhạt về điều này.

Tôi đã vắt kiệt trí nhớ của mình để thử và nghĩ về một chú thích hiện có ở đó có thuộc tính Class cho một chú thích và ghi nhớ điều này từ API JAXB:

@Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER})        
public @interface XmlJavaTypeAdapter {
    Class type() default DEFAULT.class;

    static final class DEFAULT {}    
}

Bạn có thể thấy cách họ phải xác định một lớp tĩnh giả để chứa một lớp tương đương với null.

Khó chịu.


3
Có, chúng tôi làm điều gì đó tương tự (chúng tôi chỉ sử dụng Foo.class làm mặc định). Khỉ thật.
ripper234,

7
Và trong trường hợp các trường Chuỗi, bạn phải dùng đến chuỗi ma thuật . Rõ ràng các phản vật chất nổi tiếng tốt hơn các tính năng ngôn ngữ được hiểu rõ ràng hiện nay.
Tim Yates

3
@TimYates Tôi giết tôi khi thấy mọi người xác định giá trị sentinel "không có giá trị" của riêng họ khi giá trị null có sẵn và được mọi người hiểu. Và bây giờ nó đang được yêu cầu bởi chính ngôn ngữ ?! May mắn thay, bạn có thể định nghĩa "chuỗi ma thuật" của mình là hằng số công khai. Nó vẫn không phải là lý tưởng nhưng ít nhất mã đang tìm kiếm chú thích của bạn có thể tham chiếu đến hằng số thay vì sử dụng các ký tự ở khắp mọi nơi.
spaaarky21

11

Có vẻ như có một cách khác để làm điều đó.

Tôi cũng không thích điều này, nhưng nó có thể hoạt động.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo>[] bar() default {};
}

Vì vậy, về cơ bản bạn tạo một Mảng trống để thay thế. Điều này dường như cho phép một giá trị mặc định.


3
Class<? extends Foo> bar() default null;// this doesn't compile

Như đã đề cập, đặc tả ngôn ngữ Java không cho phép giá trị null trong mặc định của chú thích.

Những gì tôi có xu hướng làm là xác định các DEFAULT_VALUEhằng số ở đầu định nghĩa chú thích. Cái gì đó như:

public @interface MyAnnotation {
    public static final String DEFAULT_PREFIX = "__class-name-here__ default";
    ...
    public String prefix() default DEFAULT_PREFIX;
}

Sau đó, trong mã của tôi, tôi làm điều gì đó như:

if (myAnnotation.prefix().equals(MyAnnotation.DEFAULT_PREFIX)) { ... }

Trong trường hợp của bạn với một lớp, tôi chỉ định nghĩa một lớp đánh dấu. Thật không may, bạn không thể có một hằng số cho điều này, vì vậy thay vào đó, bạn cần phải làm điều gì đó như:

public @interface MyAnnotation {
    public static Class<? extends Foo> DEFAULT_BAR = DefaultBar.class;
    ...
    Class<? extends Foo> bar() default DEFAULT_BAR;
}

DefaultFooLớp của bạn sẽ chỉ là một triển khai trống để mã có thể thực hiện:

if (myAnnotation.bar() == MyAnnotation.DEFAULT_BAR) { ... }

Hi vọng điêu nay co ich.


Thật tình cờ, tôi vừa thử điều này với một Chuỗi, và điều đó không hoạt động. Bạn không lấy lại cùng một đối tượng Chuỗi.
Doradus

Bạn đã sử dụng .equals()và không phải ==@Doradus? Bạn đã nhận được một giá trị khác hoặc một đối tượng khác.
Xám

Đối tượng khác nhau. Tôi đã sử dụng ==.
Doradus

Vâng, lớp là một singleton @Doradus. Tôi đã mong đợi Chuỗi cũng là một đơn lẻ nhưng tôi đoán nó không phải và bạn sẽ cần sử dụng .equals(). Thật ngạc nhiên.
Màu xám,

1

Thông báo lỗi đó gây hiểu lầm, nhưng, không, nullnghĩa đen không phải là một biểu thức hằng, như được định nghĩa trong Đặc tả ngôn ngữ Java, tại đây .

Hơn nữa, Đặc tả ngôn ngữ Java cho biết

Đó là lỗi thời gian biên dịch nếu loại phần tử không tương xứng (§9.7) với giá trị mặc định được chỉ định.

Và để giải thích điều đó có nghĩa là gì

Đó là lỗi thời gian biên dịch nếu kiểu phần tử không tương xứng với giá trị phần tử. Loại phần tử T tương xứng với giá trị phần tử V nếu và chỉ khi một trong các điều sau là đúng:

  • Tlà một kiểu mảng E[]và:
    • [...]
  • Tkhông phải là một kiểu mảng và kiểu của Vphép gán tương thích (§5.2) với T, :
    • Nếu Tlà một kiểu nguyên thủy hoặc String, thì Vlà một biểu thức hằng (§15.28).
    • Nếu TClasshoặc một lời gọi của Class(§4.5), thì đó Vlà một lớp nghĩa đen (§15.8.2).
    • Nếu Tlà một kiểu enum (§8.9), thì đó Vlà một hằng số enum (§8.9.1).
    • V không rỗng .

Vì vậy, ở đây, loại phần tử của bạn Class<? extends Foo>,, không tương xứng với giá trị mặc định, bởi vì giá trị đó là null.


Giải pháp của bạn là không thích hợp cho bản sao & dán ;-) Một số không thích suy nghĩ, khi đọc
Matthias M

1
IMHO vâng, bạn đã làm (nhưng đó không phải là tôi). Biểu thức của bạn có dạng như "when (...) hoặc (V không phải là null)", nghe có vẻ như bất kỳ giá trị nào không phải là null đều đúng. Công thức của JLS là một con thú thực sự, nhưng có cả bên ngoài orvà bên trong and, không thể bỏ sót.
maaartinus

@maaartinus Đã không thấy bình luận của bạn từ đó lâu rồi, hãy cập nhật câu trả lời của tôi để thêm toàn bộ trích dẫn.
Sotirios Delimanolis

1

Như những người khác đã nói, có một quy tắc nói rằng null không phải là giá trị mặc định hợp lệ trong Java.

Nếu bạn có một số lượng giới hạn các giá trị có thể có mà trường có thể có , thì một tùy chọn để giải quyết vấn đề này là đặt trường loại một enum tùy chỉnh mà bản thân nó chứa ánh xạ thành null. Ví dụ: hãy tưởng tượng tôi có chú thích sau mà tôi muốn có giá trị null mặc định cho:

public @interface MyAnnotation {
   Class<?> value() default null;
}

Như chúng tôi đã thiết lập, điều này không được phép. Thay vào đó, những gì chúng ta có thể làm là xác định một enum:

public enum MyClassEnum {
    NULL(null),
    MY_CLASS1(MyClass1.class),
    MY_CLASS2(MyClass2.class),
    ;

    public final Class<?> myClass;

    MyClassEnum(Class<?> aClass) {
        myClass = aClass;
    }
}

và thay đổi chú thích như vậy:

public @interface MyAnnotation {
  MyClassEnum value() default MyClassEnum.NULL;
}

Nhược điểm chính của điều này (bên cạnh việc cần liệt kê các giá trị có thể có của bạn) là hiện tại bạn đã gói giá trị của mình trong một enum, vì vậy bạn sẽ cần gọi thuộc tính / getter trên enum để nhận giá trị.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.