Кілька анотацій одного типу на одному елементі?


91

Я намагаюся ляпати дві або більше анотацій одного типу на одному елементі, в даному випадку методу. Ось приблизний код, з яким я працюю:

public class Dupe {
    public @interface Foo {
      String bar();
    }

    @Foo(bar="one")
    @Foo(bar="two")
    public void haha() {}
}

Складаючи вищезазначене, javac скаржиться на дублікат анотації:

max @ upsight: ~ / work / daybreak $ javac Dupe.java 
Dupe.java:5: дубльована анотація

Чи просто не можна повторювати подібні анотації? Педантично кажучи, чи не відрізняються обидва випадки @Foo вище через те, що їх зміст різниться?

Якщо вищезазначене неможливо, які можливі способи вирішення?

ОНОВЛЕННЯ: Мене попросили описати мій варіант використання. Ось іде.

Я будую синтаксичний цукровий механізм, щоб "зіставити" POJO із сховищами документів, такими як MongoDB. Я хочу дозволити індекси визначатись як анотації на геттерах або сеттерах. Ось надуманий приклад:

public class Employee {
    private List<Project> projects;

    @Index(expr = "project.client_id")
    @Index(expr = "project.start_date")
    public List<Project> getProjects() { return projects; }
}

Очевидно, що я хочу мати можливість швидко знаходити екземпляри Employee за різними властивостями Project. Я можу або вказати @Index двічі з різними значеннями expr (), або скористатися підходом, зазначеним у прийнятій відповіді. Незважаючи на те, що Hibernate робить це, і це не вважається хакерством, я вважаю, що все одно має сенс принаймні дозволити мати кілька анотацій одного типу на одному елементі.


1
Існує спроба послабити це повторюване правило, щоб дозволити програму на Java 7. Чи можете ви описати ваш варіант використання?
notnoop

Я відредагував своє запитання з описом, чому я хочу це зробити. Дякую.
Макс А.

У CDI може бути дуже зручно, щоб забезпечити надання квасолі для кількох кваліфікацій. Наприклад, я щойно намагався повторно використати боб у двох місцях, визначивши його "@Produces @PackageName (" test1 ") @PackageName (" test2 ")"
Річард Корфілд,

Далі: Наведена нижче відповідь не вирішує цю проблему, оскільки CDI бачить композит як один Кваліфікатор.
Річард Корфілд,

@MaxA. Будь ласка, змініть своє прийняття "зеленого чека" на правильну відповідь mernst. Java 8 додала можливість повторення анотацій.
Василь Бурк

Відповіді:


140

Дві або більше анотацій одного типу заборонені. Однак ви можете зробити щось подібне:

public @interface Foos {
    Foo[] value();
}

@Foos({@Foo(bar="one"), @Foo(bar="two")})
public void haha() {}

Однак вам знадобиться спеціальна обробка анотацій Foos в коді.

До речі, я щойно використав це 2 години тому, щоб обійти ту ж проблему :)


2
Чи можете ви це зробити також у Groovy?
Excel20,

5
@ Excel20 Так. Вам доведеться використовувати квадратні дужки, наприклад, наприклад @Foos([@Foo(bar="one"), @Foo(bar="two")]). Дивіться groovy.codehaus.org/Annotations+with+Groovy
sfussenegger

Трохи пізно вдень, але хочете вказати на пораду, яка може обробити список Foo всередині Foos? На даний момент я намагаюся визначити результат методу, але, незважаючи на те, що Foos перехоплений, порада Foo ніколи не надходить
Stelios Koussouris

Оновлення: станом на Java 8 ця відповідь застаріла. Дивіться правильну сучасну відповідь mernst. Java 8 додала можливість повторення анотацій.
Василь Бурк

65

Повторювані анотації в Java 8

У Java 8 (випущена в березні 2014 року) можна писати повторювані / повторювані анотації.

Див. Підручник, Повторювані анотації .

Див. Специфікацію, JEP 120: Повторювані анотації .



21

Окрім інших згаданих способів, у Java8 є ще один менш детальний спосіб:

@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Foo {
    String value();

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FooContainer {
        Foo[] value();
        }

@Foo("1") @Foo("2") @Foo("3")
class Example{

}

Приклад за замовчуванням отримує FooContainerяк анотацію

    Arrays.stream(Example.class.getDeclaredAnnotations()).forEach(System.out::println);
    System.out.println(Example.class.getAnnotation(FooContainer.class));

Обидва наведені вище друку:

@ com.FooContainer (значення = [@ com.Foo (значення = 1), @ com.Foo (значення = 2), @ com.Foo (значення = 3)])

@ com.FooContainer (значення = [@ com.Foo (значення = 1), @ com.Foo (значення = 2), @ com.Foo (значення = 3)])


Варто зазначити, що ім'я методу / поля у FooContainer повинно мати строгу назву "value ()". Інакше Foo не буде компілювати.
Томаш Муларчик

... також містить тип анотації (FooContainer) не може застосовуватися до більшої кількості типів, ніж тип повторюваних анотацій (Foo).
Томаш Муларчик

16

http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html

Починаючи з Java8, ви можете описати повторювані анотації:

@Repeatable(FooValues.class)
public @interface Foo {
    String bar();
}

public @interface FooValues {
    Foo[] value();
}

Примітка, valueобов’язкове поле для списку значень

Тепер ви можете використовувати анотації, що повторюють їх, замість заповнення масиву:

@Foo(bar="one")
@Foo(bar="two")
public void haha() {}

12

Як сказав sfussenegger, це неможливо.

Звичайним рішенням є створення "множинної" анотації , яка обробляє масив попередньої анотації. Зазвичай його називають однаковим, із суфіксом 's'.

До речі, це дуже використовується у великих державних проектах (наприклад, Hibernate), тому це не слід розглядати як хакерство, а скоріше правильне рішення для цієї потреби.


Залежно від ваших потреб, може бути краще дозволити вашій попередній анотації обробляти кілька значень .

Приклад:

    public @interface Foo {
      String[] bars();
    }

Я знав, що це моторошно знайоме. Дякую за освіження моєї пам’яті.
Макс А.

Тепер це можливо з Java 8.
Аніс,

4

об'єднання інших відповідей у ​​найпростішу форму ... анотація з простим списком значень ...

@Foos({"one","two"})
private String awk;

//...

public @interface Foos{
    String[] value();
}

3

Якщо у вас є лише 1 параметр "bar", ви можете назвати його "value". У цьому випадку вам не доведеться взагалі писати ім'я параметра, коли ви використовуєте його таким чином:

@Foos({@Foo("one"), @Foo("two")})
public void haha() {}

трохи коротший і акуратніший, імхо ..


Правильний момент, але як ця спроба відповісти на питання ОП?
Мордехай

@MouseEvent ви маєте рацію, я думаю, це було скоріше вдосконаленням головної відповіді sfussenegger і, отже, більше належить там у коментарях. Але відповідь у будь-якому випадку застаріла через повторювані анотації ...
golwig

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.