Five annotations each Java developer should know.
Since JDK5 was launched, annotations have become an indispensable part of the Java ecosystem. Although developers have developed numerous custom annotations for Java frameworks (such as Spring @ Autowired), some annotations recognized by the compiler are very important.
In this article, we will see the annotations supported by five Java compilers and understand their intended uses. By the way, we will explore the basic principles behind its creation, some characteristics of its use, and some examples of correct application. Although some annotations are more common than other annotations, non-Beginner Java developers should understand each annotation.
First, we will study one of the most common annotations in Java: @ Override.
@ Override
The implementation of override methods or the ability to provide implementation for abstract methods is the core of any object-oriented (OO) language. Java is an OO language and has many common object-oriented abstraction mechanisms. Therefore, any methods in non-final methods or interfaces defined by non-Ultimate superclasses (interface methods cannot be final) can be overwritten by the quilt class. Although the overwriting method at the beginning looks simple, if the execution is incorrect, many minor bugs may be introduced. For example, overwriting the Object # equals method with a single parameter of the override class type is a common error:
public class Foo { public boolean equals(Foo foo) { // Check if the supplied object is equal to this object }}
Since all classes are implicitly inherited from the Object class, the purpose of the Foo class is to overwrite the Object # equals method, so Foo can be tested to determine whether it is equal to any other Object in Java. Although our intent is correct, this is not the case with our implementation. In fact, our implementation does not cover the Object # equals method at all. On the contrary, we provide method overloading: instead of replacing the implementation of the equals method provided by the Object class, we provide the second method to specifically accept the Foo Object instead of the Object. We can use a simple implementation as an example to illustrate that this implementation returns true for all equal checks, but when the provided Object is treated as an Object (the operation that Java will execute, for example, in Java Collections Framework (in JCF), it will never be called:
public class Foo { public boolean equals(Foo foo) { return true; }}Object foo = new Foo();Object identicalFoo = new Foo();System.out.println(foo.equals(identicalFoo)); // false
This is a very subtle but common error that can be captured by the compiler. Our intention is to override the Object # equals method, but because we specify a parameter of the type Foo rather than the Object type, we actually provide the overloaded Object # equals method, instead of overwriting it. To capture this error, we introduce the @ Override annotation, which instructs the compiler to check whether the overwrite is actually executed. If no valid overwrite is performed, an error is thrown. Therefore, we can update the Foo class as follows:
public class Foo { @Override public boolean equals(Foo foo) { return true;}}
If we try to compile this class, we now receive the following error:
$ javac Foo.javaFoo.java:3: error: method does not override or implement a method from a supertype @Override ^1 error
Essentially, we have transformed this implicit assumption that we have covered the method into explicit verification by the compiler. If our intent is mistakenly implemented, the Java compiler will issue an error-the code that does not allow us to be correctly implemented is successfully compiled. Generally, if any of the following conditions is not met, the Java compiler will issue an error (reference from the Override annotation document) for methods using the @ Override annotation ):
-
This method does overwrite or implement the methods declared in the superclass.
-
The signature of this method is equivalent to override-equivalent of any public method (equals or hashCode) declared in the Object ).
Therefore, we can also use this annotation to ensure that the subclass method actually overwrites non-final concrete or abstract methods in the superclass:
public abstract class Foo { public int doSomething() { return 1; } public abstract int doSomethingElse();}public class Bar extends Foo { @Override public int doSomething() { return 10; } @Override public int doSomethingElse() { return 20; }}Foo bar = new Bar();System.out.println(bar.doSomething()); // 10System.out.println(bar.doSomethingElse()); // 20
@ Override annotation is not only not limited to the specific or abstract methods in the superclass, but also can be used to ensure that the interface methods are overwritten (starting with JDK 6 ):
public interface Foo { public int doSomething();}public class Bar implements Foo { @Override public int doSomething() { return 10; }}Foo bar = new Bar();System.out.println(bar.doSomething()); // 10
In general, any method that overwrites non-final class methods, abstract superclass methods, or interface methods can be annotated using @ Override. For more information about effective coverage, see the Overriding and Hiding document and Chapter 9.6.4.4 of Java Language Specification (JLS.
@ FunctionalInterface
With the introduction of lambda expressions in JDK 8, functional interfaces become increasingly popular in Java. These special types of interfaces can be replaced by lambda expressions, method references, or constructor references. According to the @ FunctionalInterface document, function interfaces are defined as follows:
A function interface has only one abstract method. The default methods have an implementation, so they are not abstract.
For example, the following interfaces are considered functional interfaces:
public interface Foo { public int doSomething();}public interface Bar { public int doSomething(); public default int doSomethingElse() { return 1; }}
Therefore, each of the following can be replaced by a lambda expression, as shown below:
public class FunctionalConsumer { public void consumeFoo(Foo foo) { System.out.println(foo.doSomething()); } public void consumeBar(Bar bar) { System.out.println(bar.doSomething()); }}FunctionalConsumer consumer = new FunctionalConsumer();consumer.consumeFoo(() -> 10); // 10consumer.consumeBar(() -> 20); // 20
It is important to note that abstract classes are not functional interfaces even if they only contain one abstract method. For more information, see Allow lambdas to implement abstract classes written by Brian Goetz, Chief Java language architect. Similar to the @ Override annotation, the Java compiler provides the @ FunctionalInterface annotation to ensure that the interface is indeed a functional interface. For example, we can add this annotation to the interface created above:
@FunctionalInterfacepublic interface Foo { public int doSomething();}@FunctionalInterfacepublic interface Bar { public int doSomething(); public default int doSomethingElse() { return 1; }}
If we mistakenly define an interface as a non-functional interface and annotate the interface with @ FunctionalInterface, the Java compiler will issue an error. For example, we can define the following non-functional interfaces with annotations:
@FunctionalInterfacepublic interface Foo { public int doSomething(); public int doSomethingElse();}
If we try to compile this interface, we will receive the following error:
$ javac Foo.javaFoo.java:1: error: Unexpected @FunctionalInterface annotation@FunctionalInterface^ Foo is not a functional interface multiple non-overriding abstract methods found in interface Foo1 error
Using this annotation, we can ensure that we do not mistakenly create non-functional interfaces originally intended to be used as function interfaces. Note that even if the @ FunctionalInterface annotation does not exist, the interface can be used as a functional interface (instead of lambdas, method reference, and constructor reference ), as we have seen in the previous example. This is similar to the @ Override annotation, that is, a method can be overwritten, even if it does not contain the @ Override annotation. In both cases, annotations are optional techniques that allow the compiler to execute the expected intent.
For more information about @ FunctionalInterface annotations, see the @ FunctionalInterface documentation and section 4.6.4.9 of JLS.
@ SuppressWarnings
Warning is an important part of all compilers and provides feedback to developers-potentially risky behavior or errors that may occur in future compiler versions. For example, using a generic type in Java without its associated formal generic parameters (called the original type) will cause a warning, just like using code that is not recommended (see the @ Deprecated section below ). Although these warnings are important, they may not always apply or are not always correct. For example, there may be warnings for unsafe type conversions, but we can ensure it is safe based on the context that uses it.
To ignore specific warnings in some contexts, the @ SuppressWarnings annotation is introduced in JDK 5. This annotation accepts one or more string parameters-the name of the warning to be ignored. Although the names of these warnings are usually different in Compiler implementations, three warnings are standardized in Java (so they are common in all Java compiler implementations ):
-
Unchecked: indicates the type conversion warning that has not been checked (the compiler cannot ensure that the type conversion is safe), which may cause access to members of the original type (see section 4.8 of JLS) narrow reference conversion or unsafe downward conversion (see section 5.1.6 of JLS), unchecked type conversion (see section 5.1.9 of JLS) use generic parameters with variable parameters (see Section 8.4.1 of JLS and the @ SafeVarargs section below) and use invalid covariant return types (see section 8.4.8.3 of JLS), uncertain parameter evaluation (see section 15.12.4.2 of JLS), unchecked method reference type conversion (see section 15.13.2 of JLS), or an unchecked lambda conversation (see section 15.27.3 of JLS ).
-
Deprecation: indicates that warnings of obsolete methods, classes, and types are used (see section 9.6.4.6 of JLS and the @ Deprecated section below ).
-
Removal: indicates the warning that the discarded methods, classes, and types are used (see Chapter 9.6.4.6 of JLS and the @ Deprecated section below ).
To ignore specific warnings, you can add one or more names of the @ SuppressedWarning annotation and suppression warning (provided in the form of a string array) to the context in which a warning occurs:
public class Foo { public void doSomething(@SuppressWarnings("rawtypes") List myList) { // Do something with myList }}
@ SuppressWarnings annotation can be used in any of the following situations:
-
Type
-
Domain
-
Method
-
Parameters
-
Constructor
-
Local variable
-
Module
In general, the @ SuppressWarnings annotation should be applied to the most direct warning range. For example, if a warning should be ignored for a local variable in a method, the @ SuppressWarnings annotation should be applied to a local variable instead of a method or class that contains a local variable:
public class Foo { public void doSomething() { @SuppressWarnings("rawtypes") List myList = new ArrayList(); // Do something with myList }}@ SafeVarargs
Variable parameters are a useful technique in Java, but they may cause some serious problems when used together with generic parameters. Because generics are non-specific in Java, the actual (Implementation) Type of variables with generics cannot be determined at runtime. Unable to make this judgment, the variable may store reference to a type other than its actual type, as shown in the following code snippet (from Java Generics FAQs):
List ln = new ArrayList<Number>();ln.add(1);List<String> ls = ln; // unchecked warning String s = ls.get(0); // ClassCastException
After ln is allocated to ls, the heap contains the variable ls, which has the List <String> type, but stores the referenced value actually of the List <Number> type. This invalid reference is called heap pollution. This error cannot be identified until the runtime, so it is displayed as a warning during compilation and ClassCastException occurs during runtime. This problem may be aggravated when a combination of generic and variable parameters:
public class Foo { public <T> void doSomething(T... args) { // ... }}
In this case, the Java compiler will create an array inside the calling site to store variable parameters, but the T type is not implemented, so it will be lost at runtime. Essentially, the parameter to doSomething is actually of the Object [] type. If it depends on the running type of T, this will cause serious problems, as shown in the following code snippet:
public class Foo { public <T> void doSomething(T... args) { Object[] objects = args; String string = (String) objects[0]; }}Foo foo = new Foo();foo.<Number>doSomething(1, 2);
If you execute this code snippet, ClassCastException will occur because the first Number parameter passed at the call site cannot be converted to String (similar to the ClassCastException thrown in the independent heap contamination example ). Generally, the compiler does not have enough information to correctly determine the exact type of a Universal variable parameter, which may cause heap contamination, this pollution can be spread by allowing an internal variable parameter array to be transited from the method definition, as shown in the following example from Protocol Java version 3rd pp.147:
public static <T> T[] toArray(T... args) { return args;}
In some cases, we know that the method is actually safe and will not cause heap pollution. If you can make this decision with assurance, you can use the @ SafeVarargs annotation to annotate the method to suppress warnings related to possible heap pollution. However, this raises the question: when will the generic variable parameter method be considered as type-safe? Josh Bloch provides a complete solution based on the 3rd page of objective Java version 147th-based on the method and the internally created array for storing its variable parameters:
If the method does not store anything in an array (this will overwrite the parameter) and does not allow escaping of array references (this will allow untrusted code to access the array), it is safe. In other words, if the variable parameter array is only used to pass a variable number of parameters from the caller to the method-after all, this is the purpose of the Variable Parameter-then the method is safe.
Therefore, if we create the following method (from pp.149), we can use the @ SafeVarags annotation to reasonably annotate our method:
@SafeVarargsstatic <T> List<T> flatten(List<? extends T>... lists) { List<T> result = new ArrayList<>(); for (List<? extends T> list : lists) { result.addAll(list); } return result;}
For more information about @ SafeVarargs annotations, see @ SafeVarargs, Chapter 9.6.4.7 in JLS, and Item32 in objective Java 3rd.
@ Deprecated
When developing code, sometimes the code will become outdated and should not be used again. In these cases, there is usually a substitute task that is more suitable for the task at hand, and although the existing calls to outdated code may be retained, all new calls should use the replacement method. This outdated code is called not recommended. In some emergencies, unrecommended code may be deleted. It should be converted to replacement code immediately before a framework or library version deletes the obsolete code from its code base.
To support documents of codes that are not recommended, Java contains @ Deprecated annotations, it will mark constructors, domains, local variables, methods, software packages, modules, parameters, or types as obsolete. If the discarded element (constructor, domain, local variable, etc.) is used, the compiler issues a warning. For example, you can create an obsolete Class and use it as follows:
@Deprecatedpublic class Foo {}Foo foo = new Foo();
If we compile this code (in the file named Main. java), we will receive the following warning:
$ javac Main.javaNote: Main.java uses or overrides a deprecated API.Note: Recompile with -Xlint:deprecation for details.
Usually, an alert is triggered when @ Deprecated annotation is used, except for the following five situations:
-
The Declaration itself is declared as a waste (that is, recursive call ).
-
Declare that the annotation is forbidden to discard the warning (I .e. @ SuppressWarnings ("deprecation") annotation, as described above, applies to the context that uses the deprecation element.
-
The usage and description are all in the same outermost class (that is, if the class calls its own discard method ).
-
Used in the import declaration, this declaration imports a type or component that is generally not in favor of use (that is, when the obsolete Class is imported into another class ).
-
Exports or opens commands.
As mentioned above, in some cases, when the unrecommended elements are deleted, the calling code should immediately delete the unrecommended elements (terminally deprecated code ). In this case, you can use the @ Deprecated annotation provided by the forRemoval parameter, as shown below:
@Deprecated(forRemoval = true)public class Foo {}
Using this eventually discards the code will cause a series of more rigorous Warnings:
$ javac Main.javaMain.java:7: warning: [removal] Foo in com.foo has been deprecated and marked for removal Foo foo = new Foo(); ^Main.java:7: warning: [removal] Foo in com.foo has been deprecated and marked for removal Foo foo = new Foo(); ^2 warnings
In addition to the same exception described in the Standard @ Deprcated annotation, a warning is always triggered that the final discard is triggered. We can also add the document to the @ Deprecated annotation by providing the since variable for the annotation:
@Deprecated(since = "1.0.5", forRemoval = true)public class Foo {}
You can use the @ deprecated JavaDoc element (note the lowercase letter d) to further document obsolete elements, as shown in the following code snippet:
/** * Some test class. * * @deprecated Replaced by {@link com.foo.NewerFoo}. * * @author Justin Albano */@Deprecated(since = "1.0.5", forRemoval = true)public class Foo {}
The JavaDoc tool generates the following documents:
For more information about @ Deprecated annotations, see the @ Deprecated document and Chapter 9.6.4.6 in JLS.
End
Annotations have been an indispensable part of Java since JDK 5 introduced annotations. Although some annotations are more popular than other annotations, the five annotations described in this article are what developers at the beginner level and above should understand and master: @ Override, @ FunctionalInterface, @ SuppressWarnings, @ SafeVarargs, and @ Deprecated. Although each method has its unique purpose, all these annotations make Java applications more readable and allow the compiler to execute some other implicit assumptions on our code. With the continuous development of the Java language, these proven annotations may serve for many years and help ensure that more applications act according to developers' intentions.