1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
12.2 制作本地副本.md 49 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

12.2 Создание локальной копии

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

  • Процесс передачи аргументов автоматически создает проблемы с псевдонимами.
  • Нет локальных объектов, только локальные ссылки.
  • Ссылки имеют область видимости, объекты же — нет.
  • Проблема "времени существования" объекта в Java отсутствует.
  • Отсутствуют языковые средства (например, константы), препятствующие модификации объекта (что бы избежать побочных эффектов псевдонимов).

Если требуется просто считывать информацию из объекта, не изменяя его, то передача ссылки является наиболее эффективной формой передачи аргументов. Это вполне уместно; обычно используемые методы также являются самыми эффективными. Однако иногда требуется рассматривать объект как "локальный", чтобы изменения затрагивали только локальную копию, а не внешний объект. Многие языки программирования поддерживают создание локальной копии внешнего объекта внутри метода (примечание ①). Хотя Java этого не поддерживает, она позволяет достичь аналогичного эффекта.①: В C язык обычно управляет небольшим количеством данных, и по умолчанию используется передача по значению. C++ также следует этой модели, но передача объектов по значению не всегда является эффективной. Кроме того, код, поддерживающий передачу по значению в C++, часто сложно писать и вызывает проблемы.

12.2.1 Передача по значению

Сначала стоит решить вопрос с терминологией; наиболее подходящим для "передачи по значению" кажется термин "параметр". "Передача по значению" и её значение зависят от понимания способа работы программы. Самым распространённым значением является получение локальной копии любого передаваемого объекта, однако основная проблема заключается в том, как воспринимаются сами передаваемые данные. Для значения "передачи по значению" существуют два явно отличающихся мнения:(1) Java передает по значению всё. Если базовый тип данных передается в метод, то получается локальная копия этого типа. Но если ссылка передается в метод, то получается копия этой ссылки. Таким образом, люди считают, что "всё" передается по значению. Конечно, это предположение имеет одно условие: ссылка также должна передаваться. Однако модель дизайна Java выглядит несколько опережающей своё время, позволяя нам игнорировать (в большинстве случаев) тот факт, что мы работаем со ссылками. То есть она позволяет нам представлять ссылки как "объекты", поскольку система автоматически заботится о различиях между ними при вызове метода. Java в основном передает значения без параметров, но объекты передаются по ссылкам. Эта концепция основана на том, что ссылка является лишь «альтернативным названием» объекта, поэтому вопрос передачи ссылки не рассматривается, а вместо этого указывается, что передается сам объект. Поскольку при передаче объекта в метод не создается локальная копия объекта, можно сделать вывод, что объекты не передаются по значению. Компания Sun как бы поддерживает эту идею, так как один из ключевых слов, которое она "сохраняет, но не реализует", — это byvalue (по значению). Однако никто не знает, когда это ключевое слово будет использоваться.Несмотря на наличие двух различных взглядов, расхождение между ними сводится к различной интерпретации понятия «ссылка». В остальной части этой книги я намерен избежать этого вопроса. Читатель вскоре поймет, что продолжение спора бесполезно — важно понять, что передача ссылки может привести к неожиданным изменениям объекта вызывающего кода.

12.2.2 Клонирование объектов

Если требуется модифицировать объект, не меняя при этом объект вызывающего кода, следует создать локальную копию объекта. Это одна из самых распространенных причин использования локальных копий. Для создания такой копии достаточно использовать метод clone(). Слово clone означает «клонировать», то есть создание полной копии. Этот метод определен в базовом классе Object как защищённый (protected). Однако в любом производном классе, где требуется клонирование, его следует переопределить как открытый (public). Например, стандартный библиотечный класс Vector переопределяет метод clone(), позволяя вызывать этот метод для Vector, как показано ниже:

//: Cloning.java
// Операция клонирования работает только с некоторыми
// элементами стандартной библиотеки Java.
import java.util.*;

