Книга е още в много ранна фаза на написване


Създаване на локални копия



страница70/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   65   66   67   68   69   70   71   72   73

Създаване на локални копия


Да припомним: всички аргументи в Java се подават чрез подаване на ма­ни­пу­ла­то­ри. Тоест, когато давате “обект,” в действителност давате само манипулатор към обект който живее извън метода, така че ако направите промени с него ма­ни­пулатор, променяте външния обект. Освен това:

  • Автоматично се създават псевдоними при предаването на аргументите.

  • Няма локални обекти, а само локални манипулатори.

  • Манипулаторите имат обхвати, обектите нямат.

  • В Java не стои въпросът с времето на живот на обектите.

  • Няма поддръжка от езика (напр. като const) за предотвратяване моди­фи­ци­ра­нето на обект (за предпазване от отрицателните ефести на псев­до­ни­ми­те).

Ако само четете информация от обект и не го променяте, даването на ма­ни­пу­ла­тор е най-ефикасната форма на подаване на аргументи. Това е добре; най-ефи­касният начин да се направят нещата е и начин по подразбиране. Обаче по­ня­кога става нужда да се третират обекти както ако бяха “локални” така че про­ме­ните да засягат само локалното копие и да не засягат външния обект. Много про­грамни езици поддържат възможността за правене на локално копие на външ­­ния обект вътре в метода.1 Java не прави така, но дава възможност да се по­­стигне такъв ефект.

Предаване по стойност


Това извежда на преден план терминологичен въпрос, който винаги изглежда до­бре за аргумент. Терминът е “предаване по стойност,” а значението зависи от то­ва как схващате работата на програмата. Общото значение е че вземате ло­кал­но копие от това което подавате, но реалният въпрос е какво мислите за то­ва, което подавате. Като дойде ред на значението “предаване по стойност,” има две доволно различни становища:

  1. Java дава всичко по стойност. Когато давате примитиви на метод, имате различно копие на примитива. Когато давате манипулатор на метод, имате ко­пие на манипулатора. Ergo, всичко се предава по стойност. Разбира се, предположението е че винаги мислите (и правите) да се предават ма­ни­пу­ла­то­ри, но изглежда каточели дизайнът на Java е изминал голям път нанатък поз­волявайки ви (товечето време) да работите с манипулатор. Тоест, изглежда да мислите за манипулатора като за “обекта,” понеже всяко извикване на метод работи с обекта.

  2. Java подава примитивите по стойност (няма аргумент), но обектите се пре­да­ват с позоваване. Това е световната координатна система където ма­ни­пу­ла­торите са псевдоними на обектите, така че не мислите за подаване на ма­ни­пулатори, ами казвате “Аз предавам обект.” Доколкото не се работи с ло­кално копие на обекта като го давате за аргумент, обектите явно не се пре­дават по стойност. Види се има поддръжка за тази гледна точка от Sun, по­неже една от “резервираните но не реализирани” ключови думи е byvalue. (Никой не знае, обаче, дали тя ще види бялелия свят някой ден.)

Като сме огледали добре и двете и сме казали “Зависи от това как гледате на ма­нипулатора,” Ще се опитам да проследя въпроса в останалата част на гла­ва­та. В края на краищата това не е толкова важно – мажното е да разберете, че по­даването на манипулатор може да промени извикващия обект неочаквано.

Клониране на обекти


Най-вероятната причина да правите локално копие е ако искате да променяте ко­пието и не искате да се променя извикващия обект. Ако пожелаете да правите ло­кално копие, просто викате метода clone( ) да направи това. Този метод е де­фи­ниран като protected в базовия клас Object и трябва да го подтиснете като public в който и да е извлечен клас който искате да може да се клонира. На­при­мер стандартния библиотечен клас ArrayList подтиска clone( ), така че може да из­викаме clone( ) за ArrayList:

//: c12:Cloning.java

// The clone() operation works for only a few

