Suppress error java

public abstract @interface SuppressWarnings implements Annotation


public

abstract
@interface
SuppressWarnings

implements

Annotation

java.lang.SuppressWarnings


Indicates that the named compiler warnings should be suppressed in the
annotated element (and in all program elements contained in the annotated
element). Note that the set of warnings suppressed in a given element is
a superset of the warnings suppressed in all containing elements. For
example, if you annotate a class to suppress one warning and annotate a
method to suppress another, both warnings will be suppressed in the method.

As a matter of style, programmers should always use this annotation
on the most deeply nested element where it is effective. If you want to
suppress a warning in a particular method, you should annotate that
method rather than its class.

Summary

Public methods

String[]


value()

The set of warnings that are to be suppressed by the compiler in the
annotated element.

Inherited methods

From interface

java.lang.annotation.Annotation



abstract

Class<? extends Annotation>


annotationType()

Returns the annotation interface of this annotation.


abstract

boolean


equals(Object obj)

Returns true if the specified object represents an annotation
that is logically equivalent to this one.


abstract

int


hashCode()

Returns the hash code of this annotation.


abstract

String


toString()

Returns a string representation of this annotation.

Public methods

value

public String[] value ()

The set of warnings that are to be suppressed by the compiler in the
annotated element. Duplicate names are permitted. The second and
successive occurrences of a name are ignored. The presence of
unrecognized warning names is not an error: Compilers must
ignore any warning names they do not recognize. They are, however,
free to emit a warning if an annotation contains an unrecognized
warning name.

The string "unchecked" is used to suppress
unchecked warnings. Compiler vendors should document the
additional warning names they support in conjunction with this
annotation type. They are encouraged to cooperate to ensure
that the same names work across multiple compilers.

Returns
String[] the set of warnings to be suppressed

Annotations are a very important part of Java in modern technologies, Most of the technologies such as Hibernate, Spring, Spring Boot, JPA, and so Many other Libraries are using annotations and making developers’ life a lot easy. In Java, built-in General Annotations are – 

  1. @Override
  2. @Deprecated
  3. @FunctionalInterface
  4. @SuppressWarnings

Syntax: The signature for Java @SuppressWarnings annotation is as follows:

@Retention(value=SOURCE)
@Target(value = {TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE })
public @interface SuppressWarnings {
       String[] value;
    }

As we can see, the above signature has only one element, which is Array of String, with multiple possible values.

All annotations have two properties :

  1. Target (@Target(value = {TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE }))  – It will be used with almost everything, wherever you want to suppress warnings.
  2. Retention (@Retention(value=SOURCE)): Retention policy of functional Interface “SOURCE”, which means annotation won’t go till compiler.

Illustrations: 

Use of @SuppressWarnings is to suppress or ignore warnings coming from the compiler, i.e., the compiler will ignore warnings if any for that piece of code.

1. @SuppressWarnings("unchecked")
   public class Calculator {
          }
          
- Here, it will ignore all unchecked warnings coming from that class. (All methods, variables, constructors).
2. public class Calculator {
   @SuppressWarnings("unchecked")
      public int sum(x,y) {
        .
      }
   }
   
- It will stop warning from that function only, and not from other functions of Calculator class.

This annotation is dangerous because a warning is something potentially wrong with the code. So if we’re getting any warning, the first approach should be resolving those errors. But if we’re suppressing any warnings, we have to have some solid reason. The reason should be commented near to the annotation every time it is used.

Possible Values Inside @SuppressWarnings Annotation Element  are as follows:

Values

Description
All  It will suppress all warnings.
Cast          Suppress the warning while casting from a generic type to a nonqualified type or the other way around.
  Deprecation    Ignores when we’re using a deprecated(no longer important) method or type.
divzero Suppresses division by zero warning.
empty Ignores warning of a statement with an empty body.
unchecked  It doesn’t check if the data type is Object or primitive.
fallthrough  Ignores fall-through on switch statements usually (if “break” is missing).
hiding  It suppresses warnings relative to locals that hide variable
serial It makes the compiler shut up about a missing serialVersionUID.
finally Avoids warnings relative to finally block that doesn’t return.
unused To suppress warnings relative to unused code.

Note: The primary and most important benefit of using @SuppressWarnings Annotation is that if we stuck because of some known warning, then this will ignore the warning and move ahead. E.g. – deprecated and unchecked warnings.

