Spiral group

_________________________________________________________________________________

 

< Раздел “Язык Java > < Раздел “Технологии Java > < Основная страница >

 

Массивы в Java. JDK 1.5.

 

Статья опубликована: 28.06.2006

Последнее обновление: 18.03.2007

 

 

Объявление массивов

 

Массив – это структура данных, в которой хранятся элементы одинакового типа. Массив в Java является объектом. Одномерный массив можно объявлять двумя способами, например:

 

int[] n;

 

или

 

int n[];

 

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

 

Доступ к элементам массива с помощью индекса

 

Доступ к элементам массива осуществляется с помощью целочисленного индекса. Индекс меняется от нуля, до максимально допустимого значения, на единицу меньшего длины массива. Значение индекса массива всегда имеет тип int. Индекс первого элемента массива начинается с 0. Поэтому, если у вас есть массив из семи элементов, то индекс может принимать значения от 0 до 6.

При обращении к элементу можно в качестве индекса также можно использовать переменную типа byte, short или char, поскольку эти типы автоматически расширяются до int. Попытка задействовать long приведет к ошибке компиляции.

 

Создание массивов, инициализаторы массивов и безымянные массивы

 

Предыдущий пример объявляет переменную n, но не инициализирует настоящий массив. В этом описании размер массива не указывается и сам массив не создается. Как и любой другой объект, массив должен быть создан оператором new. В языке Java предусмотрен вариант одновременного создания массива и его инициализация с помощью инициализатора, представляющего собой выражение, помещенное в пару фигурных скобок {}. Например:

 

int[] n = {2, 3, 9, 12, 15};

int[] f = {};   //эквивалентно new int[0]

 

Существует еще один способ создания массива, когда его значения заранее неизвестны. Для этого применяется оператор new, например:

 

int[] n = new int[5];

 

Данная команда создаст массив целых чисел из пяти элементов. Наиболее частая ошибка некоторых программистов состоит в том, что сразу после создания массива идет попытка работы с его элементами. В данном случае построен только сам массив, а сами элементы еще не созданы. Поэтому после создания подразумевается последующая инициализация элементов.

При создании размер массива может быть задан равным 0. Это будет вполне полноценный массив, содержащий 0 элементов. Его длина будет равна 0. Однако такой массив не эквивалентен объекту null. Такая конструкция в ряде случаев оказывается весьма полезной. Например:

 

int[] n = new int[0];

 

После создания массив инициализируется значением по умолчанию для типа его элементов. Инициализация значений по умолчанию гарантируется спецификацией языка. Ниже приведены значения по умолчанию для примитивных типов и типа Object.

 

Тип

Значение

byte

0

short

0

int

0

long

0

float

0.0

double

0.0

boolean

false

char

‘ ’

Object

null

 

После создания можно присваивать элемента массива новые значения. Например:

 

int[] n = new int[5];

for(int i=0; i<5; i++)

{

 n[i]=i;

}

 

Данный код заполнит массив числами от 0 до 4

 

Кроме перечисленных методов инициализации можно инициализировать и безымянный массив. Например, вторая строка нижеприведенного кода является инициализацией безымянного массива. 

 

int[] n = {2, 3, 9, 12, 15};

n = new int{20, 30, 40, 50, 60};

 

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

 

int[] n = {2, 3, 9, 12, 15};

int[] new_array = {20, 30, 40, 50, 60};

n = new_array;

 

Следует иметь ввиду, что в подобной ситуации количество элементов новых данных не обязательно должно совпадать с количеством элементов, при начальной инициализации. Например, в следующем примере массив n после выполнения всех операций будет содержать только два элемента, хотя в самом начале был инициализирован пятью элементами.

 

int[] n = {2, 3, 9, 12, 15};

int[] new_array = {20, 30};

n = new_array;

 

Инициализация массива, который выступает в качестве входного параметра метода

 

Массив может выступать в качестве входного параметра для метода или конструктора класса. Хорошо, если перед вызовом метода элементы массива инициализированы. Однако если они не инициализированы, то следует использовать специальную форму инициализации. Например:

 

Файл ArrayParameterTransfer.java

class ArrayParameterTransfer

{

 static void displayElements(StringBuffer[] sb)

 {

  for(int i=0; i<sb.length; i++)

  {

   System.out.println("sb["+i+"]="+sb[i]);

  }

 }

 

 public static void main (String args [])

 {

  displayElements(new StringBuffer[]{new StringBuffer("First"),

                                  new StringBuffer("Second")});

 }

}

 

Выражение

 

new StringBuffer[]{new StringBuffer("First"), new StringBuffer("Second")}

 

во входном параметре метода displayElements является смесью выражения, создающего массивы с помощью оператора new, и инициализатора.

 

Контроль выхода за границы массива

 