// items in the standard Java library.

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);

}

}
public class Cloning {



public static void main(String[] args) {

ArrayList v = new ArrayList();

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

v.add(new Int(i));

System.out.println("v: " + v);

ArrayList v2 = (ArrayList)v.clone();

// Increment all v2's elements:

for(Iterator e = v2.iterator();

e.hasNext(); )

((Int)e.next()).increment();

// See if it changed v's elements:

System.out.println("v: " + v);

}

} ///:~


Методът clone( ) прави Object, който после трябва да бъде преобразуван в пра­вил­ния тип. Този пример показва как митодът clone( ) на ArrayList не се опитва ав­томатично да клонира всеки съдържан в ArrayList обект – стария ArrayList и клонинга ArrayList са псевдоними на един и същ обект. Това често се нарича плит­ко копиране, понеже се копира само “повърхностната” порция на обект. Фак­тически обектът се състои от тази “повърхност” плюс обектите към които со­чат манипулаторите в обекта, плюс всички обекти към които тези обекти со­чат и т.н. Това често се споменава като “паяжина от обекти.” Копирането на ця­ла­та суматоха се нарича дълбоко копиране.

Може да видите ефекта от плиткото копиране в изхода, където операциите изпълнени над v2 засягат v:

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

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

Да не се прилага clone( ) към обектите съдържани в ArrayList е вероятно добро пред­положение, понеже не е сигурно, че всички те са клонируеми.2

Правене на клас клонируем


Макар че клониращия метод да принадлежи към базовия за всички клас Object, кло­нирането не е автоматично налице за всеки клас.3 Това може да изглежда анти­интуитивно на идеята че всички наследени методи са достъпни в из­вле­че­ния клас. Клонирането в Java върви срещу тази идея; ако искате да се клонира в клас, трябва да добавите специално код за да стане това.

Използване на трик с protected


За да се предотврати клонируемостта по подразбиране във всеки клас, методът clone( ) е protected в базовия клас Object. Това значи не само недостъпност за клиент-програмиста който просто използва класа (без да наследява от него), но съ­що и че не може да се вика clone( ) чрез манипулатор към базовия клас. (Ма­кар и това да би могло да бъде полезно в някои ситуации, като тази да се кло­ни­ра куп Objectи полиморфно.) Това е ефективно начин да се даде, по време на ком­пилация, информация че вашият обект не е клонируем – и достатъчно не­прият­но многото класове в стандартната Java библиотека не са клонируеми. Та­ка, ако напишете:

Integer x = new Integer(1);

x = x.clone();

ще получите, по време на компилация, съобщение за грешка, казващо че clone( ) не е достъпенe (понеже Integer не го подтиска и се вика този по под­раз­би­ране - protected версията).

Ако, обаче, сте в клас извлечен от Object (както всички класове са), имате поз­во­лението да викате Object.clone( ) понеже той е protected и вие сте в на­след­ни­ка. Базовият клас clone( ) има полезна функционалност – прави фактическо по­би­тово копие на обекта на извлечения клас, съгласно с общия смисъл на опе­ра­цията клониране . Обаче тогава трябва да направите вашата клонираща опе­ра­ция public за да бъде достъпна. Така че двата ключови въпроса при кло­ни­ра­не­то са: практически винаги се вика super.clone( ) и се прави public.

Вероятно бихте искали да подтиснете clone( ) в по-нататък извлечени класове, ина­че вашият (сега public) clone( ) ще се използва, а това може да не е точното не­що (макар и, понеже Object.clone( ) прави копие на фактическия обект, боже и да бъде). Трикът с protected работи еднократно, първият път когато на­сле­ди­те от клас който не е клонируем и го направите клонируем. Във всички класове на­следени от вашия клас методът clone( ) е достъпен понеже е невъзможно в Java да се намали достъпът на метод при наследяване. Тоест, щом класът е кло­ни­руем, всичко извлечено от него е клонируемо, ако не използвате механизма (опи­сан по-късно) за “изключване” на клонирането.


Прилагане на интерфейса Cloneable


Има още едно нещо за да се осигури клонируемостта на обект: да се приложи Cloneable interface. Този interface е малко странен понеже е празен!

interface Cloneable {}