class Int {
  private int i;
  public Int(int ii) { i = ii; }
  public void increment() { i++; }
  public String toString() {
    return Integer.toString(i);
  }
}
``````Метод `clone()` создаёт объект `Object`, который немедленно должен быть преобразован в правильный тип. Этот пример демонстрирует, что метод `clone()` в классе `Vector` не может автоматически пытаться клонировать каждый объект внутри `Vector`  из-за проблемы с алиасами, старый `Vector` и клонированный `Vector` содержат одни и те же объекты. Обычно мы называем это "простым копированием" или "얕ой копией", так как она копирует только "поверхностные" части объекта. Реальные объекты, помимо своей "поверхности", также содержат ссылки на другие объекты, а эти объекты, в свою очередь, могут указывать на ещё более дальние объекты, и так далее. Это приводит к понятию "объектной сети" или "сети взаимосвязей объектов". Когда все связи этой сети копируются, это называется "полной копией" или "глубокой копией".
```Результаты "얕ой копии" можно видеть в выводе, обратите внимание, что действия над `v2` влияют и на `v`:

v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


Общие правила гласят, что поскольку нельзя гарантировать, что объекты внутри `Vector` являются "клонируемыми" (Примечание ②), лучше не пытаться клонировать эти объекты.

②: "Клонируемый" на английском языке звучит как `cloneable`. Обратите внимание, что ключевое слово `cloneable` специально предусмотрено в Java библиотеке.

### 12.2.3 Делаем класс клонируемым

Хотя метод клонирования определен во всех базовых классах `Object`, он не выполняется автоматически для каждого класса. Это кажется странным, потому что методы базового класса должны быть доступны для производных классов. Однако Java работает против этого; если вы хотите использовать метод клонирования в своем классе, вам придется добавить специальный код для обеспечения его выполнения.

(1) Использование `protected`Чтобы избежать того, чтобы каждый созданный нами класс имел по умолчанию возможность клонирования, метод `clone()` был "сохранён" (установлен как `protected`) в базовом классе `Object`. В результате этого клиентские программисты, использующие этот класс, не имеют доступа к этому методу по умолчанию; кроме того, мы не можем вызвать `clone()` через ссылку на базовый класс (хотя это было бы полезно в некоторых случаях, например при использовании полиморфизма для клонирования последовательности объектов). На этапе компиляции это фактически сообщает нам, что объект не является клонируемым — и самое странное то, что большинство классов в Java библиотеке не поддерживают клонирование. Таким образом, если мы выполним следующий код:

```java
int x = new int(l);
x = x.clone();
```Тогда при компиляции появляется неприятное сообщение об ошибке, которое говорит нам, что `clone()` недоступен — потому что `Integer` не переопределяет его, а использует защищённую версию по умолчанию.

Однако, если мы находимся в классе, производном от `Object` (все классы производятся от `Object`), то имеем право вызвать `Object.clone()`, так как он является защищённым, и мы находимся внутри итератора. Базовый метод `clone()` предоставляет полезную функциональность — он выполняет "битовую" копию объекта производного класса, поэтому это эквивалентно стандартному клонированию. Однако затем нам следует сделать свой метод клонирования публичным, иначе он будет недоступен. В общем, два ключевых момента при клонировании: почти всегда следует вызывать `super.clone()`, и важно сделать клонирование публичным.Иногда может потребоваться переопределить метод `clone()` в более глубоко производном классе, и тогда можно просто использовать наш метод `clone()` (теперь сделанный публичным), но это может не быть тем, что мы хотели бы видеть (хотя, поскольку `Object.clone()` создаёт фактическую копию объекта, возможно, это допустимо). Трюк с защищённостью можно применять только один раз: первый раз, когда вы наследуетесь от класса, который не имеет возможности клонировать себя, и хотите сделать этот класс "клонируемым". А после этого, метод `clone()` становится доступным для использования во всех случаях наследования от нашего класса, так как Java не позволяет уменьшать область доступа метода после наследования. Другими словами, как только объект становится клонируемым, все производные от него объекты также становятся клонируемыми, пока не используется специальное устройство (обсуждаемое ниже), чтобы "отключить" возможность клонирования.(2) Реализация интерфейса `Cloneable`

Чтобы полностью реализовать способность объекта к клонированию, требуется ещё одно действие: реализация интерфейса `Cloneable`. Этот интерфейс кажется странным, так как он пустой!

```java
interface Cloneable {}

