Да припомним: всички аргументи в Java се подават чрез подаване на манипулатори. Тоест, когато давате “обект,” в действителност давате само манипулатор към обект който живее извън метода, така че ако направите промени с него манипулатор, променяте външния обект. Освен това:
-
Автоматично се създават псевдоними при предаването на аргументите.
-
Няма локални обекти, а само локални манипулатори.
-
Манипулаторите имат обхвати, обектите нямат.
-
В Java не стои въпросът с времето на живот на обектите.
-
Няма поддръжка от езика (напр. като const) за предотвратяване модифицирането на обект (за предпазване от отрицателните ефести на псевдонимите).
Ако само четете информация от обект и не го променяте, даването на манипулатор е най-ефикасната форма на подаване на аргументи. Това е добре; най-ефикасният начин да се направят нещата е и начин по подразбиране. Обаче понякога става нужда да се третират обекти както ако бяха “локални” така че промените да засягат само локалното копие и да не засягат външния обект. Много програмни езици поддържат възможността за правене на локално копие на външния обект вътре в метода.1 Java не прави така, но дава възможност да се постигне такъв ефект.
Предаване по стойност
Това извежда на преден план терминологичен въпрос, който винаги изглежда добре за аргумент. Терминът е “предаване по стойност,” а значението зависи от това как схващате работата на програмата. Общото значение е че вземате локално копие от това което подавате, но реалният въпрос е какво мислите за това, което подавате. Като дойде ред на значението “предаване по стойност,” има две доволно различни становища:
-
Java дава всичко по стойност. Когато давате примитиви на метод, имате различно копие на примитива. Когато давате манипулатор на метод, имате копие на манипулатора. Ergo, всичко се предава по стойност. Разбира се, предположението е че винаги мислите (и правите) да се предават манипулатори, но изглежда каточели дизайнът на Java е изминал голям път нанатък позволявайки ви (товечето време) да работите с манипулатор. Тоест, изглежда да мислите за манипулатора като за “обекта,” понеже всяко извикване на метод работи с обекта.
-
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 се виждаше вече като чудесен език за програмиране в/за Internet, нещата се промениха. Внезапно изскочиха изискванията за сигурност и, разбира се, тяхната връзка с използването на обекти, и липсата на желание всички обекти да могат да бъдат клонирани от когото и да е. Така че това което се вижда са множеството кръпки по първоначалната праволинейна и проста схема: clone( ) е сега protected в Object. Трябва да го подтиснете и да implement Cloneable и да работите с изключенията.
Нищо не е че трябва да използвате Cloneable интерфейса самоако се каните да викате clone( ) на Object понеже той проверява по време на изпълнение дали обектът е Cloneable. Но за пълнота (и понеже Cloneable е празен така или иначе) ще го прилагате.
Сподели с приятели: |