Причината за прилагане на този празен interface очевидно не е че въз­на­ме­ря­ва­те да правите ъпкаст към Cloneable и да викате някой от неговите методи. Из­полз­ването на interface тук е малко като “hack” понеже се използва не по пред­наз­начение. Прилагането на Cloneable interface работи като флаг, вграден в ти­па на класа.

Има две причини за съществуването на Cloneable interface. Първо, бихте могли да имате ъпкастнат манипулатор към базовия клас и да не може да определите обек­тът клонируем ли е или не. В този случай може да използвате ключовата ду­ма instanceof (описана в глава 11) за да намерите дали манипулаторът сочи към обект който може да бъде клониран:

if(myHandle instanceof Cloneable) // ...

Втората причина е допускането, че вероятно не бихте искали всички обекти да бъ­дат клонируеми. Така Object.clone( ) потвърждава дали класът прилага Cloneable интерфейс. Ако не, той изхвърля CloneNotSupportedException из­клю­чение. Изобщо, принудени сте да implement Cloneable като част от под­дръж­ката на клониране (в езика).


Успешно клониране


След като сте разбрали подробностите около използването на метода clone( ), вие сте в състояние лесно да правите класове, които да могат да се дублицират ло­кално:

//: c12:LocalCopy.java

// Creating local copies with 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 can't clone");

}

return o;



}

public String toString() {

return Integer.toString(i);

}

}


public class LocalCopy {

static MyObject g(MyObject v) {

// Passing a handle, modifies outside object:

v.i++;


return v;

}

static MyObject f(MyObject v) {



v = (MyObject)v.clone(); // Local copy

v.i++;


return v;

}

public static void main(String[] args) {



MyObject a = new MyObject(11);

MyObject b = g(a);

// Testing handle equivalence,

// not object equivalence:

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, тоест, да­ли прилага интерфейса Cloneable. Ако не, Object.clone( ) изхвърля CloneNotSupportedException за да индицира че не може да клонирате обекта. Та­ка че трябва да сложите вашия super.clone( ) в трай-блок, за да хванете из­клю­чение, което никога няма да се случи (понеже сте приложили Cloneable ин­тер­фейс).

В LocalCopy двата метода g( ) и f( ) демонстрират разликата между двата под­хо­да към подаването на аргументи. g( ) показва подаването по указател и мо­ди­фи­цирането на външния обект и връща указател към него, докато f( ) клонира ар­гумента, по този начин разделяйки го от оригиналния обект. После може да пра­ви каквото си иска, даже да върне манипулатор към този нов обект без да за­сегне външния изобщо. Забележете следния някакси любопитно изглеждащ опе­ратор:

v = (MyObject)v.clone();

Това е където е създадено локалното копие. За да се предотврати смущението от такъв оператор, напомняме че такъв странен програмен идиом е перфектен в Java понеже всичко което има име е фактически манипулатор. Така че ма­ни­пу­ла­торът v се използва за clone( ) на копие от това, към което е насочен, а се връ­ща манипулатор към базовия обект Object (понеже е дефинирано по този начин в Object.clone( )) което после трябва да се преобразува в правилния тип.

В main( ) се тества разликата между двата подхода в двата аргумента. Изходът е:

a == b


a = 12

b = 12


c != d

c = 47


d = 48

Важно е да се отбележи, че тестването за равенство в Java не гледа съ­дър­жа­ние­то на сравняваните обекти. Операторите == и != просто сравняват съ­дър­жа­ние­то на манипулаторите. Ако адресите в манипулаторите са едни и същи, ма­ни­пу­латорите сочат един и същ обект и затова са “равни.” Така че операциите фак­тически проверяват дали двата манипулатора са псевдоними на един и същи обект!


Ефектът от Object.clone( )


Какво всъщност става когато се вика Object.clone( ) та е толкова важно да се извика super.clone( ) когато подтискате clone( ) във ваш клас? Методът clone( ) в ко­реновия клас е отговорен за алокирането на паметта и побитовото копиране на обектите. Тоест той не просто отделя памет и копира Object – фактически се опре­деля точната дължина на копирания обект, отделя се памет и се копира. По­неже всичко това става чрез кода в метода clone( ) определен в кореновия клас (който няма представа какво ще се наследява от него), може да познаете че про­цесът използва RTTI за определяне на фактическия обект който ще се кло­ни­ра. По този начин методът clone( ) може да алокира точното количество па­мет и да извърши копирането.

Каквото и да правите, първо ще се вика super.clone( ) в процеса на кло­ни­ра­не­то. Това дава основа на цялата операция чрез създаването на точен дубликат. След тази точка може да направите каквото трябва за да завърши клонирането (кое­то може и да не трябва да е точно копиране - б.пр.).

За да определите какви трябва да бъдат допълнителните операции, трябва да знае­те какво точно прави Object.clone( ). В частност, дали клонира на­зна­че­ние­то на всички манипулатори? Следния пример проверява това:

//: c12:Snake.java

// Tests cloning to see if destination of

// handles are also cloned.


public class Snake implements Cloneable {

private Snake next;

private char c;

// Value of i == number of segments

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();

}

public String toString() {



String s = ":" + c;

if(next != null)

s += next.toString();

return s;

}

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(

"after s.increment, s2 = " + s2);

}

} ///:~



