2011-08-15 45 views
18

Java cho phép enum làm giá trị cho giá trị chú thích. Làm cách nào tôi có thể xác định loại giá trị mặc định chung enum cho giá trị chú thích enum?Giá trị mặc định enum cho giá trị chú thích enum Java

Tôi đã xem xét những điều sau đây, nhưng nó sẽ không biên dịch:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
public <T extends Enum<T>> @interface MyAnnotation<T> { 

    T defaultValue(); 

} 

Có một giải pháp cho vấn đề này hay không?

BOUNTY

Là không có vẻ như có một giải pháp trực tiếp cho trường hợp góc Java này. Vì vậy, tôi bắt đầu một tiền thưởng để tìm ra giải pháp thanh lịch nhất cho vấn đề này.

Giải pháp lý tưởng nên lý tưởng đáp ứng các tiêu chí sau:

  1. Một chú thích tái sử dụng trên tất cả các enums
  2. nỗ lực tối thiểu/phức tạp để lấy giá trị enum mặc định như một enum từ trường hợp chú thích

GIẢI PHÁP TỐT NHẤT SO FAR

By Dunes:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
public @interface MyAnnotation { 

    // By not specifying default, 
    // we force the user to specify values 
    Class<? extends Enum<?>> enumClazz(); 
    String defaultValue(); 

} 

... 

public enum MyEnumType { 
    A, B, D, Q; 
} 

... 

// Usage 
@MyAnnotation(enumClazz=MyEnumType.class, defaultValue="A"); 
private MyEnumType myEnumField; 

Tất nhiên, chúng ta không thể buộc người dùng phải chỉ định một giá trị mặc định có giá trị tại thời gian biên dịch. Tuy nhiên, mọi quá trình xử lý trước chú thích có thể xác minh điều này bằng valueOf().

CẢI THIỆN

Arian cung cấp giải pháp thanh lịch để thoát khỏi clazz trong các lĩnh vực chú thích:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
public @interface MyAnnotation { 

} 

... 

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
@MyAnnotation() 
public @interface MyEnumAnnotation { 

    MyEnumType value(); // no default has user define default value 

} 

... 

@MyEnumAnnotation(MyEnum.FOO) 
private MyEnumType myValue; 

Bộ xử lý chú thích nên tìm kiếm cả MyEnumAnnotation trên lĩnh vực đối với giá trị mặc định được cung cấp.

Điều này yêu cầu tạo một loại chú thích cho mỗi loại enum, nhưng đảm bảo thời gian biên dịch được kiểm tra an toàn.

+0

Điều đó không phải là vô nghĩa? Bất cứ khi nào bạn sẽ xử lý chú thích khi chạy, thông tin chung sẽ bị mất. – Dunes

+0

Nó sẽ tránh tôi phải xác định một chú thích cho mỗi loại enum để sử dụng trong mã của tôi (nó là một vấn đề thời gian biên dịch). – JVerstry

+0

Tôi không có nghĩa ý tưởng này không hữu ích. Nhưng generics chỉ được biết đến lúc biên dịch. Bạn đã đánh dấu chú thích của mình để được giữ lại trong thời gian chạy. Nhưng để truy cập chú thích bạn phải đi qua phản xạ - bạn sẽ không có bất kỳ ý tưởng nào về kiểu gốc chung. – Dunes

Trả lời

2

Tôi không chắc chắn những gì trường hợp sử dụng của bạn, vì vậy tôi có hai câu trả lời:

trả lời 1:

Nếu bạn chỉ muốn viết ít mã càng tốt, đây là đề nghị của tôi mở rộng câu trả lời Dunes':

public enum ImplicitType { 
    DO_NOT_USE; 
} 

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
public @interface MyAnnotation { 

    Class<? extends Enum<?>> clazz() default ImplicitType.class; 

    String value(); 
} 

@MyAnnotation("A"); 
private MyEnumType myEnumField; 

Khi clazzImplicitType.class, hãy sử dụng loại trường là lớp enum.

Trả lời 2:

Nếu bạn muốn làm một số phép thuật khuôn khổ và muốn duy trì biên dịch kiểm tra an toàn kiểu, bạn có thể làm một cái gì đó như thế này:

/** Marks annotation types that provide MyRelevantData */ 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.ANNOTATION_TYPE) 
public @interface MyAnnotation { 
} 

Và trong mã khách hàng, bạn sẽ có

/** Provides MyRelevantData for TheFramework */ 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
@MyAnnotation 
public @interface MyEnumAnnotation { 

    MyEnumType value(); // default MyEnumType.FOO; 

} 

@MyEnumAnnotation(MyEnum.FOO) 
private MyEnumType myValue; 

Trong trường hợp này, bạn sẽ quét trường cho chú thích được chú thích bằng MyAnnotation. Tuy nhiên, bạn sẽ phải truy cập giá trị thông qua sự phản chiếu trên đối tượng chú thích. Có vẻ như cách tiếp cận này phức tạp hơn ở phía khung công tác.

