вторник, 3 июля 2012 г.

@SuppressWarnings: пример

@SuppressWarnings в java (list of warnings) список предупреждений

1) Praemonitus praemunitus (предупреждён, значит, вооружён)
Практически все компиляторы в процессе компилирования исходного программного кода выполняют статический анализ этого кода, что позволяет идентифицировать и предупреждать о наличии некоторых общих ошибок программирования. Положительный эффект заключается в том, что каждое имеющееся предупреждение - потенциальная проблема, и чем раньше эта проблема выявляется (в данном случае - уже на этапе компиляции), тем дешевле её решение. В целом, многие опытные разработчики придерживаются мнения, что конечный код не должен вызывать предупреждений компилятора. Более того, в некоторых компаниях идут дальше и настраивают политики систем контроля версий таким образом, чтобы коммиты не вызывали подобных предупреждений.
Однако жизнь такова, что иногда исключения из строгих правил всё-таки возникают. И оказывается, что в каком-то конкретном случае выдаваемое компилятором предупреждение легально, и что было бы здорово указать  компилятору проигнорировать конкретное предупреждение. Как вообще можно управлять политикой предупреждений в java?

2) Опция компилятора -Xlint
Пусть у нас есть следующий метод, в котором имеется потенциальная проблема: использование коллекции без generics.
package by.father.examples.warnings;

import java.util.ArrayList;
import java.util.List;

public class MethodWithUncheckedOperation {

    public void someUnusedMethodWithUncheckedOperation () {
        List list = new ArrayList();
        list.add("element");
        list.get(0).toString();
    }

}
Итак, пытаемся компилировать:
E:\warni\dev\examples\src\main\java\by\father\examples\warnings>java -version
java version "1.6.0_33"
Java(TM) SE Runtime Environment (build 1.6.0_33-b03)
Java HotSpot(TM) Client VM (build 20.8-b03, mixed mode, sharing)

E:\warni\dev\examples\src\main\java\by\father\examples\warnings>javac MethodWithUncheckedOperation.java
Note: MethodWithUncheckedOperation.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Компиляция прошла без предупреждений, однако нам сделали замечание. Смотрим, что нам предложит компилятор в списке опций:
E:\warni\dev\examples\src\main\java\by\father\examples\warnings>javac -X
  -Xlint                     Enable recommended warnings
  -Xlint:{all,cast,deprecation,divzero,empty,unchecked,fallthrough,path,serial,finally,overrides,-cast,-deprecation,-divzero,-empty,-unchecked,-fallth
rough,-path,-serial,-finally,-overrides,none}Enable or disable specific warnings
    ...
These options are non-standard and subject to change without notice.
Выясняется, что опция -Xlint позволяет включить все рекомендуемые предупреждения, а -Xlint:name - только необходимые (при этом приводится список поддерживаемых компиляторов предупреждений). Попробуем:
E:\warni\dev\examples\src\main\java\by\father\examples\warnings>javac -Xlint:all MethodWithUncheckedOperation.java
MethodWithUncheckedOperation.java:13: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.List
        list.add("element");
                ^
1 warning

E:\warni\dev\examples\src\main\java\by\father\examples\warnings>javac -Xlint:unchecked MethodWithUncheckedOperation.java
MethodWithUncheckedOperation.java:13: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.List
        list.add("element");
                ^
1 warning

E:\warni\dev\examples\src\main\java\by\father\examples\warnings>javac -Xlint:none MethodWithUncheckedOperation.java
Note: MethodWithUncheckedOperation.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Таким образом,
  • по умолчанию компилятор не выдаёт предупреждения (равноценно применению -Xlint:none или стандартной -nowarn);
  • применение опции -Xlint:name позволяет нам сказать компилятору, нужно ли формировать предупреждения, и какие именно;
  • список доступных параметров -Xlint является "компиляторо-зависимым"; примеры списков предупреждений, доступных для подавления в компиляторах IBM 1.6.0 JDK for Linux x86_64 и Sun 1.6.0_u14 for Linux x86_64, можно посмотреть тут: Eclipse SuppressWarnings Annotation. В целом, они ничем не отличаются от только что представленного варианта. А вот вывод для компилятора Sun 1.5.0:
E:\warni\dev\examples\src\main\java\by\father\examples\warnings>"C:\Java\jdk1.5.0_12\bin\java.exe" -version
java version "1.5.0_12"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_12-b04)
Java HotSpot(TM) Client VM (build 1.5.0_12-b04, mixed mode)

E:\warni\dev\examples\src\main\java\by\father\examples\warnings>"C:\Java\jdk1.5.0_12\bin\javac.exe" -X
  -Xlint                     Enable recommended warnings
  -Xlint:{all,deprecation,unchecked,fallthrough,path,serial,finally,-deprecation,-unchecked,-fallthrough,-path,-serial,-finally}Enable or disable specific warnings
  • следует помнить, что опции с ключом -X объявлены как нестандартные со всеми вытекающими последствиями.