Snake (змията - б.пр.) е направена от сегменти, всеки от тип Snake. Така че това е едностранно свързан списък. Сегментите се създават рекурсивно, де­кре­мен­ти­райки първия аргумент на конструктора докато се достигне нула. За да се да­де на всеки сегмент уникален белег, вторият аргумент, който е char, се ин­кре­мен­тира за всяко рекурсивно викане на конструктора.

Методът increment( ) рекурсивно инкрементира всеки белег, така че може да ви­дите промените, а toString( ) рекурсивно извежда всеки белег. Изходът е:

s = :a:b:c:d:e

s2 = :a:b:c:d:e

after s.increment, s2 = :a:c:d:e:f

Това значи, че само първият сегмент е дублициран от Object.clone( ), така че се пра­ви плитко копие. Ако искате да се дублицира цялата змия – дълбоко ко­пи­ра­не – трябва да извършите допълнителни операции във вашия подтиснат clone( ).

Типично ще викате super.clone( ) във всеки клас извлечен от клонируем клас за да се осигури че всички операции от базовия клас (включително Object.clone( )) ще се извършат. Това е последвано от явно извикване на clone( ) за всеки ма­ни­пу­латор във вашия обект; иначе те ще станат псевдоними на тези в оригиналния обект. Това е аналогично на начина на викане на конструктори – конструктора на базовия клас пърно, после следващият извлечен конструктори така нататък до крайния извлечен конструктор. Разликата е че clone( ) не е конструктор така че нищо не го кара да става автоматично. Трябва да го правите сами.

Клониране на композиран обект


Като се опитвате да правите дълбоко копиране на композиран обект срещате про­блем. Трябва да предполагате че методът clone( ) в членовете-обекти на свой ред прави дълбоко коперане на техните манипулатори и така нататък. То­ва е само обещание. То ефективно значи че за да работи дълбокото копиране тряб­ва или да притежавате всичкия сорс на всичките класове или най-малкото да имате достатъчно сведения за всеки клас за да знаете дали той ще направи дъл­бокото копиране коректно.

Този пример показва какво ще трябва да направите та дълбоко да копирате ком­позиран обект:

//: c12:DeepCopy.java

// Cloning a composed object


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;



}

}
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();

}

// Must clone handles:



o.depth = (DepthReading)o.depth.clone();

o.temperature =

(TemperatureReading)o.temperature.clone();

return o; // Upcasts back to Object

}

}
public class DeepCopy {



public static void main(String[] args) {

OceanReading reading =

new OceanReading(33.9, 100.5);

// Now clone it:

OceanReading r =

(OceanReading)reading.clone();

}

} ///:~


DepthReading и TemperatureReading са доста подобни; и двата съдържат само при­митиви. Затова методът clone( ) може да бъде доста прост: вика super.clone( ) и връща резултата. Забележете че кодът на clone( ) за двата класа е идентичен.