+0

Trông rất thú vị, nhưng không nên clazz vẫn còn trong phiên bản MyAnnotation và không nên MyEnumAnnotation được chú thích với @ MyAnnotation (clazz = MyEnumType.class)? Hay tôi đang thiếu một cái gì đó? – JVerstry

+0

Tôi hy vọng nó rõ ràng hơn bây giờ. – Cephalopod

+0

Đối với phương pháp thứ hai, bạn không cần trường clazz, vì bạn có thể nhận được giá trị enum trực tiếp từ chú thích ứng dụng khách. Nếu bạn cũng cần kiểu giá trị, bạn có thể sử dụng kiểu trả về của hàm value(). – Cephalopod

3

Nói một cách đơn giản, bạn không thể làm điều đó. Enums có thể không dễ dàng được sử dụng như các loại generic; có lẽ một ngoại lệ, đó là Enums có thể thực sự thực hiện các giao diện cho phép sử dụng phần nào năng động. Nhưng điều đó sẽ không hoạt động với chú thích vì tập hợp các loại có thể được sử dụng bị hạn chế nghiêm ngặt.

3

Cú pháp loại chung của bạn hơi lệch. Nó nên là:

public @interface MyAnnotation<T extends Enum<T>> {... 

nhưng trình biên dịch cho lỗi:

Syntax error, annotation declaration cannot have type parameters

Ý tưởng tốt đẹp. Có vẻ như nó không được hỗ trợ.

+0

Vâng, tôi cũng đã thử điều đó. Nhưng ... – JVerstry

5

Không hoàn toàn chắc chắn ý bạn là gì khi bạn nói có giá trị mặc định nếu giá trị nói trên không được cung cấp trong hàm tạo, nhưng không quan tâm đến kiểu chung khi chạy.

Các công trình sau đây, nhưng là một chút của một bản hack xấu xí.

import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 

public class Main { 

    @MyAnnotation(clazz = MyEnum.class, name = "A") 
    private MyEnum value; 

    public static v oid main(String[] args) { 
     new Main().printValue(); 
    } 

    public void printValue() { 
     System.out.println(getValue()); 
    } 

    public MyEnum getValue() { 
     if (value == null) { 
      value = getDefaultValue("value", MyEnum.class); 
     } 
     return value; 
    } 

    private <T extends Enum<?>> T getDefaultValue(String name, Class<T> clazz) { 

     try { 
      MyAnnotation annotation = Main.class.getDeclaredField(name) 
        .getAnnotation(MyAnnotation.class); 

      Method valueOf = clazz.getMethod("valueOf", String.class); 

      return clazz.cast(valueOf.invoke(this, annotation.value())); 

     } catch (SecurityException e) { 
      throw new IllegalStateException(e); 
     } catch (NoSuchFieldException e) { 
      throw new IllegalArgumentException(name, e); 
     } catch (IllegalAccessException e) { 
      throw new IllegalStateException(e); 
     } catch (NoSuchMethodException e) { 
       throw new IllegalStateException(e); 
     } catch (InvocationTargetException e) { 
      if (e.getCause() instanceof RuntimeException) { 
       throw (RuntimeException) e.getCause(); 
       /* rethrow original runtime exception 
       * For instance, if value = "C" */ 
      } 
      throw new IllegalStateException(e); 
     } 
    } 

    public enum MyEnum { 
     A, B; 
    } 

    @Retention(RetentionPolicy.RUNTIME) 
    @Target(ElementType.FIELD) 
    public @interface MyAnnotation { 

     Class<? extends Enum<?>> clazz(); 

