Ошибка "недопустимый универсальный тип для 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 - это использование необработанного типа.