Почему требуется реализация этого пустого интерфейса? Очевидно, это не связано с приведением типа к Cloneable и вызовом одного из его методов. Некоторые считают использование этого интерфейса "мошенничеством", так как его используют для других целей, отличных от первоначального назначения. Реализация интерфейса Cloneable играет роль метки, которая заключена в тип класса.

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

if (myHandle instanceof Cloneable) { // ...
}

Второй причиной является то, что мы можем не захотеть, чтобы все объекты были клонируемыми. Поэтому метод Object.clone() проверяет, реализует ли класс интерфейс Cloneable. Если ответ отрицательный, он выбрасывает исключение CloneNotSupportedException. Таким образом, в общем случае нам необходимо реализовать Cloneable, чтобы предоставить поддержку клонирования.## 12.2.4 Успешное клонирование

Поняв все детали реализации метода clone(), можно создать классы, которые легко копировать, предоставляя локальную копию:

//: LocalCopy.java
// Создание локальных копий с помощью clone()
import java.util.*;

class MyObject implements Cloneable {
  int i;
  MyObject(int ii) { i = ii; }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("MyObject cannot be cloned");
    }
    return o;
  }
  public String toString() {
    return Integer.toString(i);
  }
}

public class LocalCopy {
  static MyObject g(MyObject v) {
    // Передача указателя, модифицирующего внешний объект:
    v.i++;
    return v;
  }
  static MyObject f(MyObject v) {
    v = (MyObject)v.clone(); // Локальная копия
    v.i++;
    return v;
  }
  public static void main(String[] args) {
    MyObject a = new MyObject(11);
    MyObject b = g(a);
    // Проверка эквивалентности указателей,
    // а не эквивалентности объектов:
    if(a == b)
      System.out.println("a == b");
    else
      System.out.println("a != b");
    System.out.println("a = " + a);
    System.out.println("b = " + b);
    MyObject c = new MyObject(47);
    MyObject d = f(c);
    if(c == d)
      System.out.println("c == d");
    else
      System.out.println("c != d");
    System.out.println("c = " + c);
    System.out.println("d = " + d);
  }
} ///:~
```Независимо от этого, метод `clone()` должен быть доступен, поэтому его следует сделать публичным (`public`). Вторым шагом при вызове `clone()` должно быть обращение к версии метода `clone()` базового класса. Здесь вызывается `clone()` из класса `Object`, который заранее определён. Он может быть вызван благодаря тому, что имеет свойство `protected` (защищённый), позволяющее доступ к нему из производных классов. `Object.clone()` проверяет размер исходного объекта и выделяет достаточно памяти для нового объекта, а затем копирует все двоичные биты из исходного объекта в новый объект. Это называется "битовым копированием", и обычно считается, что эта задача должна выполняться методом `clone()`. Однако перед тем как начать выполнение, `Object.clone()` сначала проверяет, реализует ли класс интерфейс `Cloneable`, то есть имеет ли он возможность клонирования. Если этот интерфейс не реализован, `Object.clone()` выбрасывает исключение `CloneNotSupportedException`, указывая, что клонировать данный объект нельзя. Поэтому лучше всего использовать блок `try-catch`, чтобы обернуть вызов `super.clone()`, попытаться захватить это исключение (которое никогда не должно возникнуть, так как здесь действительно реализован интерфейс `Cloneable`). В `LocalCopy`, два метода `g()` и `f()` демонстрируют различия в способах передачи аргументов.В частности, метод `g()` демонстрирует передачу по ссылке, которая модифицирует внешний объект и возвращает ссылку на этот внешний объект. С другой стороны, метод `f()` создает клонированную версию аргумента, отделенную от исходного объекта, и позволяет последнему оставаться независимым. Затем он продолжает выполнять свои действия, даже возвращая ссылку на новый объект без каких-либо побочных эффектов для исходного объекта.Обратите внимание на следующее несколько странное выражение:

```java
v = (MyObject) v.clone();