Example:

Java

import java.io.*;

import java.lang.*;

import java.util.*;

class Addition {

    public static int sum(int n1, int n2)

    {

        return n1 + n2;

    }

    public static int sum(int... nums)

    {

        int sum = 0;

        for (int i : nums) {

            sum += i;

        }

        return sum;

    }

}

public class GFG {

    @SuppressWarnings("unchecked")

    public static void main(String[] args)

    {

        Addition add = new Addition();

        @SuppressWarnings("deprecation")

        int sum = Addition.sum(10, 20);

        System.out.println("Sum of 10 and 20 : " + sum);

        @SuppressWarnings("rawtypes")

        List list = new ArrayList();

        list.add(12);

        list.add(120);

        System.out.println("List items : " + list);

    }

}

Output

Sum of 10 and 20 : 30
List items : [12, 120]

You may have seen the @SuppressWarnings annotation before. Have you wondered how and why to suppress warnings in Java?

Errors and warnings

The Java compiler is very strict. It generates errors and stops compiling if there’s something seriously wrong with your code. However, sometimes it just warns you of potential problems.

Compiler warning messages are usually helpful. But sometimes warnings can be noisy, especially when you can’t or don’t want to address them.

For example, the compiler will warn you if you use a deprecated class or method. Re-compiling with the -Xlint or the more specific -Xlint:deprecation flag will give you some extra information. But what happens if you can’t or don’t want to re-write the offending code, but do want to get rid of the warning?

Warning types

You can decide to suppress warnings. The purpose of the @SuppressWarnings annotation to disable specific compiler warnings. You can use it to disable or suppress warnings for a specific part of a class. It can be used on types, fields, constructors, methods, parameters and local variables. It allows you to specify which warnings you’d like the compiler to ignore. The annotation takes a single String[] which you use to specify all the warnings you’d like to ignore.

Warning types vary from compiler to compiler, but a few of the most common include:

  • deprecation warns when you’re using a deprecated method or class.
  • unchecked tells you when you’re using raw types instead of parameterized types. An unchecked warning says that a cast may cause a program to throw an exception somewhere else. Suppressing the warning with @SuppressWarnings("unchecked") tells the compiler that you believe the code is safe and won’t cause unexpected exceptions.
  • rawtypes warns that you are using a raw type instead of a parameterized type. It is like unchecked, but it is used on fields and variables.
  • serial warns you about missing serialVersionUID definitions on serializable classes.

To get a list of the warnings that the compiler you’re using can generate, run javac -X on the command line.

Example of how to suppress warnings

For example, the following class generates four warnings: deprecationuncheckedrawtypes and serial

public class ClassWithLotsOfWarnings implements Serializable {
    // no serialVersionUID field

    // a raw type
    private List list;

    public static void main(String args[]) {
        // deprecated constructor
        Date date = new Date (100, 11, 07);
        System.out.println(date);
    }

    public void add(String str) {
        // unchecked operation
        list.add(str);
    }

} // end of class

To suppress all the warnings, you could do the following:

@SuppressWarnings({"serial", "deprecation", 
                   "rawtypes", "unchecked"})
public class ClassWithLotsOfWarnings implements Serializable {
    ...
} // end of class

Best practice for suppressing warnings

The best practice is to annotate the closest element to where you need to suppress the warning. For example, if you want to suppress a warning in a specific method, you should annotate the method, not the class.

To suppress the warnings at the most appropriate places in the code, you could do the following:

@SuppressWarnings("serial")
public class ClassWithLotsOfWarnings implements Serializable {

    @SuppressWarnings("rawtypes")
    private List list;

    @SuppressWarnings("deprecation")
    public static void main(String args[]) {
        Date date = new Date (100, 11, 07);
        System.out.println(date);
    }

    @SuppressWarnings("unchecked")
    public void add(String str) {
        list.add(str);
    }

} // end of class

Now you know how to use @SuppressWarnings!

I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.

Indicates that the named compiler warnings should be suppressed in the
annotated element (and in all program elements contained in the annotated
element). Note that the set of warnings suppressed in a given element is
a superset of the warnings suppressed in all containing elements. For
example, if you annotate a class to suppress one warning and annotate a
method to suppress another, both warnings will be suppressed in the method.

As a matter of style, programmers should always use this annotation
on the most deeply nested element where it is effective. If you want to
suppress a warning in a particular method, you should annotate that
method rather than its class.