Контроль выхода за границы массива осуществляет интерпретатор. Не следует упускать из виду, что индекс первого элемента массива начинается с 0. В случае выхода индекса за пределы массива (например, попытка обратиться к элементу с индексом 10 в массиве с элементами от 0 до 9) будет инициировано исключение java.lang.ArrayIndexOutOfBoundsException и программа прекратит свою работу. Например:

 

int[] n = {1,5};

System.out.println(n[2]);        //Ошибка!

 

Не смотря на упрощенный приведенный пример, наиболее часто данная ошибка возникает в циклах при обходе массива. Еще труднее отследить поведение индекса, если существует каскад вложенных циклов из которых возможен принудительный выход. Поэтому следует внимательно относиться к вычислению индекса при написании кода программы. Существуют специальные тестовые программы для воспроизведения всех возможных ситуаций обхода массива и вычисления индекса. Однако они не во всех случаях способны выявить выход индекса за допустимые пределы.

Перехват исключения java.lang.ArrayIndexOutOfBoundsException не рекомендуется и используется только в редких случаях.

 

Длина массива

 

Если массив был инициализирован с помощью инициализатора, то длина будет подсчитана интерпретатором автоматически. Если при создании размер массива задан равным 0, то его длина будет равна 0.

Количество элементов массива можно получить через поле .length после имени массива. Например:

 

int[] n;

System.out.println(“The length of array=”+n.length);

 

 

Поле length имеет тип int, а теоретическая максимально возможная длина массива равняется 231-1, то есть немногим больше 2 млрд. Поле .length является final, поэтому задать новый размер через него нельзя. Т.е. после создания массива изменить его размер невозожно.

Существуют ситуации, в которых начинающий программировать на Java может быть сбит с толку: одновременно в коде используются метод length() и поле length. В следующей программе приводится данная ситуация.

 

Файл SBLength.java

class SBLength

{

 public static void main (String args [])

 {

  StringBuffer sb[] = {new StringBuffer("First"),

                       new StringBuffer("Second"),

                       new StringBuffer("Third")};

  for(int i=0; i<sb.length; i++)

  {

   System.out.println("sb["+i+"]="+sb[i]);

  }

  System.out.println("Elements quantity of array=" + sb.length);

  System.out.println("Second element=" + sb[1]);

  System.out.println("Symbols quantity in second element=" + sb[1].length());

 }

}

 

Результат вывода программы:

 

sb[0]=First

sb[1]=Second

sb[2]=Third

Elements quantity of array=3

Second element=Second

Symbols quantity in second element=6

Программа выводит длину массива, первый элемент и количество символов в первом элементе. Здесь поле length и метод length() это два различных члена различных классов. length является полем массива, а метод length() принадлежит классу StringBuffer.

Изменение длины массива, копирование и клонирование массива

 

Альтернативным вариантами смены длины являются дублирование массива с новой длиной стандартным копированием в цикле или дублирование с помощью метода arraycopy. Последний вариант является наиболее быстрым и гибким согласно документации. Он позволяет управлять количеством копируемых элементов, а также внутренним позиционированием в массиве источнике и массиве приемнике. Сигнатура этого метода выглядит следующим образом:

 

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

 

Копирование происходит из массива src, начиная с позиции srcPos, в массив dest, начиная с позиции destPos. Всего копируется length элементов. Обратите внимание, что src и dest имеют тип Object. Это сделано для того, чтобы этот метод мог обрабатывать любые массивы.

 

Например:

 
int[] n = {2, 3, 9, 12, 15};
int[] new_n = new int[n.length+5];
for(int i=0; i<n.length; i++)
{
 new_n[i] = n[i];
} 
 
Object[] array = new Object[5];
Object[] newarray = new Object[10];
System.arraycopy(array,0,newarray,0,array.length);
 
Если src или dest не является массивом, будет инициировано исключение java.lang.ArrayStoreException.
 
Существует еще один стандартный способ копирования массива, копию которого, тем не менее, нельзя назвать самостоятельной.
Например:
 

Файл CopyArray.java

class CopyArray
{
 public static void main (String args [])
 {
  int[] n1 = {1,2,3,4};
  int[] n2 = n1;
  n2[0]=20;
  n1[1]=30;
  for(int i=0; i<n1.length; i++)
  {
   System.out.println("n1["+i+"]="+n1[i]);
  }
 
  for(int i=0; i<n2.length; i++)
  {
   System.out.println("n2["+i+"]="+n2[i]);
  }
 }
}
 

Вывод программы будет следующим:

 

n1[0]=20

n1[1]=30

n1[2]=3

n1[3]=4

n2[0]=20

n2[1]=30

n2[2]=3

n2[3]=4

 

Вывод свидетельствует о том, что при присваивании одной переменной массива другой происходит копирование. Однако при этом обе переменные n1 и n2 будут ссылаться на один и тот же массив. Поэтому изменение элемента любой из переменных массива фактически воздействует на один и тот же массив.

