Java generics SuppressWarnings ( "unchecked" ) тайна

Почему код альтернативный (1) компилируется без предупреждений, а альтернативный код (2) выдает предупреждение "непроверенного броска"?

Общий для обоих:

class Foo<T> {
    Foo( T [] arg ) {
    }
}

Альтернатива (1):

class Bar<T> extends Foo<T> {
    protected static final Object [] EMPTY_ARRAY = {};

    @SuppressWarnings("unchecked")
    Bar() {
         super( (T []) EMPTY_ARRAY );
    }
}

Альтернатива (2):

class Bar<T> extends Foo<T> {
    @SuppressWarnings("unchecked")
    Bar() {
         super( (T []) EMPTY_ARRAY );
    }

    protected static final Object [] EMPTY_ARRAY = {};
}

Альтернатива (2) производит:

javac -Xlint:unchecked Foo.java Bar.java 
Bar.java:4: warning: [unchecked] unchecked cast
             super( (T []) EMPTY_ARRAY );
                           ^
  required: T[]
  found:    Object[]
  where T is a type-variable:
    T extends Object declared in class Bar
1 warning

Это:

java version "1.7.0_07"
Java(TM) SE Runtime Environment (build 1.7.0_07-b10)
Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)

Ответ 1

Я не могу ничего найти в JLS, @SuppressWarnings (JLS 9.6.3.5) и непроверенных предупреждений (JLS 5.1.9), разделы, похоже, не имеют проблем, которые могут привести к этой проблеме. Моя догадка (без тестирования вашего SSCE сама) заключается в том, что вы нашли ошибку в компиляторе. Я бы рекомендовал отправить отчет об ошибке в Oracle и добавить ссылку на ваш вопрос.

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

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

Edit

Я не вижу, как связанное предложение будет работать в случае моего EMPTY_ARRAY, который является static final.

Не делайте это static final больше и предоставляйте Class<T> в своем конструкторе:

@SuppressWarnings("unchecked") // Still need this
public Bar(Class<T> clazz) {
    super((T[]) Array.newInstance(clazz, 0));
}

Java почти никогда не использует значение переменной final для предупреждений, за исключением случаев с мертвым кодом. В противном случае вы получите такие крайние случаи:

class Bar<T> extends Foo<T> {
    // Is it really empty?
    protected static final Object [] EMPTY_ARRAY = SomeOtherClass.getEmptyArray();

    @SuppressWarnings("unchecked")
    Bar() {
         super( (T []) EMPTY_ARRAY );
    }
}

Им придется написать эту логику в компилятор. Это ненужное осложнение для краевых случаев, таких как "пустые массивы", и, кроме того, это кастинг, подобный этому, в конечном итоге вызывает запах кода.

Другой вариант, который может возникнуть помимо этого ответа, - использовать var args. Foo:

class Foo<T> {
    Foo( T ... arg ) {
    }
}

И Bar:

class Bar<T> extends Foo<T> {

    Bar() {
         super();
    }
}

Это должно работать, и оно устраняет все кастинга, пустые массивы, предупреждения и т.д. Подробнее о var args и их возможных вызовах здесь.

Ответ 2

Я могу подражать этому странному поведению на моем компьютере с Windows 7 64b с помощью:

  • Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
  • OpenJDK Runtime Environment (build 1.8.0-ea-lambda-nightly-h1669-20121030-b63-b00)

Это означает, что затронуты как OpenJDK, так и Oracle JDK, как JDK7, так и JDK8 (да, вы уже можете скачать его).

Eclipse, поскольку он использует собственный JDT-компилятор, не имеет этой проблемы.

Так кажется, что это действительно ошибка javac. Если вы сообщите об этом, пожалуйста, держите меня в курсе.

EDIT:

Я также установил установку JDK6 на моем компьютере, поэтому я попробовал это и фактически, он работает без предупреждения в обоих случаях, что является правильным поведением:

  • Java(TM) SE Runtime Environment (build 1.6.0_23-b05)

Хотя мои Windows - 64b, все упомянутые JDK - всего 32b.

Ответ 3

Я смог воспроизвести поведение с этой упрощенной настройкой:

class Bar<T> {
   @SuppressWarnings("unchecked")
   Bar() {
      T[]dummy = (T[]) EMPTY_ARRAY;
   }

   private static final Object [] EMPTY_ARRAY = {};
}

Как предположил Брайан, это похоже на ошибку в компиляторе. Кроме того, это поведение ограничено массивами - замена EMPTY_ARRAY на Object и отбрасывание его на T не выдает предупреждения, как ожидалось.

java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)

Ответ 4

Это ошибка как в Oracle, так и в OpenJDK 7 и 8.

Вы не можете (не должны) компилировать предупреждения/ошибки из переопределения объявлений в классе.

Ошибки времени выполнения, да; ошибки компилятора, нет.

Это Ошибка 8016636 (спасибо за его регистрацию), но за год не было никакой активности.

К сожалению, это не редкость в Oracle bug tracker.


Дополнительная странность:

  • Предупреждение подавляется, если EMPTY_ARRAY не final.

  • По иронии судьбы, предупреждение подавляется, если вы инициализируете его не-массивом, например. Object EMPTY_ARRAY = new Object(). (Но не делайте этого...)