OceanReading е композиран от DepthReading и TemperatureReading обекти та, за да стане дълбоко копиране, неговият clone( ) трябва да клонира ма­ни­пу­ла­то­рите вътре в OceanReading. За да стане това резултатът от super.clone( ) тряб­ва да бъде кастнат към OceanReading обект (та да може да имате достъп до манипулаторите depth и temperature).

Дълбоко копиране на ArrayList


Нека да се върнем към примера с ArrayList от по-рано в тази глава. Този път кла­сът Int2 е клонируем така че ArrayList може да бъде копиран дълбоко:

//: c12:AddingClone.java

// You must go through a few gyrations to

// add cloning to your own class.

import java.util.*;
class Int2 implements Cloneable {

private int i;

public Int2(int ii) { i = ii; }

public void increment() { 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 can't clone");

}

return o;



}

}
// Once it's cloneable, inheritance

// doesn't remove cloneability:

class Int3 extends Int2 {

private int j; // Automatically duplicated

public Int3(int i) { super(i); }

}
public class AddingClone {

public static void main(String[] args) {

Int2 x = new Int2(10);

Int2 x2 = (Int2)x.clone();

x2.increment();

System.out.println(

"x = " + x + ", x2 = " + x2);

// Anything inherited is also cloneable:

Int3 x3 = new Int3(7);

x3 = (Int3)x3.clone();


ArrayList v = new ArrayList();

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

v.add(new Int2(i));

System.out.println("v: " + v);

ArrayList v2 = (ArrayList)v.clone();

// Now clone each element:

for(int i = 0; i < v.size(); i++)

v2.set(i, ((Int2)v2.get(i)).clone());

// Increment all v2's elements:

for(Iterator e = v2.iterator();

e.hasNext(); )

((Int2)e.next()).increment();

// See if it changed v's elements:

System.out.println("v: " + v);

System.out.println("v2: " + v2);

}

} ///:~



Int3 е наследен от Int2 и нов примитивен член int j е добавен. Може да си по­мис­лите че ще трябва да подтиснете clone( ) пак за да осигурите копирането на j, но не е така. Когато clone( ) на Int2 се вика като clone( ) на Int3, той вика Object.clone( ), който определя че работи с Int3 и дублицира всичките битове в Int3. Доколкото не добавяте манипулатори които трябва да се клонират, едно из­викване на Object.clone( ) прави цялото необходимо дублициране, без зна­че­ние колко дълбоко в йерархията е дефиниран clone( ).

Може да видите какво е необходимо за дълбоко копиране на ArrayList: След ка­то е клониран ArrayList трябва да преминете по всички сочени от ArrayList обек­ти и да ги клонирате. Нещо подобно трябва да направите за дълбоко ко­пи­ра­не на HashMap.

Останалата част от примера показва че е станало клонирането понеже, веднъж като е клониран обектът, може да го променяте и оригинарът да остане не­за­сег­нат.

Дълбоко копиране чрез сериализация


Със сериализацията на обекти в Java 1.1 (въведена в глава 10) може да сте за­бе­ля­зали че, сериализиран и после десериализиран обект е, фактически, клониран.

Така че защо да не се използва сериализацията за постигане на дълбоко ко­пи­ра­не? Ето пример който сравнява двата подхода чрез измерване на времето за из­пъл­нение:

//: c12: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 can't clone");

}

return o;



}

}
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 can't clone");

}

// Clone the field, too:



o.o3 = (Thing3)o3.clone();

