Source: Codeceo www.codeceo.com/5-annotatio…
To highlight
Annotations have become an integral part of the Java ecosystem since THE launch of JDK5. While developers have developed countless custom annotations for Java frameworks such as Spring’s @AutoWired, some of the compiler-approved annotations are important.
In this article, we’ll look at five annotations supported by the Java compiler and see what they are expected to do. Along the way, we’ll explore the rationale behind its creation, some of the idiosyncrasies surrounding its use, and some examples of its proper application. While some of these annotations are more common than others, non-novice Java developers should digest and understand each one.
-
@Override
-
@FunctionalInterface
-
@SuppressWarnings
-
@SafeVarargs
-
@Deprecated
First, we’ll delve into one of the most commonly used annotations in Java: @Override.
@Override
The ability to override method implementations or provide implementations for abstract methods is at the heart of any object-oriented (OO) language. Because Java is an OO language with many common object-oriented abstraction mechanisms, any method in a non-final method or interface defined by a non-final superclass (interface methods cannot be final) can be overridden by subclasses. Click here to read a hands-on tutorial on new Java 10 features.
While the override method may seem simple at first, it can introduce many minor bugs if not executed correctly. For example, overwriting the Object#equals method with a single argument that overrides the class type is a common error:
public class Foo {
public boolean equals(Foo foo) {
// Check if the supplied object is equal to this object
}
}Copy the code
Since all classes implicitly inherit from the Object class, Foo’s purpose is to override the Object#equals method, so Foo can be tested to see if it is equal to any other Object in Java. While our intentions were right, our implementation was not.
In fact, our implementation doesn’t override the Object#equals method at all. Instead, we provide method overloading: Instead of replacing the implementation of equals provided by the Object class, we provide a second method that specifically accepts Foo objects instead of Object objects.
Our error can be illustrated with a simple implementation that returns true for all equality checks, but never calls it when the supplied Object is considered Object (an operation that Java will perform, for example, in the Java Collections Framework, namely JCF) :
public class Foo {
public boolean equals(Foo foo) {
return true;
}
}
Object foo = new Foo();
Object identicalFoo = new Foo();
System.out.println(foo.equals(identicalFoo)); // falseCopy the code
This is a very subtle but common error that can be caught by the compiler. Our intention was to override the Object#equals method, but because we specified a parameter of type Foo instead of type Object, we actually provided an overloaded Object#equals method instead of overwriting it. To catch such errors, we introduce the @Override annotation, which instructs the compiler to check that the Override is actually performed. If no valid override 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; }}Copy the code
If we try to compile this class, we now receive the following error:
$ javac Foo.java
Foo.java:3: error: method does not override or implement a method from a supertype
@Override
^1 errorCopy the code
In essence, we have transformed the implicit assumption that we have overridden the method into explicit validation by the compiler. If our intent is incorrectly implemented, the Java compiler issues an error that does not allow our incorrectly implemented code to compile successfully. In general, the Java compiler will issue an error (quoted from the Override annotation document) for a method that uses the @Override annotation if any of the following conditions are not met:
-
The method does override or implement methods declared in the superclass.
-
The signature of this method is equivalent to the signature override of any public method declared in Object (that is, the equals or hashCode methods).
Therefore, we can also use this annotation to ensure that subclass methods actually override non-final concrete or abstract methods in the superclass as well:
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()); // 10 System.out.println(bar.doSomethingElse()); / / 20Copy the code
The @Override annotation is not only limited to concrete or abstract methods in a superclass, but can also be used to ensure that the interface’s methods are also overridden (as of 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()); / / 10Copy the code
In general, any method that overrides a non-final class method, an abstract superclass method, or an interface method can be annotated with @Override. For more on Overriding and Hiding, see the Overriding and Hiding documentation and Section 9.6.4.4 of the Java Language Specification (JLS).
@FunctionalInterface
Functional interfaces became increasingly popular in Java with the introduction of lambda expressions in JDK 8. These special types of interfaces can be replaced with lambda expressions, method references, or constructor references. According to the @functionalinterface documentation, a FunctionalInterface is defined as follows:
A functional interface has only one abstract method. Since default methods have an implementation, 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; }}Copy the code
Therefore, each of the following can be replaced with a lambda expression, as follows:
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); // 10 consumer.consumeBar(() -> 20); / / 20Copy the code
It is important to note that abstract classes, even if they contain only one abstract method, are not functional interfaces. For more information, see Allow Lambdas to Implement Abstract Classes by Lead Java language architect Brian Goetz. Similar to the @Override annotation, the Java compiler provides the @FunctionalInterface annotation to ensure that the interface is indeed functional. For example, we could add this annotation to the interface created above:
@FunctionalInterface public interface Foo { public int doSomething(); } @Functional Interfacepublic interface Bar { public int doSomething(); public default int doSomethingElse() { return 1; }}Copy the code
If we incorrectly define an interface as a non-functional interface and annotate the wrong interface with @functionalInterface, the Java compiler issues an error. For example, we could define the following annotated non-functional interface:
@FunctionalInterface
public interface Foo {
public int doSomething();
public int doSomethingElse();
}Copy the code
If we try to compile this interface, we receive the following error:
$ javac Foo.java
Foo.java:1: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
Foo is not a functional interface
multiple non-overriding abstract methods found in interface Foo1 errorCopy the code
Using this annotation, we can ensure that we do not mistakenly create a non-functional interface that was intended to be a functional interface. Note that interfaces can be used as functional interfaces (instead of lambdas, method references, and constructor references) even in the absence of the @FunctionalInterface annotation, as we saw in our previous examples. This is similar to the @Override annotation, in that a method can be overridden even if it does not contain the @Override annotation. In both cases, annotations are an optional technique that allows the compiler to perform the desired intent.
For more information on @FunctionalInterface annotations, see the @FunctionalInterface documentation and section 4.6.4.9 of JLS. Click here to read a hands-on tutorial on new Java 10 features.
@SuppressWarnings
Warnings are an important part of all compilers, providing feedback to developers about potentially dangerous behavior or errors that may occur in future versions of the compiler. For example, using a generic type in Java without its associated formal generic parameter (called primitive type) causes a warning, as does using code that is not recommended (see the @Deprecated section below). While these warnings are important, they may not always apply or even be correct. For example, there may be a warning about an unsafe cast, but based on the context in which it is used, we can guarantee that it is safe.
To ignore specific warnings in some contexts, the @SuppressWarnings annotation was introduced in JDK 5. This annotation takes one or more string arguments that describe the name of the warning to ignore. While the names of these warnings generally vary between compiler implementations, there are three warnings that are standardized in the Java language (and therefore common across all Java compiler implementations) :
-
Unchecked. Represents an unchecked type conversion warning (the compiler cannot guarantee that the conversion is safe), Possible causes include access to members of primitive types (see JLS 4.8), narrow reference conversions or unsafe downward conversions (see JLS 5.1.6), unchecked conversions (see JLS 5.1.9), use of generic parameters with mutable parameters (see JLS 8.4.1 and @safevarargs below), use of invalid covariant return types (see JLS 8.4.8.3), evaluation of uncertain parameters (see JLS 15.12.4.2), An unchecked method reference type conversion (see JLS 15.13.2), or an unchecked lambda type dialogue (see JLS 15.27.3).
-
Deprecation: A warning indicating the use of Deprecated methods, classes, types, etc. (see JLS section 9.6.4.6 and @Deprecated below).
-
Removal: a warning that a method, class, type, etc. was used that was eventually Deprecated (see JLS section 9.6.4.6 and @deprecated below).
To ignore a particular warning, you can add the @SuppressedWarning annotation with one or more names of the suppression warning (provided as an array of strings) to the context in which the warning occurred:
public class Foo {
public void doSomething(@SuppressWarnings("rawtypes") List myList) {
// Do something with myList
}
}Copy the code
The @SuppressWarnings annotation can be used in any of the following situations:
-
type
-
The domain
-
methods
-
parameter
-
The constructor
-
A local variable
-
The module
In general, the @SuppressWarnings annotation should be applied to the most immediate warning scope. For example, if warnings should be ignored for local variables in a method, the @SuppressWarnings annotation should be applied to local variables, not to methods or classes that contain local variables:
public class Foo {
public void doSomething() {
@SuppressWarnings("rawtypes")
List myList = new ArrayList();
// Do something with myList
}
}Copy the code
@SafeVarargs
Mutable parameters are a useful technique in Java, but they can also cause some serious problems when used with generic parameters. Because generics are nonspecific in Java, the actual (implementation) type of a variable with a generic type cannot be determined at run time. Since this judgment cannot be made, a variable may store references to a type that are not 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); // ClassCastExceptionCopy the code
After ln is assigned to LS, there is a variable ls in the heap that has the type List but stores references to values that are actually of type List. This invalid reference is called heap contamination. Because this error is not determined until run time, it appears as a warning at compile time and a ClassCastException at run time. This problem can be exacerbated when generic parameters are combined with mutable parameters:
public class Foo { public <T> void doSomething(T... args) { // ... }}Copy the code
In this case, the Java compiler creates an array inside the calling site to store a variable number of arguments, but the type of T is not implemented and therefore lost at run time. In essence, the argument to doSomething is actually of type Object[]. This can cause serious problems if you rely on the runtime type of T, 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);Copy the code
If you execute this code snippet, you will cause a ClassCastException because the first Number argument passed at the calling site cannot be converted to String (similar to the ClassCastException thrown in the standalone heap contamination example). In general, it is possible that the compiler does not have enough information to correctly determine the exact types of generic mutable arguments, which can lead to heap contamination that can be propagated by allowing an internal array of mutable arguments to escape from a method, as in the following example from Effective Java, 3rd edition pp.147:
public static <T> T\[\] toArray(T... args) {
return args;
}Copy the code
In some cases, we know that the method is actually type-safe and does not pollute the heap. If this decision can be made with assurance, we can annotate the method with the @Safevarargs annotation to suppress warnings about possible heap contamination. However, this raises the question: when is a generic variadic method considered type-safe? Josh Bloch, based on page 147 of Effective Java 3rd edition, offers a complete solution based on the interaction of methods with arrays created internally to store their mutable parameters:
If the method stores nothing into the array (which overrides the arguments) and does not allow references to the array to be escaped (which makes the array accessible to untrusted code), then it is safe. In other words, if the mutable parameter array is used only to pass a variable number of arguments from the caller to the method — which, after all, is the purpose of the mutable parameter — then the method is safe.
So, if we create the following method (from pp.149, ibid.), then we can annotate our method reasonably with the @Safevarags annotation:
@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;
}Copy the code
For more information on @Safevarargs annotations, see the @Safevarargs documentation, JLS section 9.6.4.7, and Item32 in Effective Java version 3. Click here to read a hands-on tutorial on new Java 10 features.
@Deprecated
When developing code, sometimes it becomes outdated and should no longer be used. In these cases, there is usually an alternate that is better suited to the task at hand, and while existing calls to outdated code may be retained, all new calls should use the replacement method. This outdated code is referred to as code not recommended. In some emergency situations, code that is not recommended may be removed, and should be converted to replacement code immediately before deprecated code is removed from its code base in future versions of the framework or library.
To support documentation of code that is not recommended, Java includes the @deprecated annotation, which marks constructors, fields, local variables, methods, packages, modules, parameters, or types as Deprecated. If deprecated elements (constructors, fields, local variables, and so on) are used, the compiler issues a warning. For example, we could create a deprecated class and use it as follows:
@Deprecatedpublic class Foo {}
Foo foo = new Foo();Copy the code
If we compile this code (in a file named main.java), we receive the following warning:
$ javac Main.javaNote: Main.java uses or overrides a deprecated API.Note: Recompile with -Xlint:deprecation for details.Copy the code
In general, a warning is raised whenever an element with the @deprecated annotation is used, except in the following five cases:
-
The declaration itself is declared to be deprecated (that is, recursive calls).
-
Declare that the annotated Deprecation warning (that is, the @SuppressWarnings(” deprecation “) annotation, as described above, applies to the context in which the deprecated element is used.
-
Both usage and declaration are in the same outermost class (that is, if the class calls its own deprecated method).
-
Used in import declarations that import types or artifacts that are not normally approved for use (that is, when importing a deprecated class into another class).
-
Exports or exports.
As mentioned earlier, in some cases, when an unrecommended element is about to be removed, the calling code should immediately remove the unrecommended element (called terminally Deprecated code). In this case, you can use the @deprecated annotation provided by the forRemoval parameter, as follows:
@Deprecated(forRemoval = true)public class Foo {}Copy the code
Using this final deprecated code results in a series of more stringent warnings:
$ javac Main.java
Main.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 warningsCopy the code
In addition to the same exceptions described by the standard @deprcated annotation, a warning is always raised for eventual deprecation. 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 {}Copy the code
Deprecated elements can be further documented using the @deprecated JavaDoc element (note the lowercase D), 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 {}Copy the code
The JavaDoc tool will generate the following documents:
For more information on the @Deprecated annotation, see the @Deprecated documentation and section 9.6.4.6 of JLS.
At the end
Annotations have been an integral part of Java since the introduction of annotations in JDK 5. While some annotations are more popular than others, here are five that developers at beginner level and above should understand and master:
-
@Override
-
@FunctionalInterface
-
@SuppressWarnings
-
@SafeVarargs
-
@Deprecated
While each approach has its own unique uses, all of these annotations make the Java application more readable and allow the compiler to make some other implicit assumptions about our code. As the Java language continues to evolve, these tried-and-true annotations may serve for years, helping to ensure that more applications do what developers intend.
Read more on my blog:
1.Java JVM, Collections, Multithreading, new features series tutorials
2.Spring MVC, Spring Boot, Spring Cloud series tutorials
3.Maven, Git, Eclipse, Intellij IDEA series tools tutorial
4.Java, backend, architecture, Alibaba and other big factory latest interview questions
Life is good. See you tomorrow