Это выражение служит для создания локальной копии. Чтобы избежать путаницы с таким выражением, помните, что довольно странный вид кода допустим в Java, поскольку всё, что имеет имя, фактически является ссылкой. Таким образом, ссылка v используется для клонирования того объекта, на который она указывает, и в конечном итоге возвращается ссылка на базовый тип Object (потому что это так определено в Object.clone()) и затем должна быть приведена к правильному типу.

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

a == b
a = 12
b = 12
c != d
c = 47
d = 48

Помните, что проверка "равенства" в Java не выполняет внутреннего сравнения значений сравниваемых объектов. Операторы == и != просто сравнивают содержимое ссылок. Если адреса внутри ссылок совпадают, считается, что ссылки указывают на один и тот же объект, поэтому они считаются "равными". Таким образом, операторы действительно проверяют вопрос "не являются ли ссылки одним и тем же объектом благодаря проблеме алиасов?".## 12.2.5 Эффективность Object.clone()

Что происходит при вызове Object.clone()? Когда мы переопределяем метод clone() в своём классе, что наиболее важно для вызова super.clone()?

Встроенный метод clone() в корневых классах отвечает за создание правильной емкости памяти и "битового копирования" битов из исходного объекта в новую область памяти. То есть он не просто зарезервирует место в памяти и скопирует объект, но и точно вычислит размер объекта, который требуется скопировать, а затем выполнит эту операцию. Поскольку всё это выполняется внутри метода clone(), определённого корневыми классами (корневые классы не знают, какой тип они будут наследовать), используется RTTI (Run-Time Type Information) для определения реального размера объекта, который следует клонировать. Таким образом, метод clone() может создать правильное количество памяти и выполнить правильное "битовое копирование" этого типа.

Независимо от того, что мы собираемся делать, первым шагом клонирования обычно должно быть вызов super.clone(). Выполняя точную копию, это позволяет создать надёжную основу для последующего клонирования. Затем можно выполнить необходимые действия для завершения окончательного клонирования.Чтобы точно понять, какие именно действия требуются, сначала следует правильно понять, что нам предоставляет метод Object.clone(). В частности, он автоматически выполняет клонирование всех объектов, на которые указывают ссылки? Этот пример позволяет проверить:

//: Snake.java
// Проверяет клонирование, чтобы узнать, происходит ли клонирование целевых объектов,
// на которые указывают ссылки.

public class Snake implements Cloneable {
  private Snake next;
  private char c;
  // Значение i равно количеству сегментов
  Snake(int i, char x) {
    c = x;
    if (--i > 0)
      next = new Snake(i, (char) (x + 1));
  }

  void increment() {
    c++;
    if (next != null)
      next.increment();
  }

  @Override
  public String toString() {
    String s = ":" + c;
    if (next != null)
      s += next.toString();
    return s;
  }

  @Override
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
    }
    return o;
  }

  public static void main(String[] args) {
    Snake s = new Snake(5, 'a');
    System.out.println("s = " + s);
    Snake s2 = (Snake) s.clone();
    System.out.println("s2 = " + s2);
    s.increment();
    System.out.println(
        "после s.increment(), s2 = " + s2);
  }
}
///:~

Змея состоит из нескольких сегментов, каждый из которых является экземпляром типа Snake. Таким образом, это цепочка связанных друг с другом сегментов. Все сегменты создаются в цикле, при этом значение первого параметра конструктора уменьшается до нуля. Для уникальной маркировки каждого сегмента второй параметр (тип данных char) увеличивается на каждом повторении конструктора.Метод increment() используется для циклического увеличения каждой метки, позволяя видеть изменения; а метод toString выводит каждую метку в цепочке. Вывод будет следующим:

s = :a:b:c:d:e
s2 = :a:b:c:d:e
после s.increment(), s2 = :a:c:d:e:f

