@inponomarev
Ivan Ponomarev, Synthesized.io/MIPT
//Arrays are specified in curly braces
@BugReport(..., reportedBy = {"Harry", "Carl"})
//Same as {"Harry"}
@BugReport(..., reportedBy = "Harry")
//Constructing nested annotations
@BugReport(..., ref = @Reference(id = "12345"))
//... As you can see, using the allowed types,
//you can specify an arbitrarily complex data structure
The annotation value cannot be set to null . null will not be allowed even for default values. |
Declarations
Type usage
Packages
Classes (including enum
)
Interfaces (including annotation interfaces)
Methods
Constructors
Instance fields (including enum
constants)
Local variables' declarations
Parameter variables' declarations
Type parameters
@Entity
public class User {
@Caption("user")
public User getUser(@Param("id") String userId) {
@SuppressWarnings("unchecked")
List<User> users = ....
}
}
class Cache<@Immutable V> {
...
}
//package-info.java
@GPL(version="3")
package edu.phystech.corejava;
import org.gnu.GPL;
//annotated parameter that states that userId is not `null`
User getUser(@NonNull String userId) { ... }
//Annotated type usage: each line is not `null`
List<@NonNull String> list = ...
Comparator. <@NonNull String>reverseOrder()
class Warning extends @Localized Message { ... }
List<@Localized ? extends Message> = ...
(@Localized String) text
if (text instanceof @Localized String) ...
//etc., the list of examples is not complete.
//Annotations can be almost everywhere!
this
public class Foo {
//type usage is being annotated!
void baz(@ReadOnly Foo this){
...
}
class Bar {
//type usage is being annotated!
public Bar(@ReadOnly Foo Foo.this) {
...
}
}
}
Annotation | Applicable to | Meaning |
| Methods | Method overrides another method in a base class. Won’t compile if it’s not the case (see lecture on classes). |
| Interfaces | Marks interface as functional, i. e. having the only abstract method. Won’t compile if it’s not the case (see lecture on lambdas and method references). |
| Methods and constructors | Declares safe usage of parameterized varargs (see lecture on generics) |
Annotation | Applicable to | Meaning |
| All API elements | This API element is deprecated — i. e. kept for backwards compatibility only, it is prohibited to use it in new code and it’s possible that this element will be removed in the future. |
| Everything except packages and annotations | Suppresses warnings of the given type that come from compiler and linters. |
| All API elements | Marks the code as automatically generated (and thus it doesn’t make sense to manually modify this code). |
//What code elements can be annotated
@Target({ElementType.TYPE, ElementType.METHOD})
//At what level are these annotations retained?
@Retention(RetentionPolicy.Source)
public @interface BugReport {
...
}
ElementType | Applicability of annotations |
| Annotation interfaces |
| Packages |
| Classes (including |
| Methods |
| Constructors |
| Fields |
| Method or constructor parameters |
| Local variable definitions |
| Type parameters |
| Type usage |
Retention Policy | Annotation availability | API |
| In .java files for annotation processors. Do not get into .class files and runtime. | Language Model API |
| In .java files and .class files. Invisible at run time. | Instrumentation API |
| In .java files, .class files and at run time. | Reflection API |
| All the annotations of this type will be included in the JavaDoc documentation. |
| If a class is marked with an annotation of this type, then all descendants of that class will also be marked with an annotation of the same type. |
| Allows you to specify whether an item can be marked multiple times with an annotation of the given type. |
Employee e = ... //assign Employee
Employee m = ... //assign Manager
//Get type information in run time
Class<? extends Employee> ce = e.getClass();
Class<? extends Employee> cm = m.getClass();
System.out.printf("%s, %s%n", ce.getName(), cm.getName());
//edu.phystech.hello.Employee, edu.phystech.hello.Manager
System.out.printf("%s, %s%n", ce.getSimpleName(), cm.getSimpleName());
//Employee, Manager
Class<T>
class is parameterizedDocumentation: "The actual result type of getClass()
is Class<? extends |X|>
where |X|
is the erasure of the static type of the expression on which getClass
is called."
Employee e = ...;
//No cast needed!
Class<? extends Employee> c = e.getClass();
//No cast needed!
Employee newEmployee =
ce.getDeclaredConstructor().newInstance();
//Compile-time error!
Class<? extends Number> cn = e.getClass();
Literals are strings in code that generate objects or values. For example, ''foo'' is a literal that generates a String
object with "foo" value.
Class literals are special expressions in the language that generate objects of type Class<…>
.
//everything is clear here
Class<String> c1 = String.class;
Class<Integer> c2 = Integer.class;
//Yes, you can!!
Class<Integer> c3 = int.class;
//And also double.class, boolean.class, etc.
//Note that c2 and c3 are significantly DIFFERENT objects.
//(for example, c3.getConstructors() returns an empty array.)
//There is nothing unexpected here, you can parameterize by arrays:
Class<int[]> c4 = int[].class;
When reading error logs, you can meet the following:
Type |
|
| [B |
| [S |
| [I |
| [J |
| [C |
| [D |
| [F |
| [Z |
| [Ljava.lang.Integer; |
//ClassNotFoundException can be thrown
//the type parameter cannot be known in advance here
Class<?> clazz =
Class.forName("edu.phystech.hello.Manager");
//There are two forms:
//class loader of the calling method, static initialization
static Class<?> forName(String className)
throws ClassNotFoundException
//custom class loader, static initialization on demand
static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
public InputStream getResourceAsStream(String name)
public java.net.URL getResource(String name)
getXXX
— all public elements (fields, constructors, methods)
getDeclaredXXX
— all elements declared in this class (no inherited ones)
By default, access is determined by visibility levels (e.g. private elements are not available)
Access can be granted by setAccessible(true)
In the runtime, you cannot define type parameters of an object (for example, you cannot distinguish List<Employee>
from List<Integer>
).
You can use reflection to find out all the information about type parameters of parameterized classes (e.g. List<T>
) or methods (e.g. <T extends Comparable<? super T>> T min (T[] a)
).
Reflection API is quite old, uses arrays (inconvenient in the era of streams and collections).
Many frequent tasks (for example, to get everything - public and private, own and inherited elements) do not have ready-made methods.
You can use ready-made libraries, for example, Reflections (https://github.com/ronmamo/reflections).
public class Person {
@Published
private final String firstName;
@Published
private final String lastName;
@Published
private final LocalDate birthDate;
...
}
@Published
public class JsonSerializer<T> {
private final Set<Field> publishedFields;
public JsonSerializer(Class<T> serializedClass) {
publishedFields = ReflectionUtils
.getAllFields(serializedClass,
f -> f.isAnnotationPresent(Published.class));
publishedFields.forEach(f -> f.setAccessible(true));
}
public JSONObject serialize(T o) {
JSONObject result = new JSONObject();
for (Field f : publishedFields) {
Object fieldValue = null;
try { fieldValue = f.get(o); }
catch (IllegalAccessException e) {
throw new IllegalStateException(e); }
if (fieldValue instanceof LocalDate) {
result.put(f.getName(),
((LocalDate) fieldValue).format(DateTimeFormatter.ISO_LOCAL_DATE));
} else {
result.put(f.getName(), fieldValue);
}
}
return result;
}
public class Controller {
private final Service service;
@Command("foo")
void doSomething() {
service.foo();
}
@Command("bar")
void bar() {
service.bar();
}
@Command()
void help() {
service.help();
}
}
public class CommandDispatcher {
private final Object controller;
private final Map<String, Method> methodHashMap =
new HashMap<>();
private Method defaultMethod;
public CommandDispatcher(Object controller) {
Objects.requireNonNull(controller);
this.controller = controller;
for (Method method : ReflectionUtils
.getAllMethods(controller.getClass())) {
Command command = method.getAnnotation(Command.class);
if (command != null) {
//Extract the parameter from the annotation
String commandId = command.value();
if (commandId.isEmpty()) {
defaultMethod = method;
} else {
methodHashMap.put(commandId, method);
}
}
}
}
public void executeCommand(String command)
throws InvocationTargetException, IllegalAccessException {
Method method = methodHashMap
.getOrDefault(command, defaultMethod);
if (method != null){
method.invoke(controller);
}
}
public class DataSource {
private ConnectionPool pool = new ConnectionPool(....);
// parameters taken from somewhere to connect to the database ^^^^^^
...
}
public class SpeakerDao {
private DataSource ds = new DataSource();
...
}
public class TalkDao {
private DataSource ds = ???
//we need one ConnectionPool for all...
//doesn't work :-(
...
}
public class ConnectionPool {
private final static ConnectionPool instance =
new ConnectionPool(...);
public static ConnectionPool getInstance() {
return instance;
}
//we will make the constructor private so that no one is tempted
//to create the object explicitly
private ConnectionPool(...) {
}
}
public class DataSource {
private final static DataSource instance = ...
public DataSource getInstance() {
return instance;
}
private ConnectionPool pool = ConnectionPool.getInstance();
private DataSource() {
...
}
}
![]() |
|
Direct creation is not possible (incapsulated in a private constructor).
Access a single instance only through the static getInstance()
method (or the final
variable INSTANCE
).
Leads to architectural problems.
But first, let’s digress and try to properly "cook" the singleton.
public class Singleton {
//Singleton creation is "hidden"
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
//We prohibit the option to create another instance "from the outside"
private Singleton(){
}
}
A "heavy" object is created always, even if getInstance
is not called?
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton(){
}
}
What if multiple threads are accessing the instance
variable?
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton(){
}
}
Now there is no guarantee that the instance is being created only once!
public class Singleton {
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton(){
}
}
All the threads will queue up just to access the instance
variable?
public class Singleton {
private static Singleton instance;
private static final Object lock = new Object();
public static Singleton getInstance() {
if (instance == null)
synchronized (lock) {
if (instance == null)
instance = new Singleton();
}
return instance;
}
private Singleton() {
}
}
Now the program is broken. Due to the fact that reading of the instance
field is not synchronized in the first if
block, it is possible to read a partially initialized object (if the Singleton
constructor is inlined).
public class Singleton {
private static volatile Singleton instance;
private static final Object lock = new Object();
public static Singleton getInstance() {
if (instance == null)
synchronized (lock) {
if (instance == null)
instance = new Singleton();
}
return instance;
}
private Singleton() {
}
}
Well, almost. But we have a performance overhead, because we read the volatile
variable twice.
Joshua Bloch: "On my machine, this method is about 1.4 times as fast as the obvious version without a local variable". [EJ, 3rd ed., pp. 334-335]
public class Singleton {
private static volatile Singleton instance;
private static final Object lock = new Object();
public static Singleton getInstance() {
Singleton result = instance;
if (result == null) {
synchronized (lock) {
if (instance == null)
instance = result = new Singleton();
}
}
return result;
}
}
Do you already see the bug in the following code?
public class Singleton {
private static volatile Singleton instance;
private static final Object lock = new Object();
public static Singleton getInstance() {
Singleton result = instance;
if (result == null) {
synchronized (lock) {
if (instance == null)
instance = result = new Singleton();
}
}
return result;
}
}
If instance != null
when we enter the synchronized
block, we will return null
.
public class Singleton {
private static volatile Singleton instance;
private static final Object lock = new Object();
public static Singleton getInstance() {
Singleton result = instance;
if (result != null)
return result;
synchronized (lock) {
if (instance == null)
instance = new Singleton();
return instance;
}
}
private Singleton() {}
}
OR NOT?
// Correct lazy initialization in Java
public class Singleton {
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private Singleton() {
}
}