Глава 10. Массивы
Оглавление
В языке программирования Java, массивы являются объектами (§4.3.1), создаются динамически и могут быть присвоены переменным типа Object (§4.3.2). Все методы класса Object могут быть вызваны на массиве.
Объект массива содержит ряд переменных. Количество переменных может быть нулевым, в этом случае массив считается пустым. Переменные, содержащиеся в массиве, не имеют имен; вместо этого они ссылаются с помощью выражений доступа к массиву, использующих целочисленные индексы, неотрицательные целые значения. Эти переменные называются компонентами массива. Если массив имеет n компонентов, мы говорим, что n является длиной массива; к компонентам массива обращаются с помощью целых индексов от 0 до n - 1 включительно.
Все компоненты массива имеют один и тот же тип, называемый типом компонента массива. Если тип компонента массива — T, то тип самого массива записывается как T[].
Значение компонента массива типа float всегда является элементом множества значений float (§4.2.3); аналогично, значение компонента массива типа double всегда является элементом множества значений double. Не допускается, чтобы значение компонента массива типа float было элементом множества значений float-extended-exponent, которое не является также элементом множества значений float, ни для значения компонента массива типа double — быть элементом множества значений double-extended-exponent, которое не является также элементом множества значений double.
Тип компонента массива сам может быть типом массива. Компоненты такого массива могут содержать ссылки на подмассивы. Если, начиная с любого типа массива, мы рассматриваем его тип компонента, а затем (если это также тип массива) тип компонента этого типа и так далее, в конечном итоге мы должны достичь типа компонента, который не является типом массива; это называется элементарным типом исходного массива, а компоненты на этом уровне структуры данных называются элементами исходного массива.
Существуют ситуации, в которых элемент массива может быть массивом: если элементарный тип — Object или Cloneable или java.io.Serializable, то некоторые или все элементы могут быть массивами, потому что любой объект массива может быть присвоен любой переменной этих типов.
Типы массивов используются в объявлениях и в выражениях приведения типов (§15.16).
Тип массива записывается как имя элементарного типа, за которым следует некоторое количество пустых пар квадратных скобок []. Количество пар скобок указывает глубину вложенности массива.
Каждая пара скобок в типе массива может быть аннотирована аннотациями типов (§9.7.4). Аннотация применяется к паре скобок (или многоточию в объявлении параметра с переменным числом аргументов), которая следует за ней.
Элементарный тип массива может быть любым типом, будь то примитивный или ссылочный. В частности:
-
Разрешены массивы с типом интерфейса в качестве элементарного типа.
Элементом такого массива может быть нулевая ссылка или экземпляр любого типа, реализующего интерфейс.
-
Разрешены массивы с типом класса
abstractв качестве элементарного типа.Элементом такого массива может быть нулевая ссылка или экземпляр любого подкласса класса
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.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 значения также могут быть использованы в качестве значений индексов, поскольку они подвергаются числовому преобразованию с одноместными операторами (§5.6.1) и становятся 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класса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' (символом NULL).
Объект String является неизменяемым, то есть его содержимое никогда не меняется, в то время как массив char имеет изменяемые элементы.
Метод toCharArray в классе String возвращает массив символов, содержащий ту же последовательность символов, что и String. Класс StringBuffer реализует полезные методы для изменяемых массивов символов.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.