Public Method Summary

String[]

value()

The set of warnings that are to be suppressed by the compiler in the
annotated element.

Inherited Method Summary


From interface
java.lang.annotation.Annotation

abstract

Class<? extends Annotation>

abstract

boolean

equals(Object obj)

Returns true if the specified object represents an annotation
that is logically equivalent to this one.

abstract

int

hashCode()

Returns the hash code of this annotation, as defined below:

The hash code of an annotation is the sum of the hash codes
of its members (including those with default values), as defined
below:

The hash code of an annotation member is (127 times the hash code
of the member-name as computed by String.hashCode()) XOR
the hash code of the member-value, as defined below:

The hash code of a member-value depends on its type:

  • The hash code of a primitive value v is equal to
    WrapperType.valueOf(v).hashCode(), where
    WrapperType is the wrapper type corresponding
    to the primitive type of v (Byte,
    Character, Double, Float, Integer,
    Long, Short, or Boolean).
abstract

String

toString()

Returns a string representation of this annotation.

Public Methods


public

String[]

value
()

The set of warnings that are to be suppressed by the compiler in the
annotated element. Duplicate names are permitted. The second and
successive occurrences of a name are ignored. The presence of
unrecognized warning names is not an error: Compilers must
ignore any warning names they do not recognize. They are, however,
free to emit a warning if an annotation contains an unrecognized
warning name.

The string "unchecked" is used to suppress
unchecked warnings. Compiler vendors should document the
additional warning names they support in conjunction with this
annotation type. They are encouraged to cooperate to ensure
that the same names work across multiple compilers.

Returns
  • the set of warnings to be suppressed

Время прочтения
15 мин

Просмотры 20K

Аннотация — это специальная конструкция языка, связанная с классом, методом или переменной, предоставляющая программе дополнительную информацию, на основе которой программа может предпринять дальнейшие действия или реализовать дополнительную функциональность, такую как генерация кода, проверка ошибок и т. д.

Помимо использования стандартных аннотаций из пакета java.lang, о которых мы поговорим далее, можно также создавать свои аннотации и обрабатывать их.

В этой статье мы обсудим назначение стандартных аннотаций, а также рассмотрим на практическом примере создание и обработку своих аннотаций.

Код примеров вы можете найти на GitHub.

Основы аннотаций

Аннотации начинаются с символа @. Например, в пакете java.lang определены аннотации @Override и @SuppressWarnings.

Сама по себе аннотация не выполняет никаких действий. Она просто предоставляет информацию, которую можно использовать во время компиляции или в рантайме.

В качестве примера рассмотрим аннотацию @Override:

public class ParentClass {
  public String getName() {...}
}

public class ChildClass extends ParentClass {
  @Override
  public String getname() {...}
}

Аннотация @Override используется для обозначения переопределенного метода из базового класса. Приведенная выше программа при компиляции выдаст ошибку, потому что метод getname() в классе ChildClass аннотирован @Override, но в родительском классе ParentClass метода getname() нет.

Используя аннотацию @Override в ChildClass, компилятор проверяет, что имя переопределенного метода в дочернем классе совпадает с именем метода в родительском классе.

Стандартные аннотации

Рассмотрим некоторые из распространенных стандартных аннотаций из пакета java.lang. Чтобы увидеть их влияние на поведение компилятора, запускайте примеры из командной строки, поскольку большинство IDE могут подавлять предупреждения.

@SuppressWarnings

Аннотация @SuppressWarnings используется для подавления предупреждений компилятора. Например, @SuppressWarnings(«unchecked») отключает  предупреждения, связанные с «сырыми» типами (Raw Types). 

Давайте рассмотрим пример использования @SuppressWarnings:

public class SuppressWarningsDemo {

  public static void main(String[] args) {
    SuppressWarningsDemo swDemo = new SuppressWarningsDemo();
    swDemo.testSuppressWarning();
  }

  public void testSuppressWarning() {
    Map testMap = new HashMap();
    testMap.put(1, "Item_1");
    testMap.put(2, "Item_2");
    testMap.put(3, "Item_3");
  }
}

Если мы запустим компиляцию из командной строки с параметром -Xlint:unchecked, то получим следующее сообщение:

javac -Xlint:unchecked ./com/reflectoring/SuppressWarningsDemo.java
Warning:
unchecked call to put(K,V) as a member of the raw type Map