return o;

}

}
public class Compete {



static final int SIZE = 5000;

public static void main(String[] args) {

Thing2[] a = new Thing2[SIZE];

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

a[i] = new Thing2();

Thing4[] b = new Thing4[SIZE];

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

b[i] = new Thing4();

try {

long t1 = System.currentTimeMillis();



ByteArrayOutputStream buf =

new ByteArrayOutputStream();

ObjectOutputStream o =

new ObjectOutputStream(buf);

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

o.writeObject(a[i]);

// Now get copies:

ObjectInputStream in =

new ObjectInputStream(

new ByteArrayInputStream(

buf.toByteArray()));

Thing2[] c = new Thing2[SIZE];

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

c[i] = (Thing2)in.readObject();

long t2 = System.currentTimeMillis();

System.out.println(

"Duplication via serialization: " +

(t2 - t1) + " Milliseconds");

// Now try cloning:

t1 = System.currentTimeMillis();

Thing4[] d = new Thing4[SIZE];

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

d[i] = (Thing4)b[i].clone();

t2 = System.currentTimeMillis();

System.out.println(

"Duplication via cloning: " +

(t2 - t1) + " Milliseconds");

} catch(Exception e) {

e.printStackTrace();

}

}



} ///:~

Thing2 и Thing4 съдържат обекти-членове, така че е налице някакво дълбоко ко­пиране. Интересно е да се отбележи че докато Serializable класовете са лесни за устройване, има много повече работа за да бъдат дублицирани. Клонирането до­карва много работа за уреждането на класа, но фактическото дублициране е срав­нително просто. Резултатите действително говорят. Ето изход от три раз­лич­ни пускания:

Duplication via serialization: 3400 Milliseconds

Duplication via cloning: 110 Milliseconds
Duplication via serialization: 3410 Milliseconds

Duplication via cloning: 110 Milliseconds


Duplication via serialization: 3520 Milliseconds

Duplication via cloning: 110 Milliseconds

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

Добавяне на клонируемост надолу по йерархията


Ако създавате нов клас неговият базов клас е по подразбиране Object, което оз­на­чава неклонируемост по подразбиране (както ще видите в следващата сек­ция). Докато не добавите клонируемост явно няма и да я имате. Но може да я до­бавите в някакъв слой и от там надолу вече ще я има, както това:

//: c12:HorrorFlick.java

// You can insert Cloneability at any

// level of inheritance.

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) {

// this should never happen:

// It's Cloneable already!

throw new InternalError();

}

}



}

class MadScientist extends Scientist {}


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(); // Compile error

// h = (Hero)h.clone(); // Compile error

s = (Scientist)s.clone();

m = (MadScientist)m.clone();

}

} ///:~


Преди да бъде добавена клонируемост компилаторът ви спираше при опити да кло­нирате нещо. Когато клонируемост беше добавена за Scientist, Scientist и всич­ките му наследници са клонируеми.

Защо този странен дизайн?


Ако всичко това изглежда странна схема, така е, защото тя наистина е такава. Мо­же да се чудите защо е направено по този начин. Какво се крие зад този ди­зайн? Това което следва не е доказана прецизна история – може би защото тър­го­вията около Java го показва като перфектно проектиран език – но тя върви дъл­го по пътя към обасняването защо нещата са такива, каквито са се оказали.

Оригинално Java беше замислена като език за управление на хардуерни устрой­ства и определено не с Internet в замисъла. В език с общо предназначение като то­зи има смисъл да се даде възможност програмистът да клонира всеки обект. Та­ка clone( ) беше сложен в кореновия клас Object, но беше public метод така че всеки обект можеше да се клонира. Това изглеждаше като най-разумен под­ход и, най-после, как можеше да навреди?

Добре, когато Java се виждаше вече като чудесен език за програмиране в/за Inter­net, нещата се промениха. Внезапно изскочиха изискванията за сигурност и, разбира се, тяхната връзка с използването на обекти, и липсата на желание всич­ки обекти да могат да бъдат клонирани от когото и да е. Така че това което се вижда са множеството кръпки по първоначалната праволинейна и проста схе­ма: clone( ) е сега protected в Object. Трябва да го подтиснете и да implement Cloneable и да работите с изключенията.

Нищо не е че трябва да използвате Cloneable интерфейса самоако се каните да викате clone( ) на Object понеже той проверява по време на изпълнение дали обек­тът е Cloneable. Но за пълнота (и понеже Cloneable е празен така или ина­че) ще го прилагате.





Сподели с приятели:
1   ...   65   66   67   68   69   70   71   72   73




©obuch.info 2024
отнасят до администрацията

    Начална страница