Казалось бы, всё хорошо. Однако с помощью -Xlint:name мы можем управлять целыми типами предупреждений, и не можем выделить отдельные классы, методы, переменные и пр. И тут на помощь приходит аннотация @SupressWarnings.

3) Аннотация @SupressWarnings
Интерфейс: public interface SuppressWarnings
Реализует: интерфейс Annotation
Появился: jdk 1.5
JavaDocs: http://docs.oracle.com/javase/7/docs/api/java/lang/SuppressWarnings.html (eng), http://doc.java.sun.com/DocWeb/api/java.lang.SuppressWarnings (ru)
Параметры: String[] value - множество предупреждений в аннотированном элементе, о которых компилятор умалчивает.
Допускаются дублирующиеся имена. Второе и все последующие упоминания одного и того же имени игнорируются. Наличие неизвестных компилятору имён не является ошибкой: компиляторы должны игнорировать любые имена предупреждений, которые они не могут распознать. Однако, допускается генерация предупреждения, если аннотация содержит неизвестное имя предупреждения. Производители компиляторов должны документировать имена предупреждений, которые они поддерживают с данным типом аннотации. Поощряется сотрудничество для поддержания одинаковых имён среди разнообразных компиляторов.
Примеры: @SuppressWarnings("unchecked"), @SuppressWarnings(value="unchecked"), @SuppressWarnings({"unused", "deprecation"})

Теория.
Аннотация @SupressWarnings  указывает на то, что именованное предупреждение (warning) компилятора должно быть подавлено в аннотируемом элементе (и во всех содержащихся в нём программных элементах). Множество подавленных предупреждений в данном элементе - это супермножество подавленных предупреждений во всех вложенных элементах. К примеру, если вы аннотируете класс для подавления одного предупреждения и аннотируете метод для подавления другого, то в методе будут подавляться оба предупреждения. Аннотацию всегда следует использовать как можно ближе к той части кода, для которой она необходима - это более эффективно. Так, если требуется подавить предупреждение в специфичном методе, нужно аннотировать скорее этот метод, чем весь класс, который его содержит.

Пример использованного ранее метода с добавленными аннотациями:
    @SuppressWarnings({"unchecked", "unused"})
    public void someUnusedMethodWithUncheckedOperation () {
        List list = new ArrayList();
        list.add("element");
        list.get(0).toString();
    }
Таким образом, с помощью аннотации мы можем помечать отдельные классы, методы и переменные, что даёт нам высокую гибкость настройки. Более того, различные IDE расширяют список поддерживаемых предупреждений (соответственно, мы получаем compiler-specific + IDE-specific списки). Например, список параметров для Eclipse можно посмотреть здесь
Eclipse platform help: Excluding warnings using @SuppressWarnings. С IntelliJ IDEA немного сложнее, найти подобный список не получилось; похоже, что в этой IDE он разбросан по мощному инструменту InspectionGadgets.

Давайте рассмотрим подробнее набор предупреждений, поддерживаемых Sun-овским компилятором. Итак, можно подавлять предупреждения, связанные с:
  • all - любые;
  • cast - приведением типов;
  • deprecation (dep-ann) - использованием устаревшего кода;
  • divzero - делением на нуль;
  • empty - ? (не совсем понятно, информации не нашёл, возможно, связано с наличием пустых блоков?);
  • unchecked - непроверенными операциями;
  • fallthrough - некорректным использованием оператора switch (например, когда хотя бы в одном из case'ов пропущен завершающий break);
  • path - обсутствием заданных директорий (входящих в classpath, sourcepath и т.д.);
  • serial - пропуском поля serialVersionUID для сериализуемого класса;
  • finally - наличием некорректных блоков finally;
  • overrides - некорректным переопределением (например, переопределён equals(), но не hashCode()).
Здесь @SuppressWarnings values (eng) хороший пост с примерами и пояснениями, а здесь свежая статья List of valid @suppresswarning annotation parameters in Java for Eclipse and IBM RAD IDE (eng).

4) Итого
- предупреждения компилятора - хорошая возможность проверить свой код на наличие проблем определённых типов;
- на практике следует добиваться того, чтобы код компилировался без предупреждений;
- однако в некоторых случаях предупреждений не избежать;
- опция компилятора -Xlint позволяет включать/отключать выдачу предупреждений определённых типов;
- появившаяся в java 5 аннотация @SuppressWarning позволяет решать эту же задачу гораздо более гибко;
- нужно помнить, что как опции -Xlint, так и аннотация @SuppressWarning зависят от используемых компилятора и IDE.

3 комментария: