Глава 10. Массивы
Оглавление
В языке программирования Java массивы являются объектами (§4.3.1), создаются динамически и могут быть присвоены переменным типа Object (§4.3.2). Все методы класса Object могут быть вызваны для массива.
Объект массива содержит ряд переменных. Количество переменных может быть нулевым, в этом случае массив считается пустым. Переменные, содержащиеся в массиве, не имеют имен; вместо этого к ним обращаются с помощью выражений доступа к массиву, использующих целочисленные индексы, неотрицательные целые значения. Эти переменные называются компонентами массива. Если массив имеет n компонентов, то n называется длиной массива; к компонентам массива обращаются с помощью целочисленных индексов от 0 до n - 1 включительно.
Все компоненты массива имеют один и тот же тип, который называется типом компоненты массива. Если тип компоненты массива T, то тип самого массива записывается как T[].
Тип компоненты массива сам по себе может быть типом массива. Компоненты такого массива могут содержать ссылки на подмассивы. Если, начиная с любого типа массива, рассматривать его тип компоненты, а затем (если это также тип массива) тип компоненты этого типа и так далее, в конечном итоге необходимо достичь типа компоненты, который не является типом массива; это называется типом элемента исходного массива, а компоненты на этом уровне структуры данных называются элементами исходного массива.
Есть некоторые ситуации, в которых элемент массива может быть массивом: если тип элемента является Object или Cloneable или java.io.Serializable, то некоторые или все элементы могут быть массивами, так как любой объект массива может быть присвоен любой переменной этих типов.
Типы массивов используются в объявлениях и в выражениях приведения типов (§15.16).
Тип массива записывается как имя типа элемента, за которым следует некоторое количество пар пустых квадратных скобок []. Количество пар скобок указывает глубину вложенности массива.
Каждая пара скобок в типе массива может быть аннотирована анотациями типов (§9.7.4). Анотация применяется к паре скобок (или эллипсису, в объявлении параметра с переменным числом аргументов), которая следует за ней.
Тип элемента массива может быть любым типом, примитивным или ссылочным. В частности:
-
Разрешены массивы с типом интерфейса в качестве типа элемента.
Элементом такого массива может быть ссылка null или экземпляр любого типа, реализующего интерфейс.
-
Разрешены массивы с типом класса
abstractв качестве типа элемента.Элементом такого массива может быть ссылка null или экземпляр любого подкласса класса
abstract, который сам по себе не являетсяabstract.
Длина массива не является частью его типа.
Супертипы типа массива указаны в §4.10.3.
Отношение супертипа для типов массивов не такое же, как отношение суперкласса. Прямой супертип Integer[] — это Number[] в соответствии с §4.10.3, но прямой суперкласс Integer[] — это Object согласно Class объекту для Integer[] (§10.8). Это не имеет практического значения, потому что Object также является супертипом всех типов массивов.
Переменная типа массива хранит ссылку на объект. Объявление переменной типа массива не создаёт объект массива или не выделяет место для компонентов массива. Оно создаёт только саму переменную, которая может содержать ссылку на массив. Однако часть инициализатора декларатора (§8.3, §9.3, §14.4.1) может создать массив, ссылка на который затем становится начальным значением переменной.
Пример 10.2-1. Объявления переменных массивов
int[] ai; // array of int
short[][] as; // array of array of short
short s, // scalar short
aas[][]; // array of array of short
Object[] ao, // array of Object
otherAo; // array of Object
Collection<?>[] ca; // array of Collection of unknown type
Приведённые выше объявления не создают объектов массивов. Ниже приведены примеры объявлений переменных массивов, которые создают объекты массивов:
Exception ae[] = new Exception[3];
Object aao[][] = new Exception[2][3];
int[] factorial = { 1, 1, 2, 6, 24, 120, 720, 5040 };
char ac[] = { 'n', 'o', 't', ' ', 'a', ' ',
'S', 't', 'r', 'i', 'n', 'g' };
String[] aas = { "array", "of", "String", };
Тип массива переменной зависит от пар скобок, которые могут быть частью типа в начале объявления переменной, или в части декларатора переменной, или и то, и другое. В частности, при объявлении поля, формального параметра, локальной переменной или компонента записи (§8.3, §8.4.1, §9.3, §9.4, §14.4.1, §14.14.2, §15.27.1, §8.10.1), тип массива переменной определяется следующим образом:
-
тип элемента, который появляется в начале объявления; затем,
-
любые пары скобок, которые следуют за Идентификатором переменной в деклараторе (не применимо для параметра с переменной арностью или компонента записи); затем,
-
любые пары скобок, которые появляются в типе в начале объявления (где многоточие параметра с переменной арностью или компонента записи с переменной арностью обрабатывается как пара скобок).
Тип результата метода (§8.4.5) может быть типом массива. Точный тип массива зависит от пар скобок, которые могут появляться в части типа в начале объявления метода или после списка формальных параметров метода, или и то, и другое. Тип массива определяется следующим образом:
-
тип элемента, который появляется в Результате; затем,
-
любые пары скобок, которые следуют за списком формальных параметров; затем,
-
любые пары скобок, которые появляются в Результате.
Не рекомендуется использовать «смешанную запись» в объявлениях переменных массивов, где пары скобок появляются как в типе, так и в деклараторах; также не рекомендуется использовать в объявлениях методов, где пары скобок появляются как до, так и после списка формальных параметров.
Пример 10.2-2. Переменные массивов и типы массивов
Выражение объявления локальной переменной:
byte[] rowvector, colvector, matrix[];
эквивалентно:
byte rowvector[], colvector[], matrix[][];
поскольку тип массива каждой локальной переменной не меняется. Аналогично, выражение объявления локальной переменной:
int a, b[], c[][];
эквивалентно серии операторов объявления:
int a; int[] b; int[][] c;
Скобки разрешены в деклараторах как дань традициям C и C++. Однако общие правила объявления переменных позволяют скобкам появляться как в типе, так и в деклараторах, так что выражение объявления локальной переменной:
float[][] f[][], g[][][], h[]; // Yechh!
эквивалентно серии объявлений:
float[][][][] f; float[][][][][] g; float[][][] h;
Из-за того, как формируются типы массивов, следующие объявления параметров имеют одинаковый тип массива:
void m(int @A [] @B [] x) {}
void n(int @A [] @B ... y) {}
И, возможно, неожиданно, следующие объявления полей имеют одинаковый тип массива:
int @A [] f @B []; int @B [] @A [] g;
После создания объекта массива его длина никогда не изменяется. Чтобы сделать переменную массива ссылкой на массив другой длины, переменной необходимо присвоить ссылку на другой массив.
Одна переменная типа массива может содержать ссылки на массивы различной длины, потому что длина массива не является частью его типа.
Если переменная массива v имеет тип A[], где A — тип ссылки, то v может содержать ссылку на экземпляр любого типа массива B[], при условии, что B можно присвоить A (§5.2). Это может привести к исключению во время выполнения на позднем присваивании; см. §10.5 для обсуждения.
Массив создаётся с помощью выражения создания массива (§15.10.1) или инициализатора массива (§10.6).
Выражение создания массива указывает тип элемента, количество уровней вложенных массивов и длину массива по крайней мере для одного из уровней вложенности. Длина массива доступна как final переменная экземпляра length.
Инициализатор массива создаёт массив и предоставляет начальные значения для всех его компонентов.
Компонент массива доступен через выражение доступа к массиву (§15.10.3), которое состоит из выражения, значение которого является ссылкой на массив, за которым следует выражение индексации, заключённое в [ и ], как в A[i].
Все массивы имеют 0 нумерацию. Массив с длиной n может быть индексирован целыми числами 0 до n-1.
Пример 10.4-1. Доступ к элементам массива
class Gauss {
public static void main(String[] args) {
int[] ia = new int[101];
for (int i = 0; i < ia.length; i++) ia[i] = i;
int sum = 0;
for (int e : ia) sum += e;
System.out.println(sum);
}
}
Эта программа выводит:
5050
Программа объявляет переменную ia, которая имеет тип массива int, то есть int[]. Переменная ia инициализируется ссылкой на вновь созданный объект массива, созданный выражением создания массива (§15.10.1). Выражение создания массива указывает, что массив должен иметь 101 компонентов. Длина массива доступна с помощью поля length, как показано. Программа заполняет массив целыми числами от 0 до 100, суммирует эти целые числа и выводит результат.
Массивы должны индексироваться int значениями; short, byte или char значения также могут использоваться в качестве значений индекса, поскольку они подвергаются числовому преобразованию с приведением к int значениям.
Попытка доступа к компоненту массива с long значением индекса приводит к ошибке компиляции.
Все обращения к массиву проверяются во время выполнения; попытка использования индекса, который меньше нуля или больше или равен длине массива, вызывает исключение ArrayIndexOutOfBoundsException (§15.10.4).
Для массива, тип которого A[], где A — тип ссылки, присваивание элементу массива проверяется во время выполнения, чтобы убедиться, что присваиваемое значение совместимо с типом элемента.
Если тип присваиваемого значения не совместим с типом элемента (§5.2), то выбрасывается ArrayStoreException.
Если тип элемента массива не был бы реифицируемым (§4.7), виртуальная машина Java не могла бы выполнить проверку присваивания, описанную в предыдущем абзаце. Вот почему выражение создания массива с нереифицируемым типом элементов запрещено (§15.10.1). Можно объявить переменную типа массива, тип элемента которого не реифицируем, но присваивание результата выражения создания массива переменной неизбежно вызовет предупреждение о невыверенном преобразовании (§5.1.9).
Пример 10.5-1. ArrayStoreException
class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
public static void main(String[] args) {
ColoredPoint[] cpa = new ColoredPoint[10];
Point[] pa = cpa;
System.out.println(pa[1] == null);
try {
pa[0] = new Point();
} catch (ArrayStoreException e) {
System.out.println(e);
}
}
}
Эта программа выводит:
true java.lang.ArrayStoreException: Point
Переменная pa имеет тип Point[], а переменная cpa имеет значение ссылки на объект типа ColoredPoint[]. Значение типа ColoredPoint может быть присвоено переменной типа Point; поэтому значение cpa может быть присвоено переменной pa.
Ссылка на этот массив pa, например, проверка, является ли pa[1] null, не приведет к ошибке во время выполнения. Это потому, что элемент массива типа ColoredPoint[] является
ColoredPoint, и каждый ColoredPoint может выступать в качестве Point, так как Point является суперклассом класса ColoredPoint.
С другой стороны, присваивание элементу массива pa может привести к ошибке во время выполнения. На этапе компиляции присваивание элементу массива pa проверяется, чтобы убедиться, что присваиваемое значение является объектом типа Point. Но так как pa содержит ссылку на массив ColoredPoint, присваивание будет допустимо только в том случае, если тип присваиваемого значения во время выполнения, более конкретно, является объектом типа ColoredPoint.
Виртуальная машина Java проверяет такую ситуацию во время выполнения, чтобы убедиться, что присваивание допустимо; в противном случае выбрасывается ArrayStoreException.
Инициализатор массива может быть указан в объявлении поля (§8.3, §9.3) или объявлении локальной переменной (§14.4) или в качестве части выражения создания массива (§15.10.1), чтобы создать массив и предоставить некоторые начальные значения.
Следующее производство из §8.3 показано здесь для удобства:
Инициализатор массива записывается как список выражений, разделенных запятыми, заключенных в фигурные скобки { и }.
Запятая после последнего выражения в инициализаторе массива может появиться и будет проигнорирована.
Каждый инициализатор переменной должен быть совместим с типом элемента массива (§5.2), иначе произойдет ошибка компиляции.
Если тип элемента массива, который инициализируется, не реифицируем (§4.7), возникает ошибка компиляции.
Длина создаваемого массива равна количеству инициализаторов переменных, заключенных в фигурные скобки инициализатора массива. Выделяется память для нового массива этой длины. Если памяти недостаточно, выполнение инициализатора массива завершается с ошибкой OutOfMemoryError. В противном случае создается одномерный массив указанной длины, и каждый элемент массива инициализируется своим значением по умолчанию (§4.12.5).
Затем инициализаторы переменных, заключенные в фигурные скобки инициализатора массива, выполняются слева направо в текстовом порядке их появления в исходном коде. n'ный инициализатор переменной задает значение n-1'го элемента массива. Если выполнение инициализатора переменной завершается аномально, тогда выполнение инициализатора массива завершается по той же причине. Если все выражения инициализаторов переменных завершаются нормально, инициализатор массива завершается нормально, со значением только что инициализированного массива.
Если тип элемента — тип массива, то инициализатор переменной, указывающий на элемент, может сам быть инициализатором массива; то есть, инициализаторы массивов могут быть вложенными. В этом случае выполнение вложенного инициализатора массива создает и инициализирует объект массива путем рекурсивного применения алгоритма выше и присваивает его элементу.
Пример 10.6-1. Инициализаторы массивов
class Test {
public static void main(String[] args) {
int[][] ia = { { 1, 2 }, null };
for (int[] ea : ia) {
for (int e: ea) {
System.out.println(e);
}
}
}
}
Эта программа выводит:
1 2
перед тем, как вызвать NullPointerException при попытке обратиться ко второму элементу массива ia, который является ссылкой null.
Членами типа массива являются все следующие:
-
Поле
publicfinallength, содержащее количество элементов массива.lengthможет быть положительным или нулевым. -
Метод
publicclone, который переопределяет метод с таким же именем в классеObjectи не выбрасывает проверочных исключений. Тип возвращаемого значения методаcloneтипа массива T[]— T[].Клон многомерного массива — поверхностный, то есть он создаёт только один новый массив. Подмассивы разделяются.
-
Все члены, унаследованные от класса
Object; единственный методObject, который не унаследован, — это его методclone.См. §9.6.4.4 для другой ситуации, когда разница между
publicи не-publicметодамиObjectтребует особого внимания.
Таким образом, массив имеет те же поля и методы, что и следующий класс:
class A<T> implements Cloneable, java.io.Serializable {
public final int length = X;
public T[] clone() {
try {
return (T[])super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e.getMessage());
}
}
}
Обратите внимание, что приведение к типу T[] в приведенном выше коде привело бы к предупреждению о невыверенном преобразовании (§5.1.9), если бы массивы действительно реализовывались таким образом.
Пример 10.7-1. Массивы могут клонироваться
class Test1 {
public static void main(String[] args) {
int[] ia1 = { 1, 2 };
int[] ia2 = ia1.clone();
System.out.print((ia1 == ia2) + " ");
ia1[1]++;
System.out.println(ia2[1]);
}
}
Эта программа выводит:
false 2
показывая, что элементы массивов, на которые ссылаются ia1 и ia2, являются разными переменными.
Пример 10.7-2. Разделяемые подмассивы после клонирования
Факт, что подмассивы разделяются при клонировании многомерного массива, показан в этой программе:
class Test2 {
public static void main(String[] args) throws Throwable {
int[][] ia = { { 1, 2 }, null };
int[][] ja = ia.clone();
System.out.print((ia == ja) + " ");
System.out.println(ia[0] == ja[0] && ia[1] == ja[1]);
}
}
Эта программа выводит:
false true
показывая, что массив int[], который ia[0], и массив int[], который ja[0], являются одним и тем же массивом.
Каждый массив имеет связанный с ним Class объект, общий для всех других массивов с одинаковым типом компонентов.
Хотя тип массива не является классом, Class объект каждого массива ведет себя так, как будто:
-
Прямым суперклассом каждого типа массива является
Object. -
Каждый тип массива реализует интерфейсы
Cloneableиjava.io.Serializable.
Пример 10.8-1. Class Объект массива
class Test1 {
public static void main(String[] args) {
int[] ia = new int[3];
System.out.println(ia.getClass());
System.out.println(ia.getClass().getSuperclass());
for (Class<?> c : ia.getClass().getInterfaces())
System.out.println("Superinterface: " + c);
}
}
Эта программа выводит:
class [I class java.lang.Object Superinterface: interface java.lang.Cloneable Superinterface: interface java.io.Serializable
где строка "[I" — это сигнатура типа во время выполнения для Class объекта "массив с типом компонентов int".
Пример 10.8-2. Объекты массивов Class являются общими
class Test2 {
public static void main(String[] args) {
int[] ia = new int[3];
int[] ib = new int[6];
System.out.println(ia == ib);
System.out.println(ia.getClass() == ib.getClass());
}
}
Эта программа выводит:
false true
Хотя ia и ib ссылаются на разные массивы, результат сравнения Class объектов демонстрирует, что все массивы, компоненты которых имеют тип int, являются экземплярами одного и того же типа массива (а именно int[]).
В языке программирования Java, в отличие от C, массив char не является String, и ни String, ни массив char не завершается '\u0000' (символом NUL).
Объект String является неизменяемым, то есть его содержимое никогда не меняется, в то время как массив char имеет изменяемые элементы.
Метод toCharArray в классе String возвращает массив символов, содержащий ту же последовательность символов, что и String. Класс StringBuffer реализует полезные методы для изменяемых массивов символов.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.