Built-in annotations in Java
@Override, @Deprecated, @SuppressWarnings, @FunctionalInterface, @SafeVarargs.
speek() instead of speak(), and your dog goes silent forever. One annotation would have caught it before the code ever ran.Notes for the compiler
Java ships annotations that javac understands: **@Override (verify it really overrides), @Deprecated (warn users of an API), @SuppressWarnings (silence named warnings), @FunctionalInterface (exactly one abstract method) and @SafeVarargs** (generic varargs used safely).
A typo meets @Override
What happens?
class Shape {
double area() { return 0; }
}
class Sq extends Shape {
@Override
double Area() { return 4; }
}
void main() {
System.out.println(new Sq().area());
}Prints 4.0Prints 0.0Compile error
Show the answer
Area() (capital A) overrides nothing in Shape, so **@Override makes javac reject the code**. Without the annotation it would compile, and area() would silently return 0.0: a new method hiding the bug.
@Deprecated and @SuppressWarnings
@Deprecated(since = "9", forRemoval = true) warns every caller and states the plan. It's kept at runtime, so tools can see it. @SuppressWarnings("unchecked") silences one named warning: keep it on the smallest possible scope.
Which ones survive?
What does this print?
class Api {
@Deprecated @SuppressWarnings("all")
public void old() { }
}
void main() throws Exception {
var m = Api.class.getMethod("old");
int n = m.getAnnotations().length;
System.out.println(n);
}012
Show the answer
@Deprecated has RUNTIME retention, so reflection sees it. @SuppressWarnings (like @Override) has SOURCE retention: javac uses it, then throws it away.
Counting abstract methods
@FunctionalInterface demands exactly one abstract method. Default and static methods don't count, and neither do abstract methods matching **public Object methods** like equals. This compiles: test is the only one.
@FunctionalInterface
interface Check {
boolean test(String s);
boolean equals(Object o); // not counted
default Check negate() { // not counted
return s -> !test(s);
}
}@SafeVarargs on an overridable method
@SafeVarargs is a promise, so it's allowed only where nobody can break it with an override: static, final or private methods and constructors. On an ordinary instance method it's a compile error.
class Box {
@SafeVarargs // compile error:
<T> void addAll(T... items) { }
} // method can be overriddenThe equals(Foo) bug
A classic: writing public boolean equals(Person p) instead of equals(Object o). It overloads instead of overriding, so HashSet never calls it. With @Override, javac flags it instantly. Teams treat a missing @Override as a code-review smell.
Key takeaways
- @Override turns signature typos into compile errors
- @Deprecated(since = "9", forRemoval = true) is visible at runtime
- @FunctionalInterface: exactly one abstract method
- @SafeVarargs only on static, final, private methods or constructors
@FunctionalInterface is optional: any interface with exactly one abstract method already works with lambdas. The annotation just makes the compiler enforce it.
Practice questions
What does this print?
class Animal {
void speak() { }
}
class Dog extends Animal {
@Override
void speek() { }
}
void main() {
System.out.println("ok");
}- Compile error
- Prints a warning, then ok
- ok
Check your answer
Compile error. speek() doesn't override anything in Animal, so @Override makes javac reject the code.
What does this print?
class Old {
@Override @Deprecated
public String toString() { return "old"; }
}
void main() throws Exception {
var m = Old.class.getMethod("toString");
for (var a : m.getAnnotations())
System.out.println(
a.annotationType().getSimpleName());
}- Deprecated
- Override
- Deprecated Override
- Override Deprecated
Check your answer
Deprecated. @Deprecated has RUNTIME retention, so reflection can see it. @Override has SOURCE retention: javac checks it and then throws it away.