Если есть необходимость получить точную, но самостоятельную копию массива, то лучше воспользоваться клонированием с помощью метода clone(), который унаследован от класса Оbject. сlone() является единственным из унаследованных методов класса Object, который переопределяется для массива и не генерирует контролируемых исключительных ситуаций. Это продемонстрировано в следующей программе:

 

Файл CloneArrayDemo.java

class CloneArrayDemo

{

 public static void main (String args [])

 {

  int n1[] = {1,2,3,4};

  int n2[] = (int[])n1.clone();

  n2[0] = 100;

  System.out.println("n1 == n2 " + (n1 == n2));

  for(int i=0; i<n1.length; i++) System.out.println("n1["+i+"]="+n1[i]);

  for(int i=0; i<n2.length; i++) System.out.println("n2["+i+"]="+n2[i]);

 }

}

 

Вывод программы:

 

n1 == n2 false

n1[0]=1

n1[1]=2

n1[2]=3

n1[3]=4

n2[0]=100

n2[1]=2

n2[2]=3

n2[3]=4

 

Вывод показывает, что переменные n1 и n2 ссылаются на разные массивы.

Клонирование многомерных массивов несколько отличается от клонирования одномерных массивов и описано ниже.

Очень часто приходится сталкиваться с ситуациями, где необходим динамический массив. Это ситуации, в которых размер массива заранее неизвестен или в ходе выполнения программы необходимо часто изменять размер массива. Для этих целей лучше использовать классы коллекций, которые имплементируют класс java.util.Collection и относятся к подсистеме Java Collections Framework. Одним из наиболее употребляемых является класс java.util.ArrayList.

 

Двумерный массив

 

По сути, двумерный массив является массивом массивов. Существует два стандартных способа создания двумерного массива:

 

int[][] array = new int[8][5]

 

В данном случае размер явно указан при создании, а массив представляет собой прямоугольную матрицу. Создает двумерный массив целых чисел  из 8 строк по 5 элементов каждая. Второй стандартный способ заключается в создании списка инициализации, например:

 

int[][] array = new int[][]

{

 {1,1,1},

 {2,2,2},

 {1,2,3}

};

 

Существует и вариант создания массива произвольной формы, при котором явно не указывается размер массива. и при этом, массивы могут иметь различную длину! Например:

 

int[][] n = new int[3][];

n[0] = new int[2];

n[1] = new int[4];

n[2] = new int[3];

 

Обратите внимание, в строке 1 при инициализации массива не указана размерность по второму индексу! В результате создастся массив, содержащий три массива, первый из которых имеет длину в 2 элемента, второй в четыре элемента, а третий в 3 элемента.

Процесс создания несколько усложняется при использовании массивов объектов. Создадим аналогичный массив объектов класса MyClass.

Вариант 1. (явное создание)

MyClass[][] array = new MyClass[3][3];

for(int k = 0; k < 3; k++)

{

 for(int j = 0; j < 3; j++)

 {

  array[k][j] = new MyClass();

 }

}

Вариант 2. (использование списка инициализации)

MyClass[][] array = new MyClass[3][3]

{

 { new MyClass(), new MyClass(), new MyClass() },

 { new MyClass(), new MyClass(), new MyClass() },

 { new MyClass(), new MyClass(), new MyClass() }

};

 

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

 

n-мерный массив является одномерным массивом, элементы которого являются ссылками на массивы размерности n-1. Например, двумерный массив, представляет собой массив ссылок на одномерные массивы, а трехмерный массив представляет собой список ссылок, но уже на двумерные массивы.

n-мерные массивы можно создавать одним выражением, с использованием оператора new и указанием размеров по всем измерениям. А можно и последовательно – создать массив, указав размерность по одному или нескольким индексам, а оставшиеся неинициализированными ссылки проинициализировать вручную, создавая массивы нужной мерности, но произвольного размера. Однако для массивов большой мерности это достаточно рутиная задача.

Количество пар скобок при объявлении массива зависит от его размерности. Например, четырехмерный массив, может быть объявлен как:

 

int[][][][] n;

 

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

 

int i[][] = {{1,2}, null, {3}, {}};

 

Для четвертого элемента создана ссылка, но не инициализированы значения элементов, поэтому к элементам массива, на который указывает ссылка четвертого элемента массива i обратиться невозможно. Ссылка второго элемента установлена в null.

Ниже приведен пример обращения к элементам многомерного массива.

 

Файл Array3d.java

class Array3d

{

 public static void main (String args [])