Это означает, что только первый сегмент был скопирован методом Object.clone(), поэтому здесь происходит "얕ое клонирование". Чтобы скопировать всю змейку — то есть выполнить "глубокое клонирование" — требуется выполнять дополнительные действия внутри переопределенного метода clone(). Обычно можно вызвать super.clone() в методе клонирования класса, который может быть склонирован, чтобы гарантировать выполнение всех действий базовых классов (включая Object.clone()). При этом явно следует вызывать метод clone() для каждого внутреннего объекта с ссылками; в противном случае эти ссылки будут являться псевдонимами для ссылок исходного объекта. Вызов конструктора также происходит аналогичным образом — сначала создаются базовые классы, затем следующие производные конструкторы... и так далее до самого глубоко вложенного производного конструктора. Разница заключается в том, что clone() не является конструктором, поэтому нет способа обеспечить автоматическое клонирование. Для клонирования требуется явное выполнение этого процесса.## 12.2.6 Клонирование составного объекта

При попытке глубокого копирования составного объекта возникают проблемы. Предполагается, что метод clone() внутри членских объектов также выполняет глубокое копирование своих ссылок, и так далее. Это усложняет задачу. Чтобы обеспечить правильное выполнение глубокого копирования, необходимо контролировать код во всех классах или хотя бы полностью понимать все классы, которые должны быть включены в этот процесс.

Ниже приведён пример, который демонстрирует действия, необходимые для глубокого копирования составного объекта:

//: DeepCopy.java
// Клонирование составного объекта

class DepthReading implements Cloneable {
  private double depth;
  public DepthReading(double depth) {
    this.depth = depth;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return o;
  }
}

class TemperatureReading implements Cloneable {
  private long time;
  private double temperature;
  public TemperatureReading(double temperature) {
    time = System.currentTimeMillis();
    this.temperature = temperature;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return o;
  }
}
``````java
class OceanReading implements Cloneable {
  private DepthReading depth;
  private TemperatureReading temperature;

  public OceanReading(double tdata, double ddata) {
    temperature = new TemperatureReading(tdata);
    depth = new DepthReading(ddata);
  }

  public Object clone() {
    OceanReading o = null;
    try {
      o = (OceanReading) super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    // Нужно склонировать ссылки:
    o.depth = (DepthReading) o.depth.clone();
    o.temperature = (TemperatureReading) o.temperature.clone();
    return o; // Преобразование обратно в Object
  }
}
## 12.2.7 Глубокое клонирование с использованием `Vector`
```Давайте повторно рассмотрим пример с классом `Vector`, который был представлен ранее в этой главе. На этот раз класс `Int2` может быть клонирован, поэтому мы можем выполнить глубокое клонирование `Vector`:

```java
//: ДобавлениеКлонирования.java
// Для добавления возможности клонирования вам придется пройти через несколько шагов.
import java.util.*;

class Int2 implements Cloneable {
  private int i;
  public Int2(int ii) { i = ii; }
  public void увеличить() { i++; }
  public String toString() {
    return Integer.toString(i);
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("Int2 не может быть клонирован");
    }
    return o;
  }
}

// После того как класс становится клонируемым,
// наследование не убирает возможность клонирования:
class Int3 extends Int2 {
  private int j; // Автоматически дублировано
  public Int3(int i) { super(i); }
}

public class ДобавлениеКлонирования {
  public static void main(String[] args) {
    Int2 x = new Int2(10);
    Int2 x2 = (Int2)x.clone();
    x2.увеличить();
    System.out.println(
      "x = " + x + ", x2 = " + x2);
    // Всякий наследуемый объект также является клонируемым:
    Int3 x3 = new Int3(7);
    x3 = (Int3)x3.clone();
  }
}
Вектор v = new Вектор();
for(int i = 0; i < 10; i++) 
  v.addЭлемент(new Int2(i));
System.out.println("v: " + v);
Вектор v2 = (Вектор) v.clone();
// Теперь клонируем каждый элемент:
for(int i = 0; i < v.size(); i++)
  v2.setElementAt(((Int2) v2.elementAt(i)).clone(), i);
