Java: метод Vararg вызывается с явным массивом подклассов

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

private static class Original {
    public String getValue() {
        return "Foo";
    }
}

private static class Wrapper extends Original {
    private Original orig;

    public Wrapper(Original orig) {
        this.orig = orig;
    }

    @Override
    public String getValue() {
        return orig.getValue();
    }
}

public static void test(Original... o) {
    if (o != null && o.length > 0) {
        for (int i = 0; i < o.length; i++) {
            if (o[i] instanceof Wrapper) {
                o[i] = ((Wrapper) o[i]).orig; // Throws java.lang.ArrayStoreException at runtime
            }
        }
    }
}

public static void main(String[] args){
    test(new Wrapper[] { // Explicitly create an array of subclass type
        new Wrapper(new Original())
    });
}

Этот пример не выдает предупреждений или ошибок во время компиляции. Похоже, что компилятор решает, что Wrapper[] содержит Wrapper экземпляров, что фактически означает, что это определенно экземпляры класса Original. Это прекрасно.

Однако во время выполнения экземпляр Wrapper[] напрямую передается в метод. Я думал, что было бы достаточно разумно разорвать этот массив и воссоздать экземпляр Original[] во время выполнения, но похоже, что это не так.

Это поведение где-нибудь задокументировано (например, JLS)? Обычный программист вроде меня всегда будет предполагать, что я могу манипулировать параметром vararg Original..., как будто это Original[].


person Jai    schedule 03.07.2018    source источник
comment
Так какой именно вопрос?   -  person Hearen    schedule 03.07.2018
comment
Эта проблема является причиной того, что дженерики Java инвариантны (например, List<Dog> не является List<Animal>).   -  person Andy Turner    schedule 03.07.2018
comment
JLS, раздел 4.10.3: если S и T оба являются ссылочными типами, то S[] ›1 T[] тогда и только тогда, когда S ›1 T.   -  person Andy Turner    schedule 03.07.2018
comment
Я немного смущен тем, что тип o равен Wrapper[], а не Original[]. Разве переданный параметр не должен быть помещен в массив varargs? Или есть специальное исключение для массивов, соответствующих типу параметра?   -  person markspace    schedule 03.07.2018
comment
Нет, @markspace, поскольку массив Wrapper не является Original, вы не можете поместить его в массив Original. Из других ссылочных типов вы можете знать, что тип объекта во время выполнения может быть более точным, чем объявленный тип ссылочной переменной. Это возможно и для массивов: объявленный тип o — это массив Original, но тип среды выполнения — массив Wrapper.   -  person Ole V.V.    schedule 03.07.2018
comment
Хотя это немного странно, что это приемлемый синтаксис. Я только что проверил, и Object... принимает любой массив ссылок, так что это последовательно, но немного странно. Массив Integer передается сам по себе, а не как Object[], содержащий одну ссылку на Integer[].   -  person markspace    schedule 03.07.2018
comment
Ха! Спасибо всем. Я продолжал думать, что это проблема, связанная с varargs. Так что, в конце концов, это просто потому, что я недостаточно разбирался в массивах.   -  person Jai    schedule 03.07.2018


Ответы (1)


Да, когда Wrapper есть Original, то и Wrapper[] есть Original[] (меня это тоже удивило, когда я это понял).

Ваш Wrapper является подтипом Original, поскольку он расширяет класс Original.

И да, отношение подтипа между типами массива может привести к ArrayStoreException, если вызываемый метод попытается сохранить Original, который не является Wrapper, в переданный массив. Но это не проверяется во время компиляции. Насколько я понимаю, именно поэтому у нас есть тип ArrayStoreException, поскольку обычно другие попытки сохранить неправильный тип в массиве обнаруживаются во время компиляции. В документации ArrayStoreException< есть хороший краткий пример. /а>. Этот пример также демонстрирует, что на самом деле он не имеет ничего общего с varargs или вызовами методов, а для всех массивов.

Язык Java был разработан таким образом, начиная с версии 1 (что задолго до того, как были введены varargs, BTW). Спасибо Энди Тернеру за нахождение ссылки на спецификацию языка Java (JLS): она находится в раздел 4.10.3 Подтипы между типами массивов:

Если S и T оба являются ссылочными типами, то S[] >_1 T[] тогда и только тогда, когда S >_1 T.

person Ole V.V.    schedule 03.07.2018
comment
Да, я был слишком одержим тем фактом, что это произошло в контексте vararg, что мне не пришло в голову, что это просто проблема с массивами. Я отмечу этот ответ как правильный, несмотря на то, что этот вопрос уже помечен как дубликат, так как я думаю, что было бы неплохо найти точное предложение JLS для поддержки этого. - person Jai; 03.07.2018