Это очень интересный вопрос. Соответствующей частью спецификации является §15.12.4.2. Оцените аргументы :
Если вызываемый метод является методом переменной arity m
, он обязательно имеет n > 0 формальных параметров. Последний формальный параметр m
обязательно имеет тип T[]
для некоторых T
, и m
обязательно вызывается с k ≥ 0 действительными выражениями аргумента.
Если m
вызывается с k ≠ n фактическими выражениями аргумента или, если m
вызывается с k = n фактические выражения аргумента и тип k '-ого выражения аргумента несовместимы с присваиванием T[]
, тогда список аргументов (e<sub>1</sub>
, ..., e<sub>n-1</sub>
, e<sub>n</sub>
, ..., e<sub>k</sub>
) оценивается так, как если бы оно было записано как (e<sub>1</sub>
, ..., e<sub>n-1</sub>
, new
| T[]
| {
e<sub>n</sub>
, .. ., e<sub>k</sub>
}
), где | T[]
| обозначает стирание (§4.6) T[]
.
Интересно неясно, что на самом деле означает «какой-то * 1047». Простейшим и наиболее простым решением будет объявленный тип параметра вызванного метода; это было бы совместимо с назначением, и нет никакого фактического преимущества использования другого типа. Но, как мы знаем, javac
не идет по этому пути и использует какой-то общий базовый тип всех аргументов или выбирает некоторые границы в соответствии с неким неизвестным правилом для типа элемента массива. В настоящее время вы можете даже найти некоторые приложения в дикой природе, полагаясь на это поведение, предполагая получить некоторую информацию о фактическом T
во время выполнения путем проверки типа массива.
Это приводит к некоторым интересным последствиям:
static AutoCloseable[] ARR1;
static Serializable[] ARR2;
static <T extends AutoCloseable & Serializable> void method(T... args) {
ARR1 = args;
ARR2 = args;
}
public static void main(String[] args) throws Exception {
method(null, null);
ARR2[0] = "foo";
ARR1[0].close();
}
javac
решает создать массив фактического типа Serializable[]
здесь, несмотря на то, что тип параметра метода равен AutoClosable[]
после применения стирания типа, что является причиной, по которой назначение String
возможно во время выполнения , Таким образом, произойдет сбой только в последнем операторе при попытке вызвать для него метод close()
с
Exception in thread "main" java.lang.IncompatibleClassChangeError: Class java.lang.String does not implement the requested interface java.lang.AutoCloseable
Здесь обвиняется класс String
, хотя мы могли бы поместить любой объект Serializable
в массив, поскольку фактическая проблема заключается в том, что поле static
формально объявленного типа AutoCloseable[]
ссылается на объект фактический тип Serializable[]
.
Хотя это специфическое поведение JSM HotSpot, которое мы когда-либо заходили так далеко, поскольку его верификатор не проверяет назначения, когда задействованы типы интерфейса (включая массивы типов интерфейса), но откладывает проверку, реализует ли интерфейс настоящий класс до последнего возможного момента при попытке вызвать на нем интерфейсный метод.
Интересно, что приведения типов строги, когда появляются в файле класса:
static <T extends AutoCloseable & Serializable> void method(T... args) {
AutoCloseable[] a = (AutoCloseable[])args; // actually removed by the compiler
a = (AutoCloseable[])(Object)args; // fails at runtime
}
public static void main(String[] args) throws Exception {
method();
}
Хотя решение javac
для Serializable[]
в приведенном выше примере кажется произвольным, должно быть ясно, что независимо от того, какой тип он выбирает, одно из назначений полей будет возможно только в JVM с проверкой слабого типа. Мы также можем выделить более фундаментальный характер проблемы:
// erased to method1(AutoCloseable[])
static <T extends AutoCloseable & Serializable> void method1(T... args) {
method2(args); // valid according to generic types
}
// erased to method2(Serializable[])
static <T extends Serializable & AutoCloseable> void method2(T... args) {
}
public static void main(String[] args) throws Exception {
// whatever array type the compiler picks, it would violate one of the erased types
method1();
}
Хотя это на самом деле не отвечает на вопрос о том, что использует настоящее правило javac
(кроме того, что оно использует «some T
»), оно подчеркивает важность обработки массивов, созданных для параметра varargs , как предназначено: временное хранилище (не присваивать полям) произвольного типа, о котором вам лучше не беспокоиться.