// Увеличиваем все элементы v2:
for(Enumeration e = v2.elements(); e.hasMoreElements(); ) 
  ((Int2) e.nextElement()).increment();
// Проверяем, изменились ли элементы v:
System.out.println("v: " + v);
System.out.println("v2: " + v2);

} } ///:~Int3 наследуется от Int2, и в нем добавлен новый член типа данных int j. Возможно, вы полагаете, что вам снова придется переопределить метод clone(), чтобы гарантировать, что j будет скопирован, но это не так. Когда вызывается метод clone() класса Int2 для объекта Int3, он вызывает метод Object.clone(), который распознает текущий тип как Int3 и копирует все двоичные данные внутри него. Если нет новых ссылочных полей, требующих клонирования, один вызов метода Object.clone() выполнит всю необходимую работу — независимо от того, где именно был определен метод clone(). ```На этом можно сделать вывод о том, какие условия должны выполняться для глубокого клонирования Vector: после клонирования `Vector` следует пройтись по всем его элементам и склонировать каждый указываемый им объект. Для глубокого клонирования `Hashtable` (ассоциативного массива) также потребуется аналогичная процедура.

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

12.2.8 Глубокое клонирование с помощью сериализации

Если внимательно рассмотреть пример сериализации Java 1.1, представленный в главе 10, можно заметить, что процесс восстановления объекта после его сериализации и десериализации фактически представляет собой "клонирование" объекта.

Зачем тогда использовать сериализацию для глубокого клонирования? В следующем примере сравниваются времена выполнения этих двух подходов:

//: Compete.java
import java.io.*;

class Thing1 implements Serializable {}
class Thing2 implements Serializable {
  Thing1 o1 = new Thing1();
}

class Thing3 implements Cloneable {
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("Thing3 cannot be cloned");
    }
    return o;
  }
}
``````java
class Thing4 implements Cloneable {
   Thing3 o3 = new Thing3();
   public Object clone() {
     Thing4 o = null;
     try {
       o = (Thing4) super.clone();
     } catch (CloneNotSupportedException e) {
       System.out.println("Thing4 cannot be cloned");
     }
     // Клонируем поле:
     o.o3 = (Thing3) o3.clone();
     return o;
   }
}
публичный класс Compete {
   статический константный целое SIZE = 5000;
   публичный статический метод главный(строка[] аргументы) {
     объект Thing2[] a = новый объект Thing2[SIZE];
     для(целое i = 0; i < a.длина; i++)
       a[i] = новый объект Thing2();
     объект Thing4[] b = новый объект Thing4[SIZE];
     для(целое i = 0; i < b.длина; i++)
       b[i] = новый объект Thing4();
     попробовать {
       долгое t1 = систем.currentTimeMillis();
       объект ByteArrayOutputStream buf =
         новый объект ByteArrayOutputStream();
       объект ObjectOutputStream o =
         новый объект ObjectOutputStream(buf);
       для(целое i = 0; i < a.длина; i++)
         o.writeObject(a[i]);
       // Теперь получаем копии:
       объект ObjectInputStream in =
         новый объект ObjectInputStream(
           новый объект ByteArrayInputStream(
             buf.toByteArray()));
       объект Thing2[] c = новый объект Thing2[SIZE];
       для(целое i = 0; i < c.длина; i++)
         c[i] = (Thing2) in.readObject();
       долгое t2 = систем.currentTimeMillis();
       систем.println(
         "Дублирование через сериализацию: " +
         (t2 - t1) + " миллисекунды");
       // Теперь пробуем клонирование:
       t1 = систем.currentTimeMillis();
       объект Thing4[] d = новый объект Thing4[SIZE];
       для(целое i = 0; i < d.длина; i++)
         d[i] = (Thing4) b[i].clone();
       t2 = систем.currentTimeMillis();
       систем.println(
         "Дублирование через клонирование: " +
         (t2 - t1) + " миллисекунды");
     } захватить(исключение e) {
       e.printStackTrace();
     }
   }
}
``````java
printStackTrace();
}
}