Это пример легаси кода (до Java 5) — в коллекции мы можем случайно сохранить объекты разных типов. Для проверки подобных ошибок на этапе компиляции, были придуманы обобщенные типы (generics, дженерики). Чтобы этот код компилировался без предупреждений измените строку:

Map testMap = new HashMap();

на

Map<Integer, String> testMap = new HashMap<>();

Если подобного легаси кода много, то вы вряд ли захотите вносить изменения, поскольку это влечет за собой много регрессионного тестирования. В этом случае к классу можно добавить аннотацию @SuppressWarning, чтобы логи не загромождались избыточными предупреждениями.

@SuppressWarnings({"rawtypes", "unchecked"})
public class SuppressWarningsDemo {
  ...
}

Теперь при компиляции предупреждений не будет.

@Deprecated

Аннотация @Deprecated используется для пометки устаревших методов или типов.

IDE автоматически обрабатывают эту аннотацию и обычно отображают устаревший метод зачеркнутым шрифтом, сообщая разработчику, что больше не следует его использовать.

В примере ниже метод testLegacyFunction() помечен как устаревший:

public class DeprecatedDemo {

  @Deprecated(since = "4.5", forRemoval = true)
  public void testLegacyFunction() {

    System.out.println("This is a legacy function");
  }
}

В атрибуте since этой аннотации содержится версия, с которой элемент объявлен устаревшим, а forRemoval указывает, будет ли элемент удален в следующей версии.

Теперь вызов устаревшего метода, вызовет предупреждение во время компиляции, указывая, что лучше этот метод не использовать:

./com/reflectoring/DeprecatedDemoTest.java:8: warning: [removal] testLegacyFunction() in DeprecatedDemo has been deprecated and marked for removal
    demo.testLegacyFunction();
      ^           
1 warning

@Override

Мы уже упоминали выше аннотацию @Override. Она используется для проверки переопределенных методов во время компиляции на такие ошибки, как опечатки в регистре символов:

public class Employee {
  public void getEmployeeStatus(){
    System.out.println("This is the Base Employee class");
  }
}

public class Manager extends Employee {
  public void getemployeeStatus(){
    System.out.println("This is the Manager class");
  }
}

Здесь мы хотели переопределить метод getEmployeeStatus(), но неправильно написали имя метода. Это может привести к серьезным ошибкам. Приведенная выше программа скомпилируется и запуститься без проблем, не обнаружив эту ошибку при компиляции.

Если добавить аннотацию @Override к методу getemployeeStatus(), то при компиляции получим следующую ошибку:

./com/reflectoring/Manager.java:5: error: method does not override or implement a method from a supertype
  @Override
  ^
1 error

@FunctionalInterface

Аннотация @FunctionalInterface используется для указания того, что в интерфейсе не может быть более одного абстрактного метода. Если абстрактных методов будет больше одного, то компилятор выдаст ошибку. Функциональные интерфейсы появились в Java 8 для реализации лямбда-выражений и гарантии того, что в них не более одного абстрактного метода.

Но и без аннотации @FunctionalInterface компилятор выдаст ошибку, если вы включите в интерфейс больше одного абстрактного метода. Так зачем же нужна необязательная аннотация @FunctionalInterface?

Давайте рассмотрим следующий пример:

@FunctionalInterface
interface Print {
  void printString(String testString);
}

Если в интерфейс Print мы добавим еще один метод printString2(), то компилятор или IDE выдаст ошибку.

А что, если интерфейс Print находится в отдельном модуле и без аннотации @FunctionalInterface? Разработчики этого модуля могут легко добавить в интерфейс еще один метод и сломать ваш код. Добавив аннотацию @FunctionalInterface, мы сразу получим предупреждение в IDE:

Multiple non-overriding abstract methods found in interface com.reflectoring.Print

Поэтому рекомендуется всегда использовать аннотацию @FunctionalInterface, если интерфейс должен использоваться в качестве лямбды.

@SafeVarargs

Функциональность varargs позволяет создавать методы с переменным количеством аргументов. До Java 5 единственной возможностью создания методов с необязательными параметрами было создание нескольких методов, каждый из которых с разным количеством параметров. Varargs позволяет создать один метод с переменным количеством параметров с помощью следующего синтаксиса:

// можно написать так:
void printStrings(String... stringList)

// вместо этого мы делаем:
void printStrings(String string1, String string2)

