Массивы в Java

Массивы в Java — это структуры данных для хранения нескольких значений одного типа. Похожи на списки в Python, но есть принципиальное отличие, массивы имеют фиксированный размер, нет возможности изменить количество элементов массива после его создания. Кроме того, в массивы все элементы должны быть определенного типа.

Тип массива:

ТИП[]

Получается массив элементов типа ТИП:

int[] — массив чисел int
byte[] — массив чисел byte
String[] — массив строк
double[] — массив вещественных чисел
int[][] — массив массивов целых чисел

Поэтому, можно заводить переменные с типом массива так:

int[] a;

Способы создания массива:

  1. new ТИП[КОЛИЧЕСТВО]:
    new int[10]; // массив из 10 целых
    new String[3]; // массив из трех строк
       
    // создать переменную и назначить ей
    // массив:
    int[] a = new int[10];
    

    Массив заполняется нулями (для чисел), 0-символом для char, false для boolean. Если тип объектный (String…), то массив заполняется null (см. далее)

  2. new ТИП[]{перечисление элементов}:
    new int[]{10, 20, 30}
    new String[]{"abc", "xyz"}
       
    //создать переменную и назначить массив:
    int[] a = new int[]{10, 20, 30};
    
  3. Предыдущий способ можно сократить, если создание массива происходит при инициализации переменной. Тогда можно не писать new ТИП[]. Получится:
    int[] a = {10, 20, 30};
    //это сокращение int[] a = new int[]{10, 20, 30};
       
    //А вот так нельзя:
    processArray({10, 20, 30})
    //можно только:
    processArray(new int[]{10, 20, 30})
    

Для обращения к элементу массива используются квадратные скобки, с их помощью можно получить значение или записать его в массив. Индексы начинаются с 0:

int[] a = {10, 20, 30};
sout(a[0]); // получаю 10
sout(a[1]); // получаю 20
sout(a[2]); // получаю 30
sout(a[3]); // ошибка

a[2] = 31;
a[0] = 11;

Выход за границу массива — всегда ошибка ArrayIndexOutOfBoundsException.

Распечатать массив напрямую не получится: System.out.println(a); получите что-то типа I]@230BC. В нем зашифровано, что это массив целых (I = int) (и хэш код объекта).

Надо делать так:

System.out.println(Arrays.toString(a));

Т.е. нужна дополнительная функция Arrays.toString(), которая превращает массив в строку.

Перебор элементов массива:

Вариант как в python, цикл for в форме “for each”:

for (ПЕРЕМННАЯ : МАССИВ) {
    ТЕЛО ЦИКЛА
}

Это аналог for x in a: в Python. Но при этом переменную можно завести прямо внутри цикла:

int[] a = {10, 20, 30};
for (int x : a)
   sout(x); //распечатает 10, потом 20, 30
// x заведена в for, значит, после цикла
// этой переменной больше нет

Или, если при переборе значений массива нужны индексы, или если нужен перебор не по порядку:

(про длину массива ниже)

int[] a = {10, 20, 30};
for (int i = 0; i < a.length; i++)
   sout("%d. %d".formatted(i, a[i])); 

Получится

0. 10
1. 20
2. 30

Длина массива — это поле “.length” у объекта массива: a.length. В отличие от строк, после length не нужны скобки. (сравните с "abc".length()).

Многомерный массив

В Java, как и в Python, отдельных многомерных массивов не существует. Но можно делать массивы массивов. Посмотрим, как это выглядит:

int[][] a = new int[][]{
    {10, 20, 30},                // a[0]
    {40, 50, 60, 70, 80, 90},    // a[1]
    {100, 200}                   // a[2]
};

sout(Arrays.toString(a[0]));       // {10, 20, 30}
sout(Arrays.toString(a[0][1]));    // 20
sout(Arrays.toString(a[2]));       // {100, 200}
sout(Arrays.toString(a[2][0]));    // 100

Как устроен массив a. a[0] — это массив {10, 20, 30}.

Это было создание массива, когда сразу указываются значения внутри массива. Можно создать массив по размеру:

int[][] a = new int[5][6];

Будет создан массив из нулей, потому что, как и раньше, созданный массив заполняется нулями. Первый индекс изменяется от 0 до 4 (5-1), второй — от 0 до 5 (6 - 1). Поэтому a[0] — это массив из 6 элементов. Получается, что a — это 5 массивов из 6 элементов каждый.

Разрешается создать массив, указав размер только первого измерения:

int[][] a = new int[5][];

a[0] = new int[]{100, 200, 300};
a[1] = new int[]{11, 22, 33};
a[2] = new int[]{88, 88, 88, 88, 88, 88, 88};
a[3] = new int[]{}; // пустой массив
a[4] = new int[100]; //100 нулей

Это создает массив для пяти массивов. Т.е. в a[0], a[1], a[2], a[3], a[4] можно сохранить массивы чисел.

Обычно, в двумерных массивах считается, что первый индекс — это номер строки, второй — номер столбца.

Т.е. массив a устроен следующим образом:

 100 200 300 
  11  22  33
  88  88  88  88  88  88  88
  
   0   0   0   0   0   0   0   ...

Значение null

В Java есть особое значение null, которое содержится во всех объектных типах. Например, переменная любого объектного типа (не базового, т.е. String, int[]) может хранить null. Смысл — отсутствие значения, примерно как None в Python.

Cо значением null нельзя совершать действий, кроме как сравнивать (==) с другими значениями:

String s = null; //нет строки в переменной
int[] a = null; //нет массива (не пустой, а вообще нет)

sout(s.length()); // Ошибка
sout(a.length); // Ошибка
sout(s.equals("")); // Ошибка 
sout(s.startsWith("abc")); // Ошибка

if (s != null) // сравниваем с null
    sout(s.length()); // так можно, но в данном примере
                      // это не выполнится

Если какое-то значение может быть null, надо обязательно сначала его проверить на null и только потом использовать. Иначе возможна ошибка NullPointerException.

Пример:

String[] words = new String[10]; // 10 строк
words[0] = "cat"; 
words[1] = "dog";

for (String word : words)
   sout(word.length());

В результате увидим:

3
3
Exception:  NullPointerException

Почему? Массив words перед началом цикла был {"cat", "dog", null, null, null, null, null, null, null, null}. Поэтому, когда цикл дошел до третьего элемента, случился NullPointerException.

Чтение и запись файлов