Файл: Курс лекция по Java. Лекция 9.pdf

Добавлен: 05.02.2019

Просмотров: 1414

Скачиваний: 7

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

 

public class Test implements Cloneable {

  Point p;

  int height;

  public Test(int x, int y, int z) {

   p=new Point(x, y);

   height=z;

  }

  public static void main(String s[]) {

   Test t1=new Test(1, 2, 3), t2;

   try {

   t2=(Test) t1.clone();

  } catch (CloneNotSupportedException e) {}

   t1.p.x=-1;

   t1.height=-1;

   System.out.println("t2.p.x=" + t2.p.x + ", t2.height=" + t2.height);

  }

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

 

t2.p.x=-1, t2.height=3 

Из примера видно, что примитивное поле было скопировано и далее существует независимо
в исходном и клонированном объектах. Изменение одного не сказывается на другом.

А вот ссылочное поле было скопировано по ссылке, оба объекта ссылаются на один и тот
же  экземпляр  класса  Point.  Поэтому  изменения,  происходящие  с  исходным  объектом,
сказываются на клонированном.

Этого можно избежать, если переопределить метод clone() в классе Test.

 

 public Object clone() {

  Test clone=null;

  try {

  clone=(Test) super.clone();

 } catch (CloneNotSupportedException e) {

  throw new InternalError(e.getMessage());

}

clone.p=(Point)clone.p.clone();

return clone;

 } 

Обратите внимание, что результат метода Object.clone() приходится явно приводить к типу
Test, несмотря на то, что его реализация гарантирует, что клонированный объект будет
порожден именно от этого класса. Однако тип возвращаемого значения в этом методе для
универсальности объявлен как Object, поэтому явное сужение необходимо.

Программирование на Java

Стр. 14 из 21

Клонирование

Rendered by 

www.RenderX.com


background image

Теперь метод main можно упростить:

 

  public static void main(String s[]) {

  Test t1=new Test(1, 2, 3);

 Test t2=(Test) t1.clone();

  t1.p.x=-1;

  t1.height=-1;

  System.out.println("t2.p.x=" + t2.p.x + ", t2.height=" + t2.height);

 } 

Результатом будет:

 

t2.p.x=1, t2.height=3 

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

Реализация такого "неглубокого" клонирования в методе Object.clone() необходима, так
как  в  противном  случае  клонирование  второстепенного  объекта  могло  бы  привести  к
огромным затратам ресурсов, ведь этот объект может содержать ссылки на более значимые
объекты, а те при клонировании также начали бы копировать свои поля и так далее. Кроме
этого,  поля  клонируемого  объекта  могут  иметь  своим  типом  класс,  не  реализующий
Cloneable, что приводило бы к дополнительным проблемам. Как показано в примере, при
необходимости дополнительное копирование можно легко добавить самостоятельно.

4.1. Клонирование массивов

Итак, любой массив может быть клонирован. В этом разделе хотелось бы рассмотреть
особенности, возникающие из-за того, что Object.clone() копирует только один объект.

Рассмотрим пример:

 

int a[]={1, 2, 3};

int b[]=(int[])a.clone();

a[0]=0;

System.out.println(b[0]); 

Результатом будет ноль, что вполне очевидно, так как весь массив представлен одним
объектом, который не будет зависеть от своей копии. Усложняем пример:

 

int a[][]={{1, 2}, {3}};

int b[][]=(int[][]) a.clone();

if (...) {

// первый вариант:

a[0]=new int[]{0};

System.out.println(b[0][0]);

} else {

// второй вариант:

a[0][0]=0;

Клонирование массивов

Стр. 15 из 21

Программирование на Java

Rendered by 

www.RenderX.com


background image

System.out.println(b[0][0]);

Разберем, что будет происходить в этих двух вариантах. Начнем с того, что в первой строке
создается двухмерный массив, состоящий из 2 одномерных, итого 3 объекта. Затем, на
следующей строке при клонировании будет создан новый двухмерный массив, содержащий
ссылки на те же самые одномерные массивы.

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

Во  втором  случае  модифицируется  существующий  массив,  что  скажется  на  обоих
двумерных массивах. На консоли появится 0.

Обратите внимание, что если из примера убрать условие if-else, так, чтобы отрабатывал
первый вариант, а затем последовательно второй, то результатом будет опять 1, поскольку
в  части  второго  варианта  модифицироваться  будет  уже  новый  массив,  порожденный  в
части первого варианта.

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

5. Заключение

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

Создать  массив  можно  как  с  помощью  ключевого  слова  new,  поскольку  все  массивы,
включая определенные на основе примитивных значений, имеют объектный тип. Другой
способ – воспользоваться инициализатором, и перечислить значения всех элементов. В
первом случае элементы принимают значения по умолчанию (0, false, null).

Особым  образом  в  Java  устроены  многомерные  мас

сивы.  Они,  по  сути,  являются

одномерными, основанными на массивах меньшей размерности. Такой подход позволяет
единым образом работать с многомерными массивами. Также он позволяет создавать не
только «прямоугольные» массивы, но и любой конфигурации.

Хотя  массив  и  является  ссылочным  типом,  работа  с  ним  зачастую  имеет  некоторые
особенности.  Рассматриваются  правила  приведения  типа  массива.  Как  для  любого
объектного  типа,  приведение  к  Object  является  расширяющим.  Приведение  массивов,
основанных  на  ссылочных  типах,  во  многом  подчиняется  обычным  правилам.  А  вот
примитивные  массивы  преобразовывать  нельзя.  С  преобразованиями  связано  и

Программирование на Java

Стр. 16 из 21

Заключение

Rendered by 

www.RenderX.com


background image

возникновение  ошибки  ArrayStoreException,  причина  которой  –  невозможность  точного
отслеживания типов в преобразованном массиве для компилятора.

В  заключение  рассматриваются  последние  случаи  взаимосвязи  типа  переменной  и  ее
значения.

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

6. Контрольные вопросы

9-1.

Массивы каких типов и длин объявляются в следующем коде?

         int x[], y[][];

         byte[] a, b[][];

         String s, s1[], s2={{}, {“a”, “b”}, null};

      

a.) Ответ:

переменная x имеет тип int[], y – int[][]. Поскольку массивы не созданы,
длины у них нет.

переменная  a  имеет  тип  byte[],  b  –  byte[][][].  Поскольку  массивы  не
созданы, длины у них нет.

Переменная s не массив, s1 – String[], s2 – String[][]. Длина определена
только у s2, она равна 3. Первым элементом этого двухмерного массива
является  одномерный  массив  длиной  0,  вторым  –  массив  длиной  2,
третьим - null.

9-2.

Корректен ли следующий код, и если нет, то в каких местах будут возникать ошибки,
и какие?

