Анотації в Java, частина I

  Це перша частина статті, присвяченій такому мовною механізму Java 5 + як анотації. Вона має вступний характер і розрахована на Junior розробників або тих, хто тільки приступає до вивчення мови.
 
Я займаюся онлайн навчанням Java і опублікую частина навчальних матеріалів у рамках переробки курсу Java Core .
Мій метод навчання полягає в тому, що я
 
     
  1. строю усложняющуюся послідовність прикладів
  2.  
  3. пояснюю можливі варіанти застосування
  4.  
  5. пояснюю логіку рухало авторами (в міру можливості)
  6.  
  7. даю велику кількість тестів (50-100) всебічно перевіряє розуміння і демонструють різні комбінації
  8.  
  9. даю лабораторні для самостійної роботи
  10.  
Дана статті слід пунктам # 1 (послідовність прикладів) і # 2 (варіанти застосування).
 
 

Поїхали!

Навчальний приклад: постачити класи користувача мета-інформацією про «версії класу».
 
 Ітерація # 1:
Просто ставимо @ перед interface.
 
public @interface Version {}

 
 Ітерація # 2:
У анотацій можуть бути атрибути.
 
public @interface Version {
    public int version();
}

І заповнювати їх при використанні анотації
 
@Version(version = 42)
public class MyClass {}

Анотація вище повністю еквівалентна наступній (без public). У цьому анотації еквівалентні інтерфейсам: відсутність модифікатора області видимості автоматично означає public (а не package private як у класів).
 
public @interface Version {
    int version();
}

З protected і private — НЕ компілюється
 
public @interface Version {
    protected int version();
}
>> COMPILATION ERROR: Modifier 'protected' not allowed here

Далі я буду використовувати варіант без модифікатора public
 
 Ітерація # 3:
Якщо оголосити атрибут з ім'ям value, то його можна опускати при використанні
 
public @interface Version {
    public int value();
}

 
@Version(42)
public class MyClass {}

Хоча можна і по-старому
 
@Version(value = 42)
public class MyClass {}

 
 Ітерація # 4:
Для атрибута можна оголосити значення за замовчуванням
 
public @interface Version {
    int value();
    String author() default "UNKNOWN";
}

Тепер у нас два варіанти використання. Так
 
@Version(42)
public class MyClass {}

Або ось так
 
@Version(value = 42, author = "Jim Smith")
public class MyClass {
}

Але не ось так (слухай, прикро, так)
 
@Version(42, author = "Jim Smith")
public class MyClass {}
>> COMPILATION ERROR: Annotation attribute must be of the form 'name=value'

 
 Ітерація # 5:
Атрибути можуть мати тип масиву
 
public @interface Author {
    String[] value() default {};
}

 
@Author({"Anna", "Mike", "Sara"})
public class MyClass {}

Але тільки одновимірного
 
public @interface Author2D {
    String[][] value() default {};
}
>> COMPILATION ERROR: Invalid type of annotation member

 
 Ітерація # 6:
Можливий забавний трюк: анотація — атрибут анотації
 
public @interface Version {
    int value();
    String author() default "UNKNOWN";
}

 
public @interface History {
    Version[] value() default {};
}

Застосовується ось так
 
@History({
        @Version(1),
        @Version(value = 2, author = "Jim Smith")
})
public class MyClass {}

 
У анотацій багато обмежень. Перерахуємо деякі з них.
 
 

Обмеження: тип атрибута

1. Атрибути можуть мати тільки такі типи
 
     
  • примітиви
  •  
  • String
  •  
  • Class або «any parameterized invocation of Class»
  •  
  • enum
  •  
  • annotation
  •  
  • масив елементів будь-якого з перерахованих вище типів
  •  
Останній пункт треба розуміти як те, що допустимі тільки одномірні масиви.
 
Ну що ж, давайте діяти в рамках обмежень
 
 Ітерація # 7:
В якості типу атрибута не можна використовувати «звичайні» класи Java (за винятком java.lang.String і java.lang.Class), скажімо java.util.Date
 
import java.util.Date;

public @interface Version {
    Date date();
}
>> COMPILATION ERROR: Invalid type for annotation member

Але можна емулювати запису / структури на анотаціях
 
public @interface Date {
    int day();
    int month();
    int year();
}

 
public @interface Version {
    Date date();
}

 
@Date(year = 2001, month = 1, day = 1)
public class MyClass {}

 
 Ітерація # 8:
Атрибутом анотації може бути enum. З приємного, його можна оголосити в оголошенні анотації (як і в оголошенні інтерфейсу тут може бути оголошення enum, class, interface, annotation)
 
public @interface Colored {
    public enum Color {RED, GREEN, BLUE}
    Color value();
}

 
import static net.golovach.Colored.Color.RED;

@Colored(RED)
public class MyClass {}

 
 Ітерація # 9:
Атрибутом анотації може бути класовий літерал.
Анотація версії включає посилання на попередню версію класу.
 
public @interface Version {
    int value();
    Class<?> previous() default Void.class;
}

Перша версія класу
 
@Version(1)
public class ClassVer1 {}

Друга версія з посиланням на першого
 
@Version(value = 2, previous = ClassVer1.class)
public class ClassVer2 {}

/ / Так, я знаю, що нормальні люди не включають версію класу в ім'я класу. Але знаєте як нудно придумувати приклади узгоджені з реальною практикою?
 
 Ітерація # 10:
Менш тривіальний приклад з класовим літералом, де я не втримався і додав generic-ів.
Інтерфейс «серіалізатор» — того, хто може записати екземпляр T в байтовий потік виводу
 
import java.io.IOException;
import java.io.OutputStream;

public interface Serializer<T> {
    void toStream(T obj, OutputStream out) throws IOException;
}

Конкретний «серіалізатор» для класу MyClass
 
import java.io.IOException;
import java.io.OutputStream;

public class MySerializer implements Serializer<MyClass> {
    @Override
    public void toStream(MyClass obj, OutputStream out) throws IOException {
        throw new UnsupportedOperationException();
    }
}

Анотація, за допомогою якої ми «приклеюємо серіалізатор» до конкретного класу
 
public @interface SerializedBy {
    Class<? extends Serializer> value();
}

Ну і сам клас MyClass зазначений, як Серіалізуемое «своїм серіалізатор» MySerializer
 
@SerializedBy(MySerializer.class)
public class MyClass {}

 
 Ітерація # 11:
Складний приклад
 
public enum JobTitle {
    JUNIOR, MIDDLE, SENIOR, LEAD,
    UNKNOWN
}

 
public @interface Author {
    String value();
    JobTitle title() default JobTitle.UNKNOWN;
}

 
public @interface Date {
    int day();
    int month();
    int year();
}

 
public @interface Version {
    int version();
    Date date();
    Author[] authors() default {};
    Class<?> previous() default Void.class;
}

Ну і нарешті використання анотації
 
import static net.golovach.JobTitle.*;

@History({
        @Version(
                version = 1,
                date = @Date(year = 2001, month = 1, day = 1)),
        @Version(
                version = 2,
                date = @Date(year = 2002, month = 2, day = 2),
                authors = {@_8_Author(value = "Jim Smith", title = JUNIOR)},
                previous = MyClassVer1.class),
        @Version(
                version = 3,
                date = @Date(year = 2003, month = 3, day = 3),
                authors = {
                        @Author(value = "Jim Smith", title = MIDDLE),
                        @Author(value = "Anna Lea")},
                previous = MyClassVer2.class)
})
public class MyClassVer3 {}

 
 

Обмеження: значення атрибутів — константи часу компіляції / завантаження JVM

Повинна бути можливість обчислити значення атрибутів анотацій в момент компіляції або завантаження класу в JVM.
 
public @interface SomeAnnotation {
    int count();
    String name();
}

Приклад
 
@SomeAnnotation(
        count = 1 + 2,
        name = MyClass.STR + "Hello"
)
public class MyClass {
    public static final String STR = "ABC";
}

Ще приклад
 
@SomeAnnotation(
        count = (int) Math.PI,
        name = "" + Math.PI
)
public class MyClass {}

А ось виклики методів — це вже runtime, це вже заборонено
 
@SomeAnnotation(
        count = (int) Math.sin(1),
        name = "Hello!".toUpperCase()
)
public class MyClass {}
>> COMPILATION ERROR: Attribute value must be constant

 
 

Висновок

 Це перша частина статті про анотації в Java. У другій частині ми розглянемо такі теми:
 
     
  • Анотації, модифікують поведінку інших анотацій: @ Target, @ Retention, @ Documented, @ Inherited
  •  
  • Анотації, модифікують поведінку компілятора і JVM: @ Deprecated, @ Override, @ SafeVarargs, @ SuppressWarnings
  •  
  • Читання анотацій за допомогою Reflection API
  •  
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.