///:~В этом разделе рассматривается процесс глубокого клонирования объектов, содержащих вложенные объекты. Интересной особенностью является то, что хотя реализация интерфейса Serializable может быть легко настроена, при клонировании таких объектов требуется значительно больше работы. Процесс клонирования требует значительных усилий по настройке классов, но сам процесс копирования объектов довольно прост.Результаты нескольких запусков показывают следующее:

Клонирование через сериализацию: 3400 миллисекунд
Клонирование через клонирование: OnClickListener 110 миллисекунд

Клонирование через сериализацию: 3410 миллисекунд
Клонирование через клонирование: OnClickListener 110 миллисекунд

Клонирование через сериализацию: 3520 миллисекунд
Клонирование через клонирование: OnClickListener 110 миллисекунд

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

12.2.9 Добавление глубины клонирования

Если создать новый класс, его базовым классом будет Object, который по умолчанию не имеет возможности клонирования (как это будет видно в следующем разделе). Эта возможность не появится автоматически, если явно её не добавить. Однако можно добавить эту возможность на любом уровне наследования, после чего все уровни ниже этого уровня будут иметь возможность клонирования. Пример представлен ниже:

//: HorrorFlick.java
// Возможность клонирования может быть добавлена на любом уровне наследования.
import java.util.*;

class Person {}
class Hero extends Person {}
class Scientist extends Person implements Cloneable {
  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      // Это никогда не должно произойти:
      // Он уже реализует интерфейс Cloneable!
      throw new InternalError();
    }
  }
}
class MadScientist extends Scientist {}
``````java
public class HorrorFlick {
  public static void main(String[] args) {
    Person p = new Person();
    Hero h = new Hero();
    Scientist s = new Scientist();
    MadScientist m = new MadScientist();

    // p = (Person)p.clone(); // Compilation error
    // h = (Hero)h.clone(); // Compilation error
    s = (Scientist)s.clone();
    m = (MadScientist)m.clone();
  }
} ///:~

До добавления возможности клонирования компилятор будет блокировать попытки клонирования. После того как возможность клонирования добавлена в класс Scientist, этот класс и все его потомки могут быть клонированы.

12.2.10 Почему такой странный дизайн?

Этот вопрос затрагивает аспекты дизайна Java, связанные с возможностями клонирования и сериализации. Чувство уникальности этого решения объясняется тем, что это действительно так. Возможно, вас удивляет вопрос, почему оно работает именно таким образом, а какой же истинный смысл стоит за этим? Впереди вас ждет история, которая пока не была полностью проверена — возможно, благодаря множеству сделок с Java, язык стал хорошо спроектированным, но потребовалось много слов, чтобы объяснить все события, происходящие за кулисами.Изначально Java был создан как язык для управления аппаратными средствами, никак не связанный с интернетом. Как и в случае с другими массово используемыми языками программирования, его особенностью было то, что программист мог клонировать любой объект. Таким образом, метод clone() был помещен в базовый класс Object, и поскольку он был общедоступным, мы обычно имели возможность клонировать любой объект. Это казалось наиболее гибким подходом, так как он не приносил никакого вреда.Когда Java начал выглядеть как идеальный язык программирования для Интернета, ситуация начала меняться. Внезапно возникли вопросы безопасности, и вполне естественно, они были связаны с использованием объектов; никто не хотел бы, чтобы кто-то мог клонировать свои конфиденциальные объекты. Поэтому нам пришлось видоизменить первоначальное простое и интуитивно понятное решение, добавив множество патчей: метод clone() в классе Object был сделан защищённым (protected), требовалось переопределять его и использовать интерфейс Cloneable, а также решать проблемы с исключениями.

Единственный случай, когда использование интерфейса Cloneable не требуется, — это вызов метода clone() класса Object. Этот метод проверяет во время выполнения, реализует ли наш класс интерфейс Cloneable. Однако для сохранения последовательности (и потому что интерфейс Cloneable является пустым), лучше всего самому реализовать этот интерфейс.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master