Ошибка "недопустимый универсальный тип для instanceof" при использовании локальных классов
У меня есть следующий код Java, который использует локальный класс.
import java.util.Arrays;
public class X<T> {
void m() {
class Z {}
for (Object o : Arrays.asList(1, 2, 3))
if (o instanceof Z) {}
}
}
Он не компилируется со следующим сообщением об ошибке:
X.java:8: error: illegal generic type for instanceof
if (o instanceof Z) {}
^
1 error
Я понимаю, что локальный класс Z
наследует сигнатуру универсального типа X<T>
, являющуюся внутренним классом. Такая же ошибка компиляции появляется в этом примере, где Z
не локально, а по-прежнему внутренне:
import java.util.Arrays;
public class X<T> {
class Z {}
void m() {
for (Object o : Arrays.asList(1, 2, 3))
if (o instanceof Z) {} // Compilation error
}
}
Это можно обойти, либо сделав Z
не внутренним/статическим:
import java.util.Arrays;
public class X<T> {
static class Z {}
void m() {
for (Object o : Arrays.asList(1, 2, 3))
if (o instanceof Z) {} // Compiles now
}
}
Или с помощью квалификации XZ
:
import java.util.Arrays;
public class X<T> {
class Z {}
void m() {
for (Object o : Arrays.asList(1, 2, 3)) {
if (o instanceof X.Z) {} // Compiles now
if (o instanceof X<?>.Z) {} // Also
}
}
}
Но как я могу квалифицировать локальный класс или обойти это ограничение, не меняя сам локальный класс?
Ответы
Ответ 1
Мне кажется, это упущение или ограничение в языке Java, и я не думаю, что это возможно.
Ссылочный тип в выражении instanceof
должен быть reifiable в соответствии с JLS 4.7, то есть он должен быть выражен как reifiable тип его полностью определенным именем. В то же время в JLS 6.7 говорится, что локальные классы не имеют полностью определенного имени, поэтому они не могут быть выражены как пригодные для переопределения.
Если вы объявляете Z как универсальный, оператор instanceof
рассматривает Z
как необработанный тип, где все его универсальные свойства - в данном случае включающий класс - также считаются необработанными. (Подобно универсальным методам необработанного типа, рассматриваемым как необработанные, несмотря на какую-либо универсальную сигнатуру. Это является мерой для сохранения обратной совместимости при обобщении типов.) Поскольку любой необработанный тип является переопределимым, объявление Z как универсального будет компилироваться.
Ответ 2
Возможный обходной путь - использовать отражение:
import java.util.Arrays;
public class X<T> {
void m() {
class Z {}
for (Object o : Arrays.asList(1, 2, 3))
if (Z.class.isInstance(o)) {}
}
}
Ответ 3
По-видимому, с помощью Z общая компиляция завершается успешно. Я ожидал, что потребуется <T>
в качестве параметра типа, но вы просто должны сделать его универсальным, так что все будет делать
import java.util.Arrays;
public class X<T> {
void m() {
class Z<Anything> {}
for (Object o : Arrays.asList(1, 2, 3))
if (Z.class.isInstance(o)) {}
}
}
Правильное решение будет квалифицировать локальный класс, но я не думаю, что вы можете. Либо вы реорганизуете его в частный статический класс, либо это, вероятно, лучшее, что вы можете получить.
Ответ 4
Это должно работать либо. Используя отражение тоже. Но кажется верным решением.
import java.util.Arrays;
public class X<T> {
void m() {
class Z2 {
}
for(Object o: Arrays.asList(1,2,3)) {
if(Z2.class.isAssignableFrom(o.getClass())) {
}
}
}
}
Ответ 5
Любопытно. Я думаю, что это можно объяснить следующими частями спецификации:
Согласно JLS 15.20.2:
Это ошибка времени компиляции, если ReferenceType, упомянутый после оператора instanceof, не обозначает ссылочный тип, который можно переопределять (§4.7).
И в соответствии с JLS 4.7:
Тип подлежит возврату тогда и только тогда, когда выполняется одно из следующих:
-
Это относится к неуниверсальному объявлению класса или типа интерфейса.
-
Это параметризованный тип, в котором все аргументы типа являются неограниченными подстановочными знаками (§4.5.1).
-
Это необработанный тип (§4.8).
-
Это примитивный тип (§4.2).
-
Это тип массива (§10.1), тип элемента которого является reifiable.
-
Это вложенный тип, в котором для каждого типа T, разделенного символом ".", Сам T является переопределенным.
Например, если универсальный класс X имеет универсальный класс-член Y, то тип XY может быть reifiable, потому что X является reifiable, а Y является reifiable. Тип XY не является reifiable, потому что Y не является reifiable.
Локальный класс является вложенным типом. Он неявно рассматривается как X<T>.Z
(из этого следует, что если вы сделаете внешний класс не универсальным и не скомпилируете, инструкция instanceof для байт-кода ссылается на class X$1Z
). Как таковой, он не подлежит переуступке.
Если вы сделаете локальный класс универсальным, как продемонстрировал Эдоардо Вакки, то он работает, потому что использование instanceof Z
- это использование необработанного типа.