Type Annotations in Java
Only declarations were eligible for annotations in earlier versions of Java. With Java 8, you may now annotate any type use, including types in declarations, generics, and casts:
@Encrypted String data;
List<@NonNull String> strings;
my_graph = (@Immutable Graph) tmpgraph;
Type annotations aren't the most attractive feature of the new Java release, at least not at first glance. Annotations are simply syntax, after all! Tools provide the semantics of annotations (i.e., their meaning and behavior). The new type annotation syntax and useful tools are introduced in this article to increase productivity and create software of higher quality.
Time to market is more crucial than ever in the financial sector because to the shifting regulatory and market conditions. However, giving up security or quality is not an option; even a simple misunderstanding of percentages and basis points might have negative effects. Every other industry is experiencing the same situation.
Annotations are undoubtedly already being used by Java programmers to raise the calibre of their code. In Java 1.5, the @Override annotation was first introduced. It might be challenging to keep track of which implementation of a method will be used at runtime in large projects with complex inheritance trees. Modifying a method declaration could, if done carelessly, prevent a subclass method from being invoked. By doing away with a method call in this way, a flaw or security hole may be created. Developers were able to describe methods as overriding a superclass method as a result of the introduction of the @Override annotation. If the application doesn't follow the developer's goals, the Java compiler will use the annotations to alert them.
Through methods like metaprogramming, annotations have also been crucial in increasing developer productivity. Annotations are supposed to be able to instruct tools on how to write new code, modify existing code, or act when the tool is running. For instance, utilising annotations on declarations like @Entity, the Java Persistence API (JPA), which was included in Java 1.5, enables developers to declaratively express the relationship between Java objects and database entities. These annotations enable run-time generation of SQL queries and mapping files by tools like Hibernate.
Annotations are used to support the DRY (Don't Repeat Yourself) principle in the context of JPA and Hibernate. Annotations are surprisingly easy to discover whenever you go for tools to assist development best practises! Examples include using Dependency Injection to lessen coupling and Aspect Oriented Programming to separate concerns.
Type Annotation SYNTAX:
This is the basic syntax,
@Encrypted String data;
List<@NonNull String> strings;
my_graph = (@Immutable Graph) tmpgraph;
It is as simple as defining an annotation with the ElementType to add a new type of annotation. Either the ElementType.TYPE USE target, the TYPE PARAMETER target, or both targets
@Target ({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface Encrypted { }
The target ElementType.TYPE PARAMETER indicates that the annotation can be added to a type variable's declaration, such as class MyClassT>.... Any use of a type may be the subject of an annotation, according to the ElementType.TYPE USE target (e.g., types appearing in declarations, generics, and casts).
Similar to annotations on declarations, once types are annotated in the source code, they can be both maintained in the class file and made accessible at run-time by reflection (using the RetentionPolicy.CLASS or RetentionPolicy.RUNTIME policy on the annotation definition). Type annotations differ from their predecessors primarily in two ways. First off, type annotations on the kinds of local variable declarations can also be stored in class files, unlike declaration annotations. Additionally, the complete generic type is kept and is available at runtime.
Annotations can be stored in the class file, but they have no impact on how the programme normally runs. A programmer might, for instance, specify two File variables and a Connection variable in the method's body:
File my_file = ………;
@Encrypted File encryptedFile = ………;
@Open Connection connection = …………;
If one of these files is passed to the connection's send(...) method during programme execution, the same method implementation will be called:
connection.send(my_file);
connection.send(encryptedFile);
Although the types of parameters can be annotated, methods cannot be overloaded depending on the annotated types, as would be expected given the lack of runtime effect:
public class Connection {
void send (@Encrypted File my_file) { ………. }
}
The reason behind this restriction is that the compiler has no way of determining the relationship between types that have various annotations or between types that are annotated and unannotated.
The connection variable is referred to as the method's "receiver" in the call connection.send(...). (The name "receiver" originates from the traditional analogy of passing messages between objects in object-oriented programming.) In order to allow type annotations to be added on a method's recipient, Java 8 changes the syntax for method declarations:
void send (@open Connection this, @Encrypted File my_file)
Once more, a method declared with the new receiver parameter syntax behaves the same as one declared with the conventional syntax because annotations have no effect on how the method is executed. In actuality, the sole purpose for which the new syntax is being used is to allow a type annotation to be written on the type of the receiver.
THE CHECKER FRAMEWORK:
A framework for checking Java annotations is the Checker Framework. Prof. Michael Ernst, co-lead of the JSR 308 specification, is the project's active open-source project manager. The framework was first released in 2007. A wide variety of annotations and checkers for identifying flaws including null-pointer dereferences, unit of measure inconsistencies, security vulnerabilities, and threading/concurrency issues are pre-packaged with the Checker Framework. The checks' outputs are reliable because they use type-checking internally; a tool utilising only heuristics might overlook potential errors, whereas a checker won't. During compilation, the framework performs these tests using the compiler API. You can also quickly build your own annotation checkers as a framework to find application-specific flaws.
The framework's objective is to find flaws without making you write a lot of annotations. Smart defaults and control-flow sensitivity are the two main aspects that enable it to achieve this. For instance, the checker believes that parameters are non-null by default for identifying null pointer issues. Conditionals can also be used by the checker to verify whether it is safe to dereference an expression.
void nullSafe (Object nonNullByDefault, @Nullable Object mightBeNull)
{
nonNullByDefault. hashCode (); //Due to default OK!
mightBeNull. hashCode ();
//This is a Warning!
if (mightBeNull != null)
{
mightBeNull. hashCode(); //Due to check OK!
}
}
In reality, you hardly ever need to insert annotations in the body of methods because the checker can infer and verify the annotations automatically thanks to defaults and control-flow sensitivity. The Java team has made sure that third party tool designers and users can make their own design decisions by keeping the semantics for annotations out of the official Java compiler. As a result, error checking can be tailored to a project's specific requirements.
You could say that having the option to define your own annotations also permits domain-specific type-checking. For instance, interest rates are frequently expressed in percentages in the banking industry, although the difference between rates is frequently expressed in basis points (1/100th of 1%). To make sure you don't confuse the two, you may define two annotations, @Percent and @BasisPoints, using the Units Checker of the Checker Framework:
BigDecimal perct = returnsPct (……);
// This statement annotates for returning @Percent
requiresBps(perct);
//Displays an error which is “@Basispoints is required”.
Because the Checker Framework is attentive to control-flow in this case, it determines that pct is a @Percent BigDecimal when the call to requiresBps(pct) is made based on two facts: A @Percent BigDecimal is tagged to be returned by returnsPct(...), and pct hasn't been reallocated before the call to requiresBps (...). To try to avoid these kinds of flaws, developers frequently utilise naming conventions. You are given a guarantee that these flaws won't exist even as the code develops and evolves thanks to the Checker Framework.
Millions of lines of code have already been run through the Checker Framework, finding hundreds of flaws in even thoroughly tested software. My favourite illustration was when the framework was used to analyse the well-known Google Collections library (now Google Guava), which uncovered null-pointer flaws that had escaped the notice of thorough testing and heuristic-based static analysis tools.
It is possible to achieve these outcomes without overcrowding the code. In reality, the Checker Framework only needs 2-3 annotations per 1000 lines of code to test a property!