We've already learned what annotations are and when they are used. Do you remember any of them? The most used ones in the Java library are @Deprecated and @Override, but there are many other annotations in third-party libraries. For example, the Spring library has lots of them, and there's a useful @Test annotation for testing Java classes. You can quickly go through this page's code examples just to see how often they might be used in practice. Now let's find out how they actually work and what magic happens when they are present.
getDeclaredAnnotations method
Actually, annotations are just markers: they indicate that some class, method, or field has special properties. These annotations are further analyzed with the help of reflection when compiling or running the program.
Let's look at the following class:
class Item {
@Deprecated
public static final int maxItems = 100;
public static int inStock = 19;
private String name;
protected int basePrice;
public Item(String name, int basePrice) {
this.name = name;
this.basePrice = basePrice;
}
public String getName() {
return name;
}
public int getPrice() {
return (int) (basePrice * getMarkUp());
}
public boolean haveSuchAmount(@Deprecated int amount) {
return inStock >= amount;
}
@Deprecated
protected double getMarkUp() {
double markUp = 0.1;
// ... connecting to the remote server
return 1 + markUp;
}
@Override
public String toString() {
return getName() + " " + getPrice();
}
}It is recommended to deprecate a method instead of its parameters for the sake of clarity.
It has three @Deprecated annotations and one @Override annotation. Let's try to analyze them by using reflection.
For working with annotations, Class, Method, Field and Constructor classes have the getDeclaredAnnotations() method, that returns an array of Annotation objects. This method returns an array, not an object because multiple annotations can be applied to a single field or method. If you follow the link above, the first example there will contain a huge pile of six annotations before the class!
The Annotation class provides a useful method called annotationType that returns a Class object representing the annotation. It can be useful if you need to access all elements of the annotation or just get its name.
Code example
Let’s execute the following code. Here we go through all the names of methods and fields of our Item class and apply the getDeclaredAnnotations() method to get their annotations and print them next to the respective names:
Class itemClass = Item.class;
for (Field field : itemClass.getDeclaredFields()) {
for (Annotation annotation : field.getDeclaredAnnotations()) {
System.out.print(annotation + " - ");
}
System.out.println(field.getName());
}
for (Method method : itemClass.getDeclaredMethods()) {
for (Annotation annotation : method.getDeclaredAnnotations()) {
System.out.print(annotation + " - ");
}
System.out.println(method.getName());
}Take a look at the output:
@java.lang.Deprecated(forRemoval=false, since="") - maxItems
inStock
name
basePrice
toString
getName
@java.lang.Deprecated(forRemoval=false, since="") - getMarkUp
getPrice
haveSuchAmountYou can see that though we had three instances of annotations in our class, we manage to output only two of them. @Deprecated appeared in line with maxItems field and getMarkUp method as we might have expected. However, for some reason, there is no @Override toString line in here. Let’s try to find out why by ourselves and this way learn more about annotations and reflection. We may suppose that the absence of @Override has to do with its method: actually, toString is not declared in this class, but in the Object. To check our hypotheses let’s use another method for getting class methods getMethods. As you probably remember, it returns all public methods of the class, including the inherited ones.
for (Method method : itemClass.getMethods()) {
for (Annotation annotation : method.getDeclaredAnnotations()) {
System.out.print(annotation + " - ");
}
System.out.println(method.getName());
}If our conjecture is correct, this time we will get the @Override annotation for toString method. But look what we get by the above snippet:
toString
getName
getPrice
haveSuchAmount
wait
wait
wait
equals
@jdk.internal.HotSpotIntrinsicCandidate() - hashCode
@jdk.internal.HotSpotIntrinsicCandidate() - getClass
@jdk.internal.HotSpotIntrinsicCandidate() - notify
@jdk.internal.HotSpotIntrinsicCandidate() - notifyAllStill, something is not right! As you see, we now got four methods with unfamiliar annotations, and the toString method still has no annotation. Let’s not focus on the ones containing the words jdk and internal, we have no intention to go there for now and just reproduce them for the sake of credibility. Note that this time we didn’t get getMarkUp method at all since it is protected.
Hence we may conclude that the reason lies in the nature of the annotation itself. Indeed, @Override annotation is a compile-time annotation and is not present in the class file after compilation. While @Deprecated is present during the execution of the program, which means that it is a runtime annotation.
Conclusion
Reflection package provides functionality to retrieve annotations from the source code. Class, Field and Method classes have the getDeclaredAnnotations method, which returns a list of configured runtime annotations. Other annotations are not available at runtime because the compiler erases them.