Глава 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].
Все массивы имеют нулевое начало. Массив длиной 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и не-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.