 {

  int[][] f = { {1,2}, null, {3}, {} };

  int[][][] n = { {{1,2},{5,6},{2,8,9,4,5}}, {{3}}, {{}}, null };

  //Вывод элементов двумерного массива

  System.out.println("f.length="+f.length);

  for(int i=0; i<f.length; i++)

  {

   if(f[i] == null) System.out.println("f["+i+"]=null");

   else

   {

    for(int j=0; j<f[i].length; j++)

    {

     System.out.println("f["+i+"]["+j+"]="+f[i][j]);

    }

   }

  }

  //Вывод элементов трехмерного массива

  System.out.println("n.length="+n.length);

  for(int i=0; i<n.length; i++)

  {

   if(n[i] == null)

   {

    continue;

   }

   for(int j=0; j<n[i].length; j++)

   {

    for(int k=0; k<n[i][j].length; k++)

    {

     System.out.println("n["+i+"]["+j+"]["+k+"]="+n[i][j][k]);

    }

   }

  }

 }

}

 

Двумерный и трехмерный массив содержит элементы null по первому измерению. Поэтому при обходе элементов массива по первому измерению требуется проверка элемента на значение null. При обходе элементов двухмерного массива выводится ссылка, если ее значение равно null, а если не равно null – выводятся элементы массива по второму измерению. В трехмерном массиве, в случае если элемент по первому измерению равен null, то цикл прерывается и продолжается со следующего элемента благодаря оператору continue. Если элемент равен null, то обратиться к его элементам по остальным измерениям нельзя, потому что они не созданы. И если не осуществлять проверку, то будет выброшено исключение java.lang.NullPointerException.

Кроме этого, в данном примере в двухмерном и трехмерном массивах созданы элементы по первому измерению, но не проинициализированы их элементы по остальным измерениям. В двухмерном массиве – это четвертый элемент, а в трехмерном – третий элемент. По этим элементам в циклах будет производиться обход, но поскольку они не имеют элементов по остальным измерениям, то и вывод для их элементов не будет производиться.

Что касается клонирования многомерных массивов, то метод сlone многомерного массива создает только одномерный новый массив. Подмассивы оказываются общими, как показано в примере программы:

 

Файл CloneDoubleArrayDemo.java

class CloneDoubleArrayDemo

{

 public static void main(String[] args) throws Throwable

 {

  int n1[][] = { {1,2}, {3,4}, {5,6} };

  int n2[][] = (int[][])n1.clone();

  System.out.println("n1 == n2 " + (n1 == n2));

  System.out.println("n1[0] == n2[0] " + (n1[0] == n2[0]));

  n2[2] = new int[]{10,9};

  for(int i=0; i<n1.length; i++)

  {

   System.out.println("n1["+i+"]="+n1[i]);

   for(int j=0; j<n1[i].length; j++)

   {

    System.out.println("n1["+i+"]["+j+"]="+n1[i][j]);

   }

  }

  System.out.println();

  for(int i=0; i<n2.length; i++)

  {

   System.out.println("n2["+i+"]="+n2[i]);

   for(int j=0; j<n2[i].length; j++)

   {

    System.out.println("n2["+i+"]["+j+"]="+n2[i][j]);

   }

  }

 }

}

 

Вывод программы:

 

n1 == n2 false

n1[0] == n2[0] true