Однако при использовании в аргументах метода обобщенных типов выдаются предупреждения. Аннотация @SafeVarargs позволяет подавить их:

package com.reflectoring;

import java.util.Arrays;
import java.util.List;

public class SafeVarargsTest {

   private void printString(String test1, String test2) {
    System.out.println(test1);
    System.out.println(test2);
  }

  private void printStringVarargs(String... tests) {
    for (String test : tests) {
      System.out.println(test);
    }
  }

  private void printStringSafeVarargs(List<String>... testStringLists) {
    for (List<String> testStringList : testStringLists) {
      for (String testString : testStringList) {
        System.out.println(testString);
      }
    }
  }

  public static void main(String[] args) {
    SafeVarargsTest test = new SafeVarargsTest();

    test.printString("String1", "String2");
    test.printString("*******");

    test.printStringVarargs("String1", "String2");
    test.printString("*******");

    List<String> testStringList1 = Arrays.asList("One", "Two");
    List<String> testStringList2 = Arrays.asList("Three", "Four");

    test.printStringSafeVarargs(testStringList1, testStringList2);
  }
}

Методы printString() и printStringVarargs() приводят к одинаковому результату. Но при компиляции для метода printStringSafeVarargs() выдается предупреждение, поскольку в нем используются обобщенные типы:

javac -Xlint:unchecked ./com/reflectoring/SafeVarargsTest.java

