Annotations and Reflection
Built-in Annotations
Section titled “Built-in Annotations”@Override
Section titled “@Override”@Override (JDK 5) marks a method as intending to override a method from a superclass or implement A method from an interface. The compiler verifies the declaration actually overrides something:
public class Dog { @Override public String toString() { // compiles — Object.toString() exists return "Dog"; }
@Override public boolean equals(Dog other) { // compile error — does not override Object.equals(Object) return false; }}On interfaces (JDK 6+), @Override on a method declaration indicates the method is intended to Implement a method from a super-interface:
public interface Animal { void speak();}
public interface LoudAnimal extends Animal { @Override void speak(); // valid — overrides Animal.speak()}Always use @Override on every overriding method. It catches typos and signature mismatches at Compile time.
@Deprecated
Section titled “@Deprecated”@Deprecated (JDK 5) marks a program element as obsolete. The compiler emits a warning when Deprecated elements are used or overridden:
public class LegacyService { @Deprecated(since = "3.0", forRemoval = true) public void oldMethod() { // do not use — will be removed in a future release }
@Deprecated(since = "3.0", forRemoval = false) public void deprecatedMethod() { // still works, but prefer newMethod() }}Parameters (all optional, added in JDK 9):
since: version since which the element has been deprecatedforRemoval: whether the element is intended for removal in a future version
@SuppressWarnings("deprecation") suppresses the warning at a usage site.
@SuppressWarnings
Section titled “@SuppressWarnings”@SuppressWarnings (JDK 5) suppresses compiler warnings at the declaration level. Common keys:
| Key | Suppresses |
|---|---|
"unchecked" | Unchecked casts and raw type usage |
"deprecation" | Use of deprecated APIs |
"rawtypes" | Raw type usage (subset of unchecked) |
"unused" | Unused variables, methods, parameters |
"serial" | Missing serialVersionUID in Serializable classes |
"varargs" | Heap pollution from varargs with non-reifiable types |
Apply at the narrowest scope possible:
public class Config { @SuppressWarnings("unchecked") public <T> T getSetting(String key, Class<T> type) { Object value = rawMap.get(key); return type.cast(value); }}@FunctionalInterface
Section titled “@FunctionalInterface”@FunctionalInterface (JDK 8) marks an interface as intended to be a functional interface (SAM type — single abstract method). The compiler enforces that the interface has exactly one abstract method:
@FunctionalInterfacepublic interface Predicate<T> { boolean test(T t);
// default methods don"t count default Predicate<T> and(Predicate<? super T> other) { return t -> test(t) && other.test(t); }
// static methods don't count static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); }}@FunctionalInterfacepublic interface Broken { void run(); void stop(); // compile error — multiple abstract methods}The annotation is optional (any interface with one abstract method is a functional interface), but It makes intent explicit and catches accidental additions.
Other Built-in Annotations
Section titled “Other Built-in Annotations”@SafeVarargs(JDK 7) — suppresses heap pollution warnings on varargs methods/constructors. Must be onfinal``staticOrprivatemethods (or constructors). Applying it on non-final instance methods is a compile error (JDK 9+).@Native(JDK 8) — marks astatic finalfield as a constant that may be referenced from native code. The field must be initialized to a compile-time constant.@Repeatable(JDK 8) — covered in Meta-Annotations.
Meta-Annotations
Section titled “Meta-Annotations”Meta-annotations are annotations that apply to other annotation declarations. They control how Custom annotations behave.
@Retention
Section titled “@Retention”@Retention (JDK 5) specifies how long an annotation is retained. Takes a RetentionPolicy enum Value:
| Policy | Retained through |
|---|---|
SOURCE | Discarded by the compiler (not in .class files) |
CLASS | Retained in .class files but not loaded at runtime (default) |
RUNTIME | Available at runtime via reflection |
@Retention(RetentionPolicy.SOURCE)public @interface Inline { }
@Retention(RetentionPolicy.CLASS)public @interface Optimizable { } // not readable at runtime
@Retention(RetentionPolicy.RUNTIME)public @interface Column { String name(); } // readable via reflectionIf you need to read annotations at runtime (for framework processing, serialization config, etc.), Use RUNTIME. If annotations are purely for compile-time checking (lint rules, code generation), SOURCE is appropriate. CLASS is rarely used directly — it exists for tools that process class Files without loading them into the JVM.
@Target
Section titled “@Target”@Target (JDK 5) specifies which program elements an annotation can be applied to. Takes ElementType enum values (combinable with OR):
| ElementType | Applies to |
|---|---|
TYPE | Classes, interfaces, enums, records |
FIELD | Fields (including enum constants) |
METHOD | Methods |
PARAMETER | Method/constructor parameters |
CONSTRUCTOR | Constructors |
LOCAL_VARIABLE | Local variables |
ANNOTATION_TYPE | Annotation declarations |
PACKAGE | Package declarations (in package-info.java) |
TYPE_PARAMETER (JDK 8) | Type parameter declarations |
TYPE_USE (JDK 8) | Any type use (generics, casts, implements, throws) |
MODULE (JDK 9) | Module declarations |
RECORD_COMPONENT (JDK 16) | Record components |
@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)public @interface NotNull { }TYPE_USE is powerful — it lets you annotate any use of a type:
List<@NotNull String> names;Map<@NotNull String, @Nullable Integer> data;void process(@NotNull String input) throws @NotNull IOException;@Inherited
Section titled “@Inherited”@Inherited (JDK 5) causes an annotation to be inherited by subclasses. Only applies to class Declarations:
@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Audit { }
@Auditpublic class BaseService { }
public class UserService extends BaseService { }// UserService inherits @Audit from BaseServiceLimitations:
- Only works for class-level annotations, not methods or fields.
- Only applies to direct inheritance — interfaces implementing other interfaces do not inherit annotations.
- A subclass annotation overrides the inherited one.
@Repeatable
Section titled “@Repeatable”@Repeatable (JDK 8) allows the same annotation to be applied multiple times to the same element. Requires a container annotation:
@Repeatable(Schedules.class)@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Schedule { String cron(); String zone() default "UTC";}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Schedules { Schedule[] value();}Usage:
@Schedule(cron = "0 0 8 * * ?", zone = "America/New_York")@Schedule(cron = "0 0 20 * * ?", zone = "Asia/Tokyo")public void runDailyReport() { }At runtime, the container annotation is what reflection sees by default. Use AnnotatedElement.getAnnotationsByType() to get individual repeatable annotations regardless of Whether they are stored individually or in a container:
Schedule[] schedules = MyClass.class.getMethod("runDailyReport") .getAnnotationsByType(Schedule.class);Custom Annotation Definition
Section titled “Custom Annotation Definition”Defining an Annotation Type
Section titled “Defining an Annotation Type”An annotation type is declared with @interface. Methods define the annotation’s elements (the Attributes you set when using the annotation). Methods have no parameters, no throws clauses, and Return a limited set of types:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface Config { String name(); int priority() default 0; String[] tags() default {}; Class<?> handler() default Void.class;}Allowed return types for annotation elements:
- All primitives (
int``long``booleanEtc.) StringClass<?>(orClass<? extends SomeType>)- Enums
- Annotations
- Arrays of any of the above
Using the Annotation
Section titled “Using the Annotation”@Config(name = "payment-service", priority = 1, tags = {"critical", "financial"})public class PaymentService {
@Config(name = "processPayment", tags = "core") public void processPayment(Order order) { }}Default values can be omitted. Single-element annotations commonly use value():
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Log { String value(); Level level() default Level.INFO;}
// Usage — 'value' allows shorthand@Log("processing order")@Log(value = "processing order", level = Level.DEBUG)public void process(Order order) { }Annotation Processing at Compile Time
Section titled “Annotation Processing at Compile Time”The Annotation Processing API (javax.annotation.processing) runs during compilation. Build Tools like Lombok, MapStruct, and AutoValue use this. Register the processor in META-INF/services/javax.annotation.processing.Processor. Most projects use existing processors Rather than writing their own.
Runtime Annotation Processing
Section titled “Runtime Annotation Processing”Reading annotations at runtime with reflection:
Config classConfig = PaymentService.class.getAnnotation(Config.class);System.out.println(classConfig.name()); // "payment-service"
for (Method method : PaymentService.class.getDeclaredMethods()) { if (method.isAnnotationPresent(Config.class)) { Config config = method.getAnnotation(Config.class); System.out.println(method.getName() + ": " + config.name()); }}Reflection API
Section titled “Reflection API”Class Objects
Section titled “Class Objects”java.lang.Class<T> is the entry point for reflection. Every type has a corresponding Class Instance:
// Getting a Class objectClass<String> c1 = String.class;Class<?> c2 = Class.forName("java.lang.String");Class<?> c3 = "hello".getClass();Class<?> c4 = String[].class;Class<?> c5 = int.class;Class<?> c6 = int[].class;Class<?> c7 = void.class;Key methods on Class:
Class<?> clazz = MyClass.class;
String name = clazz.getName(); // fully qualified nameString simpleName = clazz.getSimpleName(); // simple namePackage pkg = clazz.getPackage(); // package infoClass<?> superclass = clazz.getSuperclass();Class<?>[] interfaces = clazz.getInterfaces();int modifiers = clazz.getModifiers(); // Modifier flagsboolean isInterface = clazz.isInterface();boolean isArray = clazz.isArray();boolean isPrimitive = clazz.isPrimitive();boolean isEnum = clazz.isEnum();boolean isAnnotation = clazz.isAnnotation();boolean isRecord = clazz.isRecord(); // JDK 16+Class<?> componentType = clazz.getComponentType(); // for arraysField Introspection
Section titled “Field Introspection”Field nameField = Person.class.getDeclaredField("name");nameField.setAccessible(true);String name = (String) nameField.get(person); // readnameField.set(person, "Bob"); // write
Class<?> type = nameField.getType();boolean isFinal = Modifier.isFinal(nameField.getModifiers());Method Introspection
Section titled “Method Introspection”Method substring = String.class.getMethod("substring", int.class, int.class);String result = (String) substring.invoke("hello world", 0, 5);Class<?> returnType = substring.getReturnType();Class<?>[] paramTypes = substring.getParameterTypes();Constructor Introspection
Section titled “Constructor Introspection”Constructor<ArrayList> noArgCtor = ArrayList.class.getConstructor();ArrayList<String> list = noArgCtor.newInstance();Array and Primitive Reflection
Section titled “Array and Primitive Reflection”// Array operationsint[] arr = (int[]) Array.newInstance(int.class, 10);Array.setInt(arr, 0, 42);int value = Array.getInt(arr, 0);int length = Array.getLength(arr);
// Primitive wrapper classesClass<Integer> intClass = int.class; // primitiveClass<Integer> intWrapper = Integer.class; // wrapperintClass != intWrapper; // true — different Class objectsRuntime Type Introspection Patterns
Section titled “Runtime Type Introspection Patterns”Generic Type Resolution
Section titled “Generic Type Resolution”While generic type parameters are erased, you can recover them from field declarations, method Return types, and class hierarchies using java.lang.reflect.Type:
import java.lang.reflect.*;import java.util.*;
public class TypeInspector {
public static void inspectFields(Class<?> clazz) { for (Field field : clazz.getDeclaredFields()) { Type genericType = field.getGenericType(); System.out.println(field.getName() + ": " + genericType.getTypeName());
if (genericType instanceof ParameterizedType pt) { Type rawType = pt.getRawType(); Type[] typeArgs = pt.getActualTypeArguments(); for (Type arg : typeArgs) { System.out.println(" arg: " + arg.getTypeName()); } } } }}
// For a class with: Map<String, List<Integer>> data;// inspectFields prints:// data: java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>// arg: java.lang.String// arg: java.util.List<java.lang.Integer>Annotation-Driven Dispatch
Section titled “Annotation-Driven Dispatch”A common pattern in frameworks: scan for annotations and build dispatch tables at startup:
public class CommandDispatcher { private final Map<String, Method> handlers = new HashMap<>();
public CommandDispatcher(Object target) { for (Method method : target.getClass().getDeclaredMethods()) { Command annotation = method.getAnnotation(Command.class); if (annotation != null) { method.setAccessible(true); handlers.put(annotation.value(), method); } } }
public Object dispatch(String command, Object... args) throws Exception { Method handler = handlers.get(command); if (handler == null) { throw new IllegalArgumentException("Unknown command: " + command); } return handler.invoke(null, args); }}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface Command { String value();}Bean Property Introspection
Section titled “Bean Property Introspection”java.beans.Introspector (standard library since JDK 1.1) extracts JavaBean properties from a class Based on getter/setter conventions:
import java.beans.BeanInfo;import java.beans.Introspector;import java.beans.PropertyDescriptor;
public class BeanUtils {
public static Map<String, Object> toMap(Object bean) throws Exception { Map<String, Object> map = new LinkedHashMap<>(); BeanInfo info = Introspector.getBeanInfo(bean.getClass(), Object.class); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { Method reader = pd.getReadMethod(); if (reader != null) { map.put(pd.getName(), reader.invoke(bean)); } } return map; }}Classpath Scanning
Section titled “Classpath Scanning”Reflection enables runtime classpath scanning (used by Spring, Jersey, etc.) by reading JAR entries Or directory listings. Real implementations must handle JAR files, modules, and edge cases. Libraries like Reflections or Spring’s ClassPathScanningCandidateComponentProvider handle these Complexities.
Dynamic Proxy
Section titled “Dynamic Proxy”JDK Dynamic Proxy
Section titled “JDK Dynamic Proxy”java.lang.reflect.Proxy (JDK 1.3) creates proxy instances for interfaces at runtime. The proxy Dispatches all method calls to an InvocationHandler:
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;
public interface UserService { String getUserName(long id); void saveUser(String name);}
public class LoggingInvocationHandler implements InvocationHandler { private final Object target;
public LoggingInvocationHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(">> " + method.getName() + " called"); long start = System.nanoTime(); try { Object result = method.invoke(target, args); System.out.println("<< " + method.getName() + " returned in " + (System.nanoTime() - start) + "ns"); return result; } catch (Exception e) { System.out.println("<< " + method.getName() + " threw " + e.getCause()); throw e.getCause(); } }}Creating and using a proxy:
UserService realService = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class<?>[] { UserService.class }, new LoggingInvocationHandler(realService));
proxy.getUserName(42);// >> getUserName called// << getUserName returned in 1523nsLimitations:
- Only works with interfaces, not concrete classes. For class-based proxying, use CGLIB or ByteBuddy.
equals``hashCodeAndtoStringare also dispatched to the handler (default implementations inProxy).- The proxy object is an instance of
java.lang.reflect.Proxyand all specified interfaces.
Common Proxy Use Cases
Section titled “Common Proxy Use Cases”AOP (Spring AOP, transaction management), RPC stubs, Hibernate lazy loading, Mockito/EasyMock test Doubles, and access control (permission checks before method execution).
Proxy Class Caching
Section titled “Proxy Class Caching”JDK proxies cache generated proxy classes (named $Proxy0``$Proxy1Etc.). Set sun.misc.ProxyGenerator.saveGeneratedFiles=true to write generated proxy class files to disk for Debugging.
MethodHandle and VarHandle
Section titled “MethodHandle and VarHandle”MethodHandle (JDK 7)
Section titled “MethodHandle (JDK 7)”java.lang.invoke.MethodHandle is a typed, directly executable reference to a method, field, or Constructor. It is the low-level mechanism that the JVM uses internally for invokedynamic.
import java.lang.invoke.MethodHandle;import java.lang.invoke.MethodHandles;import java.lang.invoke.MethodType;
public class MethodHandleExample {
public static String greet(String name) { return "Hello, " + name; }
public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle greet = lookup.findStatic( MethodHandleExample.class, "greet", MethodType.methodType(String.class, String.class) );
String result = (String) greet.invokeExact("World"); System.out.println(result); // "Hello, World"
String result2 = (String) greet.invoke("World"); System.out.println(result2); // "Hello, World" }}invokeExact requires the exact argument types (no implicit conversions) and the return type must Match exactly. invoke allows implicit conversions as defined by the JVM’s method handle adapters.
MethodHandle vs Reflection
Section titled “MethodHandle vs Reflection”| Aspect | MethodHandle | Reflection (Method) |
|---|---|---|
| Performance | Can be JIT-optimized, close to direct invocation | Slower, no JIT optimization |
| Access control | Checked at creation time | Checked at every invocation |
| Type safety | Enforced at invoke time | Weaker |
| Flexibility | Adapters for currying, binding | No built-in adapters |
| Learning curve | Steeper | Simpler API |
For performance-critical code, MethodHandle is preferred over Method.invoke(). The JIT can Inline method handles , especially after warmup.
MethodHandle Adapters
Section titled “MethodHandle Adapters”MethodHandles.Lookup lookup = MethodHandles.lookup();MethodHandle greeter = lookup.findStatic( MethodHandleExample.class, "greet", MethodType.methodType(String.class, String.class));
MethodHandle helloGreeter = greeter.bindTo("World");String result = (String) helloGreeter.invokeExact(); // "Hello, World"
MethodHandle insertArg = MethodHandles.insertArguments(greeter, 0, "World");result = (String) insertArg.invokeExact(); // "Hello, World"
MethodHandle asVoid = MethodHandles.dropReturn(greeter);
MethodHandle filter = MethodHandles.filterArguments(greeter, 0, lookup.findStatic(String.class, "toUpperCase", MethodType.methodType(String.class, String.class)));result = (String) filter.invokeExact("world"); // "Hello, WORLD"
MethodHandle constant = MethodHandles.constant(String.class, "42");result = (String) constant.invokeExact(); // "42"VarHandle (JDK 9)
Section titled “VarHandle (JDK 9)”java.lang.invoke.VarHandle provides fine-grained access to fields and array elements with Volatile/atomic semantics. It replaces sun.misc.Unsafe field access operations:
import java.lang.invoke.MethodHandles;import java.lang.invoke.VarHandle;
public class AtomicCounter { private volatile long value;
private static final VarHandle VH;
static { try { VH = MethodHandles.lookup() .findVarHandle(AtomicCounter.class, "value", long.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } }
public long increment() { return VH.getAndAdd(this, 1L); }
public long get() { return (long) VH.getVolatile(this); }
public void set(long newValue) { VH.setVolatile(this, newValue); }
public boolean compareAndSet(long expected, long newValue) { return VH.compareAndSet(this, expected, newValue); }}VarHandle modes:
| Method | Memory semantics |
|---|---|
get / set | Plain (no ordering guarantees) |
getVolatile / setVolatile | Volatile read/write |
getAcquire / setRelease | Acquire/release semantics |
getOpaque / setOpaque | Opaque (no reordering, no visibility) |
compareAndSet | Atomic CAS |
getAndAdd``getAndSet | Atomic RMW |
Array VarHandles:
VarHandle arrayVH = MethodHandles.arrayElementVarHandle(int[].class);int[] arr = {1, 2, 3};arrayVH.set(arr, 1, 42); // arr[1] = 42int val = (int) arrayVH.getVolatile(arr, 1); // volatile readInvokedynamic (JDK 7)
Section titled “Invokedynamic (JDK 7)”invokedynamic is a JVM bytecode instruction that defers method linkage to runtime. Foundation for Lambda expressions (JDK 8), dynamic languages (Groovy, JRuby), and string concatenation (JDK 9+). You rarely use it directly unless building a language runtime — the JVM links the call site through A CallSite object and a bootstrap method.
Performance Implications of Reflection
Section titled “Performance Implications of Reflection”Reflection vs Direct Access
Section titled “Reflection vs Direct Access”Reflection is significantly slower than direct method calls, especially before JIT warmup. The Overhead comes from:
- Permission checks: Security manager checks (if enabled)
- Method resolution: Looking up the method in the class’s method table
- Argument boxing/unboxing: For primitive parameters
- No inlining: The JIT generally cannot inline reflective calls
Benchmark (approximate, HotSpot, after warmup):
Direct call: ~2 nsMethodHandle.invoke: ~5-10 nsMethod.invoke: ~20-50 nsMethodHandle.invokeExact (warmed up): ~2-5 nsThese numbers vary significantly with JVM version, flags, and workload. Always benchmark for your Specific case.
Mitigating Reflection Overhead
Section titled “Mitigating Reflection Overhead”Cache Method objects — look up once, reuse. Avoid getMethod() inside loops.
Use MethodHandle instead of Method.invoke() — JIT can optimize invokeExact call sites.
Code generation (ByteBuddy, CGLIB, ASM) eliminates reflection entirely by generating bytecode at Runtime. This is what Hibernate, Spring, and Mockito do for performance-critical paths.
Reflection and JIT Optimization
Section titled “Reflection and JIT Optimization”The JIT compiler cannot optimize through reflection boundaries — a reflective call site is an opaque Barrier. The called method is not inlined, escape analysis doesn’t work across it, and loop Optimizations cannot see through reflective dispatch. MethodHandle.invokeExact is an exception: The JIT can sometimes inline through it because the type is known at the call site.
Common Pitfalls
Section titled “Common Pitfalls”Security Manager and Access Control
Section titled “Security Manager and Access Control”setAccessible(true) bypasses Java’s access control. In security-managed environments (JDK 16+ with Strong encapsulation), this throws InaccessibleObjectException for module-internal classes:
java --add-opens java.base/java.lang=ALL-UNNAMED MyAppAvoid relying on setAccessible(true) for JDK-internal classes. The correct approach is to use Public APIs.
IllegalAccessException on Private Members
Section titled “IllegalAccessException on Private Members”Attempting to access private members without setAccessible(true) throws IllegalAccessException:
Field field = clazz.getDeclaredField("secret");field.setAccessible(true); // required for private fieldsfield.get(obj); // OKPerformance Degradation
Section titled “Performance Degradation”Reflection in hot paths causes measurable performance degradation. Common mistake: putting Reflection inside a loop that processes millions of items:
// BADfor (Object item : items) { Method getter = item.getClass().getMethod("getValue"); Object value = getter.invoke(item); // reflection on every iteration}
// GOOD — cache the method handle outside the loopMethodHandle getter = MethodHandles.lookup() .findVirtual(item.getClass(), "getValue", MethodType.methodType(Object.class));for (Object item : items) { Object value = getter.invoke(item); // much faster after warmup}Caching Reflection Results with Wrong Keys
Section titled “Caching Reflection Results with Wrong Keys”Method lookups are parameter-sensitive. getMethod("foo", String.class) and getMethod("foo", Object.class) return different methods. Always include parameter types in your Cache key:
String key = "foo:" + Arrays.toString(paramTypes); // CORRECTReflective Access to Generics
Section titled “Reflective Access to Generics”After type erasure, you cannot cast to a generic type through reflection without unchecked warnings:
Field field = clazz.getDeclaredField("list");field.setAccessible(true);// This is an unchecked cast — the actual runtime type is List (raw)List<String> list = (List<String>) field.get(obj);The cast succeeds at runtime because erasure reduces it to (List). If the field actually contains A List<Integer>You get a ClassCastException later when accessing elements, not at the cast Site.
Primitive Type Handling
Section titled “Primitive Type Handling”Method.invoke() boxes primitives automatically, which costs in tight loops. Use Field.setInt() / Field.getInt() for primitive fields. Method.invoke always boxes — consider MethodHandle for Performance-critical paths.
Overloaded Methods and Ambiguity
Section titled “Overloaded Methods and Ambiguity”Class.getMethod() requires exact parameter types. For overloaded methods, specify the correct Parameter types:
Method m1 = clazz.getMethod("process", String.class);Method m2 = clazz.getMethod("process", int.class);// clazz.getMethod("process"); // throws NoSuchMethodExceptionProxy Equals and hashCode
Section titled “Proxy Equals and hashCode”JDK dynamic proxies delegate equals``hashCodeAnd toString to the InvocationHandler. Always Handle these methods in your handler if proxy identity semantics matter:
InvocationHandler handler = (proxy, method, args) -> { if (method.getName().equals("toString")) return "Proxy[" + target + "]"; return method.invoke(target, args);};Module System Restrictions (JDK 9+)
Section titled “Module System Restrictions (JDK 9+)”The module system restricts reflective access to non-exported packages, affecting deep reflection Into JDK internals (e.g., sun.misc.Unsafe) and frameworks that access private members of library Classes. The long-term fix is to use public APIs instead of reaching into internals via reflection.
Summary
Section titled “Summary”This topic covers the core concepts of annotations and reflection, including underlying theory, practical implementation, and key applications.
Key concepts include:
- CPU architecture and the fetch-decode-execute cycle
- memory hierarchy (cache, RAM, virtual)
- input/output systems
- operating systems and scheduling
- interrupts and polling
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Intuition
Section titled “Intuition”Annotations are like sticky notes on your code — they do not change what the code does, but they tell frameworks and tools how to treat it. @Override is a safety net that catches typos in method signatures. @Deprecated is a farewell letter warning future developers not to use this code. Reflection is the ability to read those sticky notes at runtime and act on them — this is how Spring discovers your @Controller classes, how JPA maps your @Entity classes to database tables, and how Mockito creates test doubles. The performance cost of reflection means it is best used at startup (framework initialization) rather than in hot paths.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- Generics: Type erasure and generic type tokens used in reflective type inspection.
- Concurrency Deep Dive: MethodHandle and VarHandle for low-level concurrent field access.
- Testing: Reflection-based test doubles and annotation-driven test configuration.