         int b[]=new int[5];

         for (int i=1; i<=b.length(); i++) {

            b[i]=Math.sqrt(i);

         }

      

a.) Код некорректен, в нем есть целый ряд ошибок. Во-первых, у массивов

нет  метода  length(),  есть  только  поле  length.  Во-вторых,  в  3-ей  строке
делается попытка неявного приведения от типа double к int – результат
работы метода Math.sqrt приравнивается целочисленной переменной.

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

Стр. 17 из 21

Программирование на Java

Rendered by 

www.RenderX.com


background image

время  как  максимально  допустимым  является  значение  длины  без
единицы, то есть 4.

Наконец, перебор массива, начиная с индекса 1, пропускает элемент с
индексом 0.

9-3.

Может ли массив основываться на абстрактных классах? Интерфейсах? Если да,
то какие значение могут принимать его элементы?

a.) Да. Элементы таких массивов будут ссылаться на объекты, порожденные

от  неабстрактных  классов,  которые  являются  наследниками  данного
абстрактного класса или реализуют данный интерфейс соответственно.

9-4.

Как  создать  массив,  эквивалентный  объявляемому  ниже,  но  без  заведения
переменной?

         int x[][]=new int[2][3];

      

a.) Следующим образом:

         new int[][]{{0, 0, 0}, {0, 0, 0}}

      

9-5.

Корректен ли следующий код? Если нет, то какие исправления можно предложить?

         byte b[]={1, 2, 3};

         Object o=b;

         o=new String[]{“”, “a”, “b”};

         String s[]=o;

      

a.) Нет.  В  4  строке  делается  попытка  неявного  сужения  типов  от  Object  к

String[]. Такое действие нужно делать явно:

         String s[]=(String[])o;

      

9-6.

Сколько  объектов  порождается  при  инициализации  массива  new  int[3][4]?  new
int[3][][]?

a.) В  первом  случае  создается  3  одномерных  массива  длиной  4  и  один

двумерный массив, то есть всего 4 объекта.

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

9-7.

От какого класса наследуются классы массивов? Какие интерфейсы реализуются?
Какие элементы они объявляют или переопределяют по сравнению с родительским
классом?

a.) Классы наследуются от java.lang.Object.

Реализуют 2 интерфейса – java.lang.Cloneable и java.io.Serializable.

Программирование на Java

Стр. 18 из 21

Контрольные вопросы

Rendered by 

www.RenderX.com