./com/reflectoring/SafeVarargsTest.java:28: warning: [unchecked] Possible heap pollution from parameterized vararg type List<String>
  private void printStringSafeVarargs(List<String>... testStringLists) {
                            ^
./com/reflectoring/SafeVarargsTest.java:52: warning: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
    test.printStringSafeVarargs(testStringList1, testStringList2);
                   ^
2 warnings

Добавив аннотацию @SafeVarargs, мы можем избавиться от этого предупреждения:

@SafeVarargs
private void printStringSafeVarargs(List<String>... testStringLists) {

Пользовательские аннотации

Мы можем создавать свои аннотации, например, для реализации следующей функциональности:

  1. Уменьшение дублирования кода.

  2. Автоматизация генерации бойлерплейт кода.

  3. Отлов ошибок во время компиляции, например, потенциальные Null Pointer Exception.

  4. Настройка поведения в рантайме на основе наличия аннотации.

Для примера рассмотрим аннотацию @Company:

@Company{  
  name="ABC"
  city="XYZ"
}
public class CustomAnnotatedEmployee { 
  ... 
}

При создании экземпляров класса CustomAnnotatedEmployee все экземпляры будут содержать одно и то же название компании (name) и города (city) — больше не нужно добавлять эту информацию в конструктор.

Создать пользовательскую аннотацию можно с помощью ключевого слова @interface:

public @interface Company{
}

Чтобы указать информацию об области действия аннотации и о типах элементов, к которым она может быть применена, используются мета-аннотации.

Например, чтобы указать, что аннотация применяется только к классам, используется аннотация @Target(ElementType.TYPE). А мета-аннотация @Retention(RetentionPolicy.RUNTIME) указывает, что аннотация должна быть доступна в рантайме.

С мета-аннотациями наша аннотация @Company выглядит следующим образом:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Company{
}

Далее добавим атрибуты в нашу аннотацию: имя (name) и город (city). Добавляем их, как показано ниже:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Company{
	String name() default "ABC";
	String city() default "XYZ";
}

Создадим класс CustomAnnotatedEmployee и применим к нему аннотацию @Company:

@Company
public class CustomAnnotatedEmployee {

  private int id;
  private String name;

  public CustomAnnotatedEmployee(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public void getEmployeeDetails(){
    System.out.println("Employee Id: " + id);
    System.out.println("Employee Name: " + name);
  }
}

Прочитать аннотацию @Company в рантайме можно следующим образом:

import java.lang.annotation.Annotation;

public class TestCustomAnnotatedEmployee {

  public static void main(String[] args) {

    CustomAnnotatedEmployee employee = new CustomAnnotatedEmployee(1, "John Doe");
    employee.getEmployeeDetails();

    Annotation companyAnnotation = employee
            .getClass()
            .getAnnotation(Company.class);
    Company company = (Company)companyAnnotation;

    System.out.println("Company Name: " + company.name());
    System.out.println("Company City: " + company.city());
  }
}

Результат будет следующий:

Employee Id: 1
Employee Name: John Doe
Company Name: ABC
Company City: XYZ

Анализируя аннотацию в рантайме, мы можем получить доступ к некоторой общей информации обо всех сотрудниках и избежать дублирования кода.

Мета-аннотации

Мета-аннотации — это аннотации, применяемые к другим аннотациям для предоставления информации об аннотации компилятору или среде выполнения.

Мета-аннотации могут ответить на следующие вопросы об аннотации:

  1. Может ли аннотация наследоваться дочерними классами?

  2. Должна ли аннотация отображаться в документации?

  3. Можно ли применить аннотацию несколько раз к одному и тому же элементу?

  4. К какому типу элементов можно применить аннотацию: к классу, методу, полю и т.д.?

  5. Обрабатывается ли аннотация во время компиляции или в рантайме?

@Inherited

По умолчанию аннотация не наследуется от родительского класса к дочернему. Мета-аннотация @Inherited позволяет ей наследоваться:

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Company{
  String name() default "ABC";
  String city() default "XYZ";
}

@Company
public class CustomAnnotatedEmployee {

  private int id;
  private String name;

  public CustomAnnotatedEmployee(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public void getEmployeeDetails(){
    System.out.println("Employee Id: " + id);
    System.out.println("Employee Name: " + name);
  }
}

public class CustomAnnotatedManager extends CustomAnnotatedEmployee{
  public CustomAnnotatedManager(int id, String name) {
    super(id, name);
  }
}

Поскольку CustomAnnotatedEmployee аннотирован @Company, а CustomAnnotatedManager наследуется от него, то нет необходимости ставить аннотацию на класс CustomAnnotatedManager.

Давайте проверим это.

public class TestCustomAnnotatedManager {

  public static void main(String[] args) {
    CustomAnnotatedManager manager = new CustomAnnotatedManager(1, "John Doe");
    manager.getEmployeeDetails();

    Annotation companyAnnotation = manager
            .getClass()
            .getAnnotation(Company.class);
    Company company = (Company)companyAnnotation;

    System.out.println("Company Name: " + company.name());
    System.out.println("Company City: " + company.city());
  }
}

Аннотация @Company доступна, хотя мы не указывали ее явно для класса Manager.

@Documented

@Documented указывает, что аннотация должна присутствовать в JavaDoc.

По умолчанию информация об аннотациях не отображается в JavaDoc-документации, но если использовать @Documented, она появится:

@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Company{
  String name() default "ABC";
  String city() default "XYZ";
}

@Repeatable

@Repeatable позволяет использовать аннотацию несколько раз на одном методе, классе или поле. Для использования @Repeatable — аннотации необходимо создать аннотацию-контейнер, которая хранит значение в виде массива исходных аннотаций:}

@Target(ElementType.TYPE)
@Repeatable(RepeatableCompanies.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableCompany {
  String name() default "Name_1";
  String city() default "City_1";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableCompanies {
  RepeatableCompany[] value() default{};
}

Использовать аннотацию можно следующим образом:

@RepeatableCompany
@RepeatableCompany(name =  "Name_2", city = "City_2")
public class RepeatedAnnotatedEmployee {
}

Протестируем:

public class TestRepeatedAnnotation {

  public static void main(String[] args) {

    RepeatableCompany[] repeatableCompanies = RepeatedAnnotatedEmployee.class
            .getAnnotationsByType(RepeatableCompany.class);
    for (RepeatableCompany repeatableCompany : repeatableCompanies) {
      System.out.println("Name: " + repeatableCompany.name());
      System.out.println("City: " + repeatableCompany.city());
    }
  }
}

Получим следующий результат, отображающий значение нескольких аннотаций @RepeatableCompany:

Name: Name_1
City: City_1
Name: Name_2
City: City_2

@Target

@Target определяет типы элементов, к которым может применяться аннотация. Например, в приведенном выше примере аннотация @Company была определена как TYPE, и поэтому может быть применена только к классам.

Давайте попробуем применить аннотацию @Company к методу:

@Company
public class Employee {

  @Company
  public void getEmployeeStatus(){
    System.out.println("This is the Base Employee class");
  }
}

В этом случае мы получим ошибку компилятора: @Company not applicable to method.

Существуют следующие типы целей, названия которых говорят сами за себя:

  • ElementType.ANNOTATION_TYPE

  • ElementType.CONSTRUCTOR

  • ElementType.FIELD

  • ElementType.LOCAL_VARIABLE

  • ElementType.METHOD

  • ElementType.PACKAGE

  • ElementType.PARAMETER

  • ElementType.TYPE

@Retention

@Retention указывает, когда аннотация будет доступна:

  • SOURCE — аннотация доступна в исходном коде и удаляется после компиляции.

  • CLASS — аннотация сохраняется в class-файле во время компиляции, но недоступна при выполнении программы.

  • RUNTIME — аннотация доступна в рантайме.

Если аннотация нужна только для проверки ошибок во время компиляции, как это делает @Override, мы используем SOURCE. Если аннотация нужна для обеспечения функциональности в рантайме, например, @Test в JUnit, то используем RUNTIME. Давайте поэкспериментируем с разными значениями RetentionPolicy:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ClassRetention {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface SourceRetention {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeRetention {
}

Создадим класс, который использует все три аннотации:

@SourceRetention
@RuntimeRetention
@ClassRetention
public class EmployeeRetentionAnnotation {
}

Для проверки доступности аннотаций запустите следующий код:

public class RetentionTest {

  public static void main(String[] args) {

    SourceRetention[] sourceRetention = new EmployeeRetentionAnnotation()
            .getClass()
            .getAnnotationsByType(SourceRetention.class);
    System.out.println("Source Retentions at runtime: " + sourceRetention.length);

    RuntimeRetention[] runtimeRetention = new EmployeeRetentionAnnotation()
            .getClass()
            .getAnnotationsByType(RuntimeRetention.class);
    System.out.println("Runtime Retentions at runtime: " + runtimeRetention.length);

    ClassRetention[] classRetention = new EmployeeRetentionAnnotation()
            .getClass()
            .getAnnotationsByType(ClassRetention.class);
    System.out.println("Class Retentions at runtime: " + classRetention.length);
  }
}

Результат будет следующим:

Source Retentions at runtime: 0
Runtime Retentions at runtime: 1
Class Retentions at runtime: 0

Итак, мы убедились, что в рантайме доступна только RUNTIME-аннотация.

Классификация аннотаций

Аннотации можно классифицировать по количеству передаваемых в них параметров: без параметров, с одним параметром и с несколькими параметрами.

Маркерные аннотации

Маркерные аннотации не содержат никаких членов или данных. Для определения наличия аннотации можно использовать метод isAnnotationPresent().

Например, если бы у нашей компании было несколько клиентов с разными способами передачи данных, мы могли бы аннотировать класс аннотацией, указывающей способ передачи данных:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CSV {
}

Класс Client может использовать аннотацию следующим образом:

@CSV
public class XYZClient {
    ...
}

Обработать аннотацию можно следующим образом:

public class TestMarkerAnnotation {

  public static void main(String[] args) {

  XYZClient client = new XYZClient();
  Class clientClass = client.getClass();

    if (clientClass.isAnnotationPresent(CSV.class)){
        System.out.println("Write client data to CSV.");
    } else {
        System.out.println("Write client data to Excel file.");
    }
  }
}

На основании присутствия аннотации @CSV, мы можем решить, куда записать информацию — в CSV или в файл Excel. Приведенная выше программа выдаст следующий результат:

Write client data to CSV.

Аннотации с одним значением

Аннотации с одним значением содержат только один атрибут, который принято называть value.

Давайте создадим аннотацию SingleValueAnnotationCompany с одним атрибутом value:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleValueAnnotationCompany {
  String value() default "ABC";
}

Создайте класс, использующий аннотацию:

@SingleValueAnnotationCompany("XYZ")
public class SingleValueAnnotatedEmployee {

  private int id;
  private String name;

  public SingleValueAnnotatedEmployee(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public void getEmployeeDetails(){
    System.out.println("Employee Id: " + id);
    System.out.println("Employee Name: " + name);
  }
}

Запустите следующий пример:

public class TestSingleValueAnnotatedEmployee {

  public static void main(String[] args) {
    SingleValueAnnotatedEmployee employee = new SingleValueAnnotatedEmployee(1, "John Doe");
    employee.getEmployeeDetails();

    Annotation companyAnnotation = employee
            .getClass()
            .getAnnotation(SingleValueAnnotationCompany.class);
    SingleValueAnnotationCompany company = (SingleValueAnnotationCompany)companyAnnotation;

    System.out.println("Company Name: " + company.value());
  }
}

Переданное значение «XYZ» переопределяет значение атрибута аннотации по умолчанию. Результат выглядит следующим образом:

Employee Id: 1
Employee Name: John Doe
Company Name: XYZ

Полные аннотации

Они состоят из нескольких пар «имя-значение». Например, Company(name = "ABC", city = "XYZ"). Рассмотрим наш исходный пример Company:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Company{
  String name() default "ABC";
  String city() default "XYZ";
}

Давайте создадим класс MultiValueAnnotatedEmployee со значением параметров, как показано ниже. Значения по умолчанию будут перезаписаны.

@Company(name = "AAA", city = "ZZZ")
public class MultiValueAnnotatedEmployee {
  
}

Запустите следующий пример:

public class TestMultiValueAnnotatedEmployee {

  public static void main(String[] args) {

    MultiValueAnnotatedEmployee employee = new MultiValueAnnotatedEmployee();

    Annotation companyAnnotation = employee.getClass().getAnnotation(Company.class);
    Company company = (Company)companyAnnotation;

    System.out.println("Company Name: " + company.name());
    System.out.println("Company City: " + company.city());
  }
}

Результат:

Company Name: AAA
Company City: ZZZ

Практический пример

В качестве практического примера обработки аннотаций напишем простой аналог аннотации @Test из JUnit. Пометив методы аннотацией @Test, мы сможем определить в рантайме, какие методы тестового класса нужно запускать как тесты.

Сначала создадим маркерную аннотацию для методов-тестов:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) 
public @interface Test {
}

Далее создадим класс AnnotatedMethods, в котором применим аннотацию @Test к методу test1(). Это позволит выполнить метод в рантайме. У метода test2() аннотации нет и он не должен выполняться.

public class AnnotatedMethods {

  @Test
  public void test1() {
    System.out.println("This is the first test");
  }

  public void test2() {
    System.out.println("This is the second test");
  }
}

Теперь напишем код для запуска тестов из класса AnnotatedMethods:

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class TestAnnotatedMethods {

  public static void main(String[] args) throws Exception {

    Class<AnnotatedMethods> annotatedMethodsClass = AnnotatedMethods.class;

    for (Method method : annotatedMethodsClass.getDeclaredMethods()) {

      Annotation annotation = method.getAnnotation(Test.class);
      Test test = (Test) annotation;

      // If the annotation is not null
      if (test != null) {

        try {
          method.invoke(annotatedMethodsClass
                  .getDeclaredConstructor()
                  .newInstance());
        } catch (Throwable ex) {
          System.out.println(ex.getCause());
        }

      }
    }
  }
}

Через метод getDeclaredMethods() мы получаем методы класса AnnotatedMethods. Затем перебираем методы и проверяем, аннотирован ли метод аннотацией @Test. Наконец, выполняем вызов методов, которые были аннотированы с помощью @Test.

В результате метод test1() выполнится, поскольку он аннотирован @Test, а test2() нет, так как он без аннотации @Test.

Результат:

This is the first test

Заключение

Мы сделали обзор основных стандартных аннотаций и рассмотрели, как создавать и обрабатывать свои аннотации.

Возможностей по использованию аннотаций гораздо больше, чем мы рассмотрели. Например, можно автоматически генерировать код для паттерна Builder. Шаблон проектирования Builder (строитель) используется как альтернатива конструкторам, когда в конструкторы передается много параметров или есть необходимость в нескольких конструкторах с необязательными параметрами. При большом количестве таких классов  возможность генерации кода обработчиком аннотаций сэкономит много времени и поможет избежать дублирования кода.

Примеры кода вы можете найти на GitHub.


Всех желающих приглашаем на Demo-занятие «Объектно-ориентированное и функциональное программирование». На вебинаре поговорим о стилях программирования и необходимости каждого из них. Разберём основные принципы объектно-ориентированного стиля (Инкапсуляция, Наследование, Полиморфизм), а также возможности функционального стиля, которые предоставляет язык Java. Регистрация для всех желающих по ссылке.

Понравилась статья? Поделить с друзьями:
  • Supported api 3 как исправить на планшете
  • Support viber com ошибка при регистрации что делать
  • Support viber com ошибка при регистрации как исправить на русском языке
  • Support viber com ошибка при регистрации как исправить на андройде на русском
  • Support nintendo com switch error 2005 0003