     String name(); 
    } 
} 

chỉnh sửa: Tôi đã thay đổi getDefaultValue để làm việc thông qua phương thức valueOf enums, do đó đưa ra một thông báo lỗi tốt hơn nếu giá trị cho không tham khảo ví dụ của enum.

+0

Oooh, ý tưởng hay để khám phá ở đây ... – JVerstry

+0

"Không hoàn toàn chắc chắn ý bạn là gì khi bạn nói nhận giá trị mặc định nếu giá trị nói không được cung cấp trong hàm tạo arg" -> Giá trị mặc định sẽ được xác định trong 'cá thể' chú thích trên mục được chú thích. – JVerstry

3

Các khung sử dụng chú thích thực sự có thể hưởng lợi từ việc sử dụng apt. Nó là một trình tiền xử lý có trong javac, cho phép bạn phân tích các khai báo và chú thích của chúng (nhưng không phải khai báo cục bộ bên trong các phương thức).

Đối với vấn đề của bạn, bạn sẽ cần phải viết AnnotationProcessor (một lớp được sử dụng làm điểm bắt đầu để xử lý trước) để phân tích chú thích bằng cách sử dụng Mirror API. Trên thực tế, chú thích của Dunes khá gần với những gì cần thiết ở đây. Tên enum quá xấu không phải là biểu thức liên tục, nếu không thì giải pháp của Dunes sẽ khá tốt.

@Retention(RetentionPolicy.SOURCE) 
@Target(ElementType.FIELD) 
public @interface MyAnnotation { 
    Class<? extends Enum<?>> clazz(); 
    String name() default ""; 
} 

Và đây là một ví dụ enum: enum MyEnum { FOO, BAR, BAZ, ; }

Khi sử dụng một IDE hiện đại, bạn có thể hiển thị lỗi trên trực tiếp trên các yếu tố chú thích (hoặc giá trị của chú thích), nếu tên này không phải là một enum hợp lệ không thay đổi. Bạn thậm chí có thể cung cấp gợi ý tự động hoàn chỉnh, vì vậy khi người dùng viết @MyAnnotation(clazz = MyEnum.class, name = "B") và truy cập các phím nóng để tự động hoàn thành sau khi viết B, bạn có thể cung cấp cho anh ta danh sách để chọn, chứa tất cả các hằng số bắt đầu bằng B: BAR và BAZ.

Đề xuất của tôi để triển khai giá trị mặc định là tạo chú thích đánh dấu để khai báo hằng số enum là giá trị mặc định cho enum đó. Người dùng vẫn cần phải cung cấp loại enum, nhưng có thể bỏ qua tên. Có lẽ có nhiều cách khác, để tạo một giá trị mặc định.

Đây là một số tutorial về apt và tại đây là AbstractProcessor cần được mở rộng để ghi đè phương thức getCompletions.

+0

Cảm ơn. Tôi biết về bộ xử lý chú thích. Tôi thấy giải pháp của Dune. Tôi chỉ tự hỏi liệu có ai đó có ý tưởng hay hơn không. Đó là lý do tại sao tôi đã đặt tiền thưởng. – JVerstry

+1

Kiểm tra các chú thích một lần với bộ xử lý tốt hơn luôn kiểm tra chúng khi chạy. Các generics bạn đề nghị sẽ được tốt đẹp, nhưng tiếc là không có sẵn. Enum tên dường như là cách duy nhất để giải quyết vấn đề (trừ khi bạn sử dụng cái gì khác thay vì enums), nhưng chúng không phải là hằng số thời gian biên dịch và do đó phải được viết thành chuỗi thời gian biên dịch không đổi. Việc xác thực các chuỗi đó trong thời gian biên dịch sẽ khá thuận tiện cho người dùng. – Kapep

2

Đề xuất của tôi tương tự như kapep's đề xuất. Sự khác biệt là tôi đề xuất sử dụng bộ xử lý chú thích để tạo mã.

Một ví dụ đơn giản sẽ là nếu bạn định sử dụng điều này chỉ cho các enums mà chính bạn đã viết. Chú thích enum với enum đặc biệt. Sau đó, bộ xử lý chú thích sẽ tạo chú thích mới chỉ cho enum đó.

Nếu bạn làm việc với rất nhiều enums mà bạn không viết, thì bạn có thể thực hiện một số lược đồ ánh xạ tên: tên enum -> tên chú thích. Sau đó, khi bộ xử lý chú thích gặp phải một trong các enums này trong mã của bạn, nó sẽ tự động tạo chú thích thích hợp.

Bạn hỏi cho:

  1. Một chú thích tái sử dụng trên tất cả các enums ... về mặt kỹ thuật không, nhưng tôi nghĩ rằng tác dụng là như nhau.
  2. tối thiểu nỗ lực/phức tạp để lấy giá trị enum mặc định như một enum từ trường hợp chú thích ... bạn có thể lấy giá trị enum mặc định mà không cần bất kỳ chế biến đặc biệt
0

Tôi đã có một nhu cầu tương tự và đã đưa ra các giải pháp khá đơn giản sau đây:

Giao diện thực tế @Default:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
public @interface Default {} 

Cách sử dụng:

public enum Foo { 
    A, 
    @Default B, 
    C; 
} 

Tìm mặc định:

public abstract class EnumHelpers { 
    public static <T extends Enum<?>> T defaultEnum(Class<T> clazz) { 
     Map<String, T> byName = Arrays.asList(clazz.getEnumConstants()).stream() 
      .collect(Collectors.toMap(ec -> ec.name(), ec -> ec)); 

     return Arrays.asList(clazz.getFields()).stream() 
      .filter(f -> f.getAnnotation(Default.class) != null) 
      .map(f -> byName.get(f.getName())) 
      .findFirst() 
      .orElse(clazz.getEnumConstants()[0]); 
    } 
} 

Tôi cũng đã chơi xung quanh với trả về một Optional<T> thay vì mặc định thành hằng số Enum đầu tiên được khai báo trong lớp.

Điều này, tất nhiên, là một khai báo mặc định trên toàn lớp, nhưng nó phù hợp với những gì tôi cần. YMMV :)