Dlaczego adnotacje zmieniły sposób programowania w Javie?
Przed Java 5 konfiguracja była w XML-ach, a metadata w komentarzach. Adnotacje to jak etykiety na produktach w sklepie – dają dodatkowe informacje bez zmiany zawartości. Spring, Hibernate i JUnit używają annotations zamiast XML configuration, co czyni kod bardziej readable i maintainable.
Co się nauczysz:
- Czym są adnotacje i jak działają w JVM
- Built-in annotations: @Override, @Deprecated, @SuppressWarnings
- Retention policies: SOURCE, CLASS, RUNTIME
- Tworzenie custom annotations
- Odczytywanie annotations przez reflection
Czym są adnotacje w Javie?
Adnotacje to forma metadata – dane o danych. Pozwalają na dodanie informacji do kodu bez wpływu na jego wykonanie. Kompilator, IDE, lub aplikacja w runtime może używać tych informacji do różnych celów.
Podstawowa składnia
// Najprostsza adnotacja - marker annotation @Override public void toString() { return "Example"; } // Adnotacja z single value @SuppressWarnings("unchecked") public List getRawList() { return new ArrayList(); } // Adnotacja z wieloma parametrami @RequestMapping(value = "/users", method = RequestMethod.GET) public String getUsers() { return "users"; } // Adnotacja z array values @Test(expected = {IllegalArgumentException.class, NullPointerException.class}) public void testExceptions() { // test code }
Built-in adnotacje Java
@Override – sprawdzanie nadpisywania
public class Animal { public void makeSound() { System.out.println("Some generic animal sound"); } } public class Dog extends Animal { @Override public void makeSound() { // Kompilator sprawdzi czy metoda istnieje w parent System.out.println("Woof!"); } // @Override // public void makeSond() { // BŁĄD KOMPILACJI - typo w nazwie metody // System.out.println("Woof!"); // } }
@Deprecated – oznaczanie przestarzałych elementów
public class Calculator { /** * @deprecated Użyj {@link #calculateAdvanced(int, int)} zamiast tej metody. * Ta metoda będzie usunięta w wersji 2.0. */ @Deprecated public int calculate(int a, int b) { return a + b; // Stara, prosta implementacja } public int calculateAdvanced(int a, int b) { // Nowa, lepsza implementacja z walidacją if (a < 0 || b < 0) { throw new IllegalArgumentException("Negative numbers not allowed"); } return a + b; } } // Użycie - IDE pokaże warning Calculator calc = new Calculator(); int result = calc.calculate(5, 3); // IDE: "calculate() is deprecated"
@SuppressWarnings - wyłączanie warningów
public class WarningExamples { @SuppressWarnings("unchecked") // Wyłącz unchecked warnings public ListgetLegacyList() { List rawList = getLegacyRawList(); // Raw type warning suppressed return (List ) rawList; // Unchecked cast warning suppressed } @SuppressWarnings({"unused", "deprecation"}) // Wiele warningów public void legacyCode() { String unusedVariable = "This won't show unused warning"; Calculator calc = new Calculator(); calc.calculate(1, 2); // Deprecated method warning suppressed } // Popularne typy warningów: // "all" - wszystkie warnings // "unchecked" - unchecked operations // "unused" - unused variables/methods // "deprecation" - deprecated API usage // "rawtypes" - raw types }
Retention Policies - gdzie żyją adnotacje
@Retention - określa lifecycle adnotacji
Retention Policy | Opis | Przykład użycia |
---|---|---|
SOURCE | Tylko w source code, usuwane przez kompilator | @Override, @SuppressWarnings |
CLASS | W .class files, ale nie w runtime | Annotation processors, bytecode manipulation |
RUNTIME | Dostępne w runtime przez reflection | @Test, @Autowired, @RequestMapping |
import java.lang.annotation.*; // SOURCE retention - tylko dla kompilator/IDE @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface SourceOnly { String value(); } // CLASS retention - dla annotation processors @Retention(RetentionPolicy.CLASS) // Default retention @Target(ElementType.TYPE) public @interface ClassLevel { String name(); } // RUNTIME retention - dla reflection w aplikacji @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface RuntimeAvailable { String description(); int priority() default 0; }
Tworzenie custom annotations
Przykład: @Benchmark annotation
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) // Musi być RUNTIME dla reflection @Target(ElementType.METHOD) // Tylko na metodach @Documented // Include w JavaDoc public @interface Benchmark { String description() default ""; // Optional parameter z default value int iterations() default 1; // Ile razy wykonać benchmark TimeUnit unit() default TimeUnit.MILLISECONDS; // Enum jako parameter boolean warmup() default true; // Boolean parameter String[] tags() default {}; // Array parameter } // Użycie annotation public class PerformanceTest { @Benchmark(description = "String concatenation test", iterations = 1000) public String testStringConcat() { String result = ""; for (int i = 0; i < 100; i++) { result += "test" + i; } return result; } @Benchmark(iterations = 500, unit = TimeUnit.MICROSECONDS, tags = {"io", "critical"}) public void testFileRead() throws IOException { Files.readAllLines(Paths.get("test.txt")); } }
@Target - gdzie można używać annotation
// Różne cele dla annotations @Target(ElementType.TYPE) // Klasy, interfaces, enums @Target(ElementType.METHOD) // Metody @Target(ElementType.FIELD) // Pola @Target(ElementType.PARAMETER) // Parametry metod @Target(ElementType.CONSTRUCTOR) // Konstruktory @Target(ElementType.PACKAGE) // Pakiety (package-info.java) // Można kombinować cele @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MultiTarget { String value(); } // Użycie na klasie @MultiTarget("This is a class") public class Example { // Użycie na metodzie @MultiTarget("This is a method") public void doSomething() { // implementation } }
Odczytywanie annotations przez reflection
Benchmark processor - praktyczny przykład
import java.lang.reflect.Method; public class BenchmarkProcessor { public void processBenchmarks(Object testInstance) { Class> clazz = testInstance.getClass(); // Znajdź wszystkie metody z @Benchmark for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(Benchmark.class)) { // Odczytaj annotation Benchmark benchmark = method.getAnnotation(Benchmark.class); System.out.println("Running benchmark: " + method.getName()); System.out.println("Description: " + benchmark.description()); System.out.println("Iterations: " + benchmark.iterations()); // Wykonaj benchmark runBenchmark(testInstance, method, benchmark); } } } private void runBenchmark(Object instance, Method method, Benchmark benchmark) { try { int iterations = benchmark.iterations(); boolean warmup = benchmark.warmup(); // Warmup round if (warmup) { System.out.println("Warmup..."); method.invoke(instance); } // Actual benchmark long startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { method.invoke(instance); } long endTime = System.nanoTime(); long duration = endTime - startTime; // Convert to specified time unit TimeUnit unit = benchmark.unit(); long convertedDuration = convertTime(duration, unit); System.out.printf("Benchmark completed: %d %s for %d iterations%n", convertedDuration, unit.name().toLowerCase(), iterations); // Process tags String[] tags = benchmark.tags(); if (tags.length > 0) { System.out.println("Tags: " + String.join(", ", tags)); } } catch (Exception e) { System.err.println("Benchmark failed: " + e.getMessage()); } } private long convertTime(long nanos, TimeUnit unit) { switch (unit) { case NANOSECONDS: return nanos; case MICROSECONDS: return nanos / 1000; case MILLISECONDS: return nanos / 1_000_000; case SECONDS: return nanos / 1_000_000_000; default: return nanos; } } } // Użycie benchmark processor public class BenchmarkExample { public static void main(String[] args) { PerformanceTest test = new PerformanceTest(); BenchmarkProcessor processor = new BenchmarkProcessor(); processor.processBenchmarks(test); } }
Sprawdzanie wszystkich annotations na klasie
public class AnnotationInspector { public void inspectClass(Class> clazz) { System.out.println("Inspecting class: " + clazz.getSimpleName()); // Annotations na klasie Annotation[] classAnnotations = clazz.getAnnotations(); System.out.println("Class annotations: " + classAnnotations.length); for (Annotation annotation : classAnnotations) { System.out.println(" - " + annotation.annotationType().getSimpleName()); } // Annotations na metodach for (Method method : clazz.getDeclaredMethods()) { Annotation[] methodAnnotations = method.getAnnotations(); if (methodAnnotations.length > 0) { System.out.println("Method " + method.getName() + " annotations:"); for (Annotation annotation : methodAnnotations) { System.out.println(" - " + annotation.annotationType().getSimpleName()); } } } } }
SOURCE i CLASS annotations nie wpływają na runtime performance. RUNTIME annotations mają minimalny overhead - są ładowane z classfile, ale reflection calls to read them mogą być kosztowne jeśli używane często.
Gdy chcesz stworzyć framework, dodać metadata do code (jak @Test w JUnit), lub zastąpić XML configuration. Custom annotations są świetne dla aspect-oriented programming i dependency injection.
Tak! To nazywa się meta-annotations. @Retention, @Target, @Documented są meta-annotations. Możesz tworzyć composed annotations jak @RestController (która łączy @Controller i @ResponseBody).
Tool który runs podczas compilation i może generate code based na annotations. Lombok używa annotation processors do generowania getters/setters. To bardziej zaawansowany topic niż reflection.
Nie bezpośrednio, ale możesz użyć @Inherited meta-annotation żeby annotation była inherited przez subclasses. Default behavior to brak inheritance.
🚀 Zadanie dla Ciebie
Stwórz własny mini-framework do testowania:
- @Test annotation z parameters: timeout, expected exception
- @BeforeEach i @AfterEach annotations dla setup/cleanup
- TestRunner który używa reflection do uruchamiania testów
- Report generation - ile testów passed/failed
- Bonus: @Disabled annotation do pomijania testów
Test framework na simple Calculator class z metodami add/subtract/divide. Include edge cases jak division by zero.
Przydatne zasoby:
Których adnotacji używasz najczęściej w swoim kodzie? Czy tworzyłeś już własne custom annotations? Do czego je wykorzystujesz?