n1[0]=[I@45a877

n1[0][0]=1

n1[0][1]=2

n1[1]=[I@1372a1a

n1[1][0]=3

n1[1][1]=4

n1[2]=[I@ad3ba4

n1[2][0]=5

n1[2][1]=6

 

n2[0]=[I@45a877

n2[0][0]=1

n2[0][1]=2

n2[1]=[I@1372a1a

n2[1][0]=3

n2[1][1]=4

n2[2]=[I@126b249

n2[2][0]=10

n2[2][1]=9

 

Записи типа [I@126b249 являются сигнатурами типа время выполнения и показывают, что массивы типа int[], которыми являются n1[i] и n2[i] – это одни и те же массивы.

Отдельно стоит упомянуть о копировании многомерных массивов с помощью System.arraycopy. Массивы будут копироваться только по первому индексу. В новый массив будут перенесены ссылки на массивы размерности n-1 из исходного массива. Такой подход весьма облегчает копирование массивов при добавлении новой строки – память нужно выделять только под массив по первому индексу. Далее ссылки на все элементы будут скопированы в новый массив, а последняя ссылка, которая должна указывать на новый элемент, будет проинициализирована вручную.

 

Тип массива

 

Массивы в Java являются объектами  и их тип напрямую наследуется от класса Object. Соответственно все методы класса Object могут быть вызваны массивом. Но элементы массива могут быть любых типов. Для массивов важно понимать различие: сам массив является объектом, а тип элементов (базовый тип) может быть любым. Поэтому, когда объявляется любой массив, имеется ввиду, что массив является объектом, но основан на определенном типе элементов.

Тип элементов, на котором основан массив записывается c некоторым числом пустых пар квадратных скобок []. Количество пар скобок зависит от размерности массива. Например четырехмерный массив, основанный на элементах типа int может быть объявлен как:

 

int[][][][] n;

Скобки [] могут появляться как часть типа в начале описаний, или как часть описания для конкретной переменной, или в обоих случаях сразу, как в этом примере:

byte[] b1, b2, matrix[];

 

Это объявление эквивалентно следующему:

 

byte b1[], b2[], matrix[][];

 

Базовый тип элементов массива может любым типом: примитивным или cсылочным. В частности, ссылочным типом могут быть:

— интерфейсы; в таком случае элементы массива могут иметь значение null или ссылаться на объекты любого класса, реализующего этот интерфейс;

— абстрактные классы; в этом случае элементы массива могут иметь значение null или ссылаться на объекты любого неабстрактного класса-наследника;

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

 

int[] n = new int[2];

n[0] = 1;

n[1] = 2;

Object o1 = n[0];

Object o2 = n;

//n[0] = new Object();        //Приведет к ошибке несовместимости типов ожидается int

//n = new Object();            //Приведет к ошибке несовместимости типов ожидается int[]

 

В случае многомерного массива:

 

int[][] n2 = new int[2][1];

n2[0][0] = 1;

n2[1][0] = 2;

Object o2 = n2;

Object o1 = n2[0];

Object o3 = n2[1][0];

//n2 = new Object();        //Ошибка! несовместимость типов, ожидается int

//n2[0] = new Object();     //Ошибка! несовместимость типов, ожидается int[]

//n2[1][0] = new Object();  //Ошибка! несовместимость типов, ожидается int[][]

 

Вышеприведенные примеры свидетельствуют о том, что массив является объектным типом данных, и его значения могут быть приведены явно к типу Object или присвоены переменной типа Object. Например,

 

Object o = new int[4];

Object o = (Object) new Integer[2];

 

Но не наоборот. Обратное присвоение приведет к ошибке:

 

int[] n = new int[2];

Object o = n;

n = o;               //Ошибка! несовместимость типов, ожидается int[]

 

Однако, если выстроить массив, основанный на типе Object, то присвоение элементу массива нового экземпляра класса Object будет корректным. Более того, это дает интересную возможность для хранить в качестве элемента ссылку на самого себя:

 

Object array[] = new Object[3];

array[0] = new Object();

array[1] = null;

array[2] = array;   //Элемент ссылается на весь массив!

 

Члены массива

 

Члены массива следующие:

 

public final length – поле (длина), которое содержит число компонентов массива (length может принимать только неотрицательные значения);

public clone - метод, который переопределяет метод clone() класса Оbject и не генерирует контролируемых исключительных ситуаций;

— все остальные члены, унаследованные от класса Object (это методы equals, finalize, getClass, hashCode, notify, notifyAll и три перегруженных метода wait).

Массив имеет те же самые методы, что и следующий класс, приведенный в качестве примера:

 

class A implements Cloneable

{

 public final int length = X;

 public Object clone()

 {

  try

  {

  return super.clone();

  }

  catch (CloneNotSupportedException e)

  {

  throw new InternalError(e.getMessage());

  }

 }

}

 

Каждый массив реализуется интерфейсом Cloneable.

 

 

null в массивах

 

Если одномерный массив создан на основе примитивного типа, то вполне очевидно, что его элементам не может быть присвоено значение null. И вполне очевидно, что элементам одномерного массива, созданного на основе элементов ссылочного типа может быть присвоено значение null. Например:

 

Object[] o = {new Object(), null};

System.out.println(o[1]);

int[] n = { 3, null};            //Ошибка компиляции, ожидается тип int!

 

В то же время, поскольку сам массив является объектом, то присваивать ему значение null можно и даже часто необходимо в ситуациях начальной инициализации членов класса. Например:

 

Object[] o = null;

System.out.println(o);

int[] n = null;

System.out.println(n);

 

С многомерными массивами дело обстоит несколько иначе. Поскольку сам массив является объектом и многомерный массив является массивом массивов, то попытка обратиться к элементу массива по N измерению, в случае если он null вполне возможно. При попытке же обратиться к элементам по всем остальным  измерениям, для элемента массива, который по N измерению равен null приведет к выбросу исключения java.lang.NullPointerException. Этот факт также очевиден: если элемент по N измерению принимает значение null, то элементов для него по остальным измерениям не создано и не существует. Например:

 

Object[][] o = { {new Object(),new Object()}, null};

int[][] n = { {1,5}, null};

System.out.println(o[1]);       

System.out.println(n[1]);       

System.out.println(o[1][1]);           //Ошибка! попытка обратиться к элементу, который не существует.

System.out.println(n[1][1]);           //Ошибка! попытка обратиться к элементу, который не существует.

 

Исключение сохранения массива java.lang.ArrayStoreException

 

Данное исключение возможно при несовместимости типов в следующей ситуации:

 

Файл Test.java

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

 

Здесь переменная pa имеет тип Point[], и переменная cpa имеет его ссылочное значение для объекта типа ColoredPoint[]. Point’у может присваиваться ColoredPoint,  поэтому  pa может быть присвоено значение cpa.

С другой стороны, присваивание массиву pa может заканчиваться ошибкой во время выполнения. Во время компиляции, присваивание элементу pa проверяется, чтобы удостовериться в том, что присвоенное значение - Point. Но когда pa содержит ссылку на массив типа ColoredPoint присваивание возможно, только если тип присвоенного значения во время выполнения – тип ColoredPoint. Во время выполнения Java контролирует такую ситуацию, чтобы гарантировать что присваивание допустимо. Если это не так, то генерируется ArrayStoreException .

 

Заполнение массива с базовым типом double или float

 

В случае вычислений с элементами массива, базовый тип которых double или float, элементы могут выводиться с различным количеством знаков после запятой в зависимости от результата вычислений.

Следующая программа демонстрирует случаи возникновения вывода чисел с высокой точностью при работе с массивами типа double. Первые два цикла демонстрируют поведение, при котором элементы выводятся с фиксированным количеством знаков после запятой.  Вторые два цикла демонстрируют поведение, при котором элемент может выводиться с большим количеством знаков после запятой.

 

Файл DoubleArray.java

public class DoubleArray

{

 public static void main(String[] args)

 {

  double m = 0;

  double array[][] = new double[2][3];

 

  System.out.println("------- Without precision ---------");

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    array[i][j] = i+j;

   }

  }

 

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    System.out.println("array["+i+"]["+j+"]="+array[i][j]);

   }

  }

 

  System.out.println("------- With precision ---------");

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    array[i][j] = m + 0.1;

    m += 0.1;

   }

  }

 

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    System.out.println("array["+i+"]["+j+"]="+array[i][j]);

   }

  }

 }

}

 

Данная ситуация возникает потому, что Java достаточно точно производит вычисления. Используя расчеты с float и double возникает дискомфорт при выводе элементов с различным числом знаков после запятой. Для решения этой проблемы можно ввести точность, с которой нужно получить результат. Это стало возможным с введением в Java 2 5.0 аргументов с переменной длиной, и  зависимой от них функциональной возможностью форматирования вывода, которую долго ждали программисты. Базовой частью для создания форматированного вывода в языке Java служит класс Formatter, включенный в пакет java.util. Он обеспечивает вывод в любом из понравившихся форматов. Кроме этого в Java 2 5.0 добавлен метод printf() для классов PrintStream и PrintWriter. Метод printf() автоматически использует класс  Formatter. В таком случае программа изменит свой вид:

 

Файл DoubleArray.java

import java.util.Formatter;

import java.util.Locale;

 

public class DoubleArray

{

 public static void main(String[] args)

 {

  Formatter fmt;

 

  double m = 0;

  double array[][] = new double[2][3];

 

  System.out.println("------- Without precision ---------");

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    array[i][j] = i+j;

   }

  }

 

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    System.out.println("array["+i+"]["+j+"]="+array[i][j]);

   }

  }

 

  System.out.println("------- With precision ---------");

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    array[i][j] = 10000/(m + 0.1);

    m += 0.1;

   }

  }

 

  for(int i=0; i<2; i++)

  {

   for(int j=0; j<3; j++)

   {

    fmt = new Formatter();

    fmt.format(Locale.GERMANY,"%.1f",array[i][j]);

    System.out.println("array["+i+"]["+j+"]="+fmt);

   }

  }

 }

}

 

Если при выводе нужно в качестве разделителя целой  дробной части использовать запятую вместо точки, то можно воспользоваться локалью в перегруженном методе format класса Formatter. Следует обратить внимание на строку fmt.format(Locale.GERMANY, "%.1f",array[i][j]);, в которой в методе format используется локаль для представления чисел. Если нужна запятая, то можно воспользоваться Германским стандартом, который есть в списке констант класса Locale. Принятые стандарты в Герании правила употребления точек и запятых в числах в точности противоположны американским: запятая используется в качестве десятичного разделителя, а точка в качестве разделителя тысяч. Вывод программы будет следующим:

 

------- Without precision ---------

array[0][0]=0.0

array[0][1]=1.0

array[0][2]=2.0

array[1][0]=1.0

array[1][1]=2.0

array[1][2]=3.0

------- With precision ---------

array[0][0]=100000,0

array[0][1]=50000,0

array[0][2]=33333,3

array[1][0]=25000,0

array[1][1]=20000,0

array[1][2]=16666,7

 

В случае создания массивов на основе типов double и float с использованием явной инициализации не следует забывать указывать суффиксы d и f или D и F после самого числа, которые указывают на типовую принадлежность. Кроме того, в промежуточных вычислениях, числа с плавающей точкой не имеющие суффикса f или F рассматриваются как числа типа double, для которых не всегда обязателен суффикс d или D.

 

Файл DoubleArrayInit.java

class DoubleArrayInit

{

 public static void main (String args [])

 {

  double[] db = {1.25d, 2.2d, 3.2d};

  for (int i=0; i<db.length; i++) System.out.println(db[i]);

 }

}

 

Файл FloatArrayInit.java

class FloatArrayInit

{

 public static void main (String args [])

 {

  float[] ft = {1.25f, 2.2f, 3.2f};

  for (int i=0; i<ft.length; i++) System.out.println(ft[i]);

 }

}

 

Объекты Class для массивов

 

Каждый массив имеет связанный объект Class. Суперкласс типа-массива рассматривается как Object, что видно из следующего примера:

 

Файл Test.java

class Test
{
 public static void main(String[] args)
 {
  int[] ia = new int[3];
  System.out.println(ia.getClass());
  System.out.println(ia.getClass().getSuperclass());
 }
}

который выдает:

class [I
class java.lang.Object

где строка "[I" является сигнатурой типа время выполнения для объекта класса "массив с компонентами типа int".

Частичный обход элементов массива средствами цикла for

 

Иногда может возникнуть необходимость доступа не ко всем элементам массива, а в определенной очередности. Например, к каждому второму или каждому пятому элементу. Для этих целей можно управлять индексной переменной прямо средствами цикла for. Например:

 

class OrdinalIndex

{

 public static void main (String args [])

 {

  int[] n = {2,4,6,8,10,12,14};

  for(int i=0; i<n.length; i=i+2)

  {

   System.out.println("n["+i+"]="+n[i]);

  }

 }

}

 

Улучшенный цикл for для обхода элементов массива в JDK 1.5

 

Улучшенный цикл for в стиле for-each, который введен в Java 2 5.0, существенно упрощает прохождение массива в цикле. Он устраняет необходимость устанавливать счетчик цикла, задавая начальное и конечное значения, и вручную индексировать массив. Вместо этого он автоматически проходит весь массив, получая поочередно каждый элемент, начиная с первого и заканчивая последним. Кроме упрощения синтаксиса  устраняется возможность ошибки выхода за границы диапазона массива (boundary errors). Тем не менее, поскольку цикл for в стиле for-each может обрабатывать массив только последовательно от начала к концу, то его область применения ограничена по сравнению с классической формой цикла for. В следующем примере приведены две формы цикла:
 
String days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
 
for(String day: days)
{
 System.out.println("day: " + day);
}
 
for(int i=0; i < days.length; i++)
{
 System.out.println("day: " + days[i]);
}
 
Улучшенный цикл for может применяться и для обработки многомерных массивов. Например:
 

Файл ForEachDemo.java

class ForEachDemo
{
 public static void main (String args [])
 {
  int sum = 0;
  int[][] nums = new int[3][4];
 
  for(int i = 0; i<3; i++)
  {
   for(int j=0; j < 4; j++)
   {
    nums[i][j] = (i+1)*(j+1);
   }
  }
 
  for(int x[] : nums)
  {
   for(int y : x)
   {
    System.out.println(y);
    sum += y;
   }
  }
  System.out.println("Summation: "+sum);
 }
}
 
Вывод программы:
 
1
2
3
4
2
4
6
8
3
6
9
12
Summation: 60
 
При использовании цикла for в стиле for-each для обработки массива с N измерениями получаемые объекты должны быть массивами с N-1 измерениями. Переменная цикла x определена как ссылка на одномерный массив целых чисел. Подобное объявление необходимо так как в каждом проходе цикла for извлекается следующий массив из двухмерного массива nums, начиная с массива заданного как nums[0]. Внутренний цикл for затем просматривает каждый из этих массивов, отображая значения каждого элемента.
 

Ограничения для настраиваемого массива в JDK 1.5

 

Механизм идентификации типа во время выполнения является одним из мощных базовых принципов языка Java, который реализует  полиморфизм. Однако такой механизм не страхует разработчика от несовместимого приведения типов в ряде случаев. Самый частый случай – манипулирование группой объектов, различные типы которых заранее неизвестны и определяются во время выполнения.

Поскольку ошибки, связанные с несовместимостью типов  могут проявиться только на этапе выполнения, то это затрудняет их поиск и ликвидацию. Введение настраиваемых типов в Java 2 5.0 частично отодвигает возникновение подобных ошибок с этапа выполнения на этап компиляции и обеспечивает недостающую типовую безопасность. Отпадает необходимость в явном приведении типов при переходе от типа Object к конкретному типу. Следует иметь ввиду, что средства настройки типов работают только с объектами и не распространяются на примитивные типы данных, которые лежат вне дерева наследования классов. Благодаря настраиваемым типам все приведения выполняются автоматически и скрыто. Это позволяет обезопасить от несоответствия типов и гораздо чаще повторно использовать код.

С введением настраиваемых типов появилась и некоторая специфика в работе с массивами, у которых базовый тип (тип элементов по конечному измерению) задан с помощью настраиваемого типа. Существует два ограничения, которые вытекают из ограничений по настраиваемым типам:

— нельзя создавать экземпляр массива, у которого базовый тип задан с помощью настраиваемого типа;

— нельзя создать массив из ссылок на объекты конкретной версии настраиваемого типа.

 

Файл GenArrays.java

class Gen<T extends Number>

{

 T ob;

 T vals[]; //ok

 Gen(T o, T[] nums)

 {

  ob = o;

  //Этот оператор недопустим.

  //vals = new T[10]; //Не может создать массив из объектов типа Т

 

  //А этот оператор верен.

  //можно присвоить ссылку на существующий массив

  vals = nums;

  //Вывести на экран элементы массива vals

  for(T i:vals) System.out.println(i);

 }

}

 

 

class GenArrays

{

 public static void main(String args[])

 {

  Integer n[] = {1,2,3,4,5};

  Double db[] = {5d,4d,3d,2d,1d};

  Gen<Integer> iOb1 = new Gen<Integer>(50,n);

  Gen<Double> iOb2 = new Gen<Double>(10d,db);

 

  //Не может создать массив из элементов конкретной версии

  //настраиваемого типа.

  //Gen<Integer> gens[] = new Gen<Integer>[10];  //Неверно!

 

  //Правильный вариант.

  Gen<?> gens[] = new Gen<?>[10];  //OK

 }

}

 

Вывод программы:

 

1

2

3

4

5

5.0

4.0

3.0

2.0

1.0

 

Данная программа показывает как можно повторно использовать код класса Gen для разных оболочек примитивных типов, которые расширяют тип Number. Кроме того отражены способы создания массивов на основе настраиваемых типов. При вызове конструктора следует обратить внимание на то, что для первого параметра осуществляется автоматическое приведение типов от примитива до его соответствующего класса-оболочки, которое доступно начиная с Java 2 5.0.

Для массивов с настраиваемыми типами можно объявить ссылку на массив типа T, такую как в следующей строке:

 

T vals[]; //OK

 

Но нельзя создать массив и элементов типа T, подобно попытке, приведенной в следующей помеченной как комментарий строке:

 

//vals = new T[10];  //не может создать массив из объектов типа Т

 

Вы не можете создавать массив из элементов типа T, потому что параметр T не существует во время выполнения, и у компилятора нет способа узнать, массив из элементов какого типа формировать в действительности.

Тем не менее можно передать ссылку на совместимый по типу массив в конструктор Gen() при создании объекта и присвоить эту ссылку переменной val, как показано в следующей строке:

 

vals = nums //можно присвоить ссылку существующему массиву

 

Приведенная строка выполнится, потому что у массива, переданного в класс Gen, известен тип, который в момент создания объекта будет таким же как параметр типа T.

Внутри метода main() Вы не можете объявить массив ссылок на конкретную версию настраиваемого типа. Следующая строка:

 

//Gen<Integer> gens[] = new Gen<Integer>[10];  //Неверно!

 

не будет компилироваться. Массивы из элементов конкретной версиии настраиваемого типа просто не разрешены, поскольку могут привести к потере типовой безопасности.

Однако вы можете создать массив из ссылок на настраиваемый тип, если используете метасимвол, как показано в следующей строке:

 

Gen<?> gens[] = new Gen<?>[10]; //OK

 

Такой подход предпочтительней, чем использование массива из элементов несформированного (raw) типа, так как, по крайней мере, какой-то контроль типов будет выполнен.

Для полного понимания сути данных ограничений следует обратиться к дополнительной информации, где изложен такой термин как стирание при преобразовании исходного кода в объектный код. Информация на данную тему относится к нововведениям в Java 2 5.0. Она достаточно обширна и является отдельной темой, которая не описывается в данной статье.

 

 

Ссылки:

 

1. Хорстман К.С., Корнелл Г. Библиотека профессионала. Java 2. Том 1. Основы. — М.: Издательский дом Вильямс, 2003. — 848 с.

2. Евгений Матюшкин. Java - философия, идеология, техника -> Техника -> Массивы в Java.

http://skipy.dev.juga.ru/technics/arrays.html

3. Фесюнов Конспект лекций по Java. Занятие 5.

http://www.javable.com/tutorials/fesunov/

4. Java форум -> JavaTalks -> Основы языка Java. Заполнение двумерного массива.

http://www.javatalks.ru/sutra133.php&highlight=&sid=6f5183512c33a45170983dd6928b63a7

5. Спецификация языка Java. Глава 10. Массивы.

http://www.helloworld.ru/texts/comp/lang/java/java3/10-doc.htm

6. Шилдт Г. Java 2, v5.0 (Tiger). Новые возможности: Пер. с англ. – СПб.: БХВ-Петербург, 2005. – 208 с.

 

 

 

 

Автор:

 

 

Загребин Виктор Александрович

 

 

< Раздел “Язык Java > < Раздел “Технологии Java > < Основная страница >

 

 

Hosted by uCoz