Programmēšana

Java padoms 76: alternatīva dziļās kopēšanas tehnikai

Objekta dziļas kopijas ieviešana var būt mācīšanās pieredze - jūs uzzināt, ka nevēlaties to darīt! Ja attiecīgais objekts attiecas uz citiem sarežģītiem objektiem, kas savukārt attiecas uz citiem, tad šis uzdevums patiešām var būt biedējošs. Tradicionāli katra objekta klase ir atsevišķi jāpārbauda un jārediģē, lai ieviestu Klonējams saskarni un ignorēt tās klons () metodi, lai izveidotu dziļu sevis, kā arī tā saturošo objektu kopiju. Šajā rakstā ir aprakstīta vienkārša tehnika, kas jāizmanto šīs laikietilpīgās parastās dziļās kopijas vietā.

Dziļās kopijas jēdziens

Lai saprastu, kas a dziļa kopija ir, vispirms apskatīsim seklas kopēšanas jēdzienu.

Iepriekšējā JavaWorld raksts "Kā izvairīties no slazdiem un pareizi ignorēt metodes no java.lang.Object", Marks Roulo paskaidro, kā klonēt objektus, kā arī to, kā panākt seklu kopēšanu, nevis dziļu kopēšanu. Šeit īsumā apkopojot, sekla kopija rodas, kad objekts tiek kopēts bez tā saturošajiem objektiem. Lai ilustrētu, 1. attēlā parādīts objekts, obj1, kas satur divus objektus, saturObj1 un saturObj2.

Ja tiek veikta sekla kopija obj1, tad tas tiek kopēts, bet tajā esošie objekti nav, kā parādīts 2. attēlā.

Dziļa kopija rodas, ja objekts tiek kopēts kopā ar objektiem, uz kuriem tas attiecas. 3. attēlā parādīts obj1 pēc tam, kad tai ir veikta dziļa kopija. Ne tikai ir obj1 ir nokopēti, bet arī tajā esošie objekti ir nokopēti.

Ja kāds no šiem ietvertajiem objektiem satur objektus, tad dziļā kopijā tiek kopēti arī šie objekti utt., Līdz viss grafiks tiek šķērsots un nokopēts. Katrs objekts ir atbildīgs par sevis klonēšanu, izmantojot to klons () metodi. Noklusējums klons () metode, mantota no Objekts, veic objekta seklu kopiju. Lai iegūtu dziļu kopiju, jāpievieno papildu loģika, kas skaidri sauc visus esošos objektus klons () metodes, kuras savukārt sauc par to saturošajiem objektiem " klons () metodes utt. Lai to izdarītu pareizi, var būt grūti un laikietilpīgi, un tas reti ir jautri. Padarīt lietas vēl sarežģītākas, ja objektu nevar tieši modificēt un tā klons () metode rada seklu kopiju, tad klase jāpaplašina, klons () metode ir ignorēta, un šī jaunā klase tiek izmantota vecās klases vietā. (Piemēram, Vector nesatur dziļai kopijai nepieciešamo loģiku.) Un, ja vēlaties uzrakstīt kodu, kas līdz izpildes laikam atliek jautājumu par to, vai veidot objektu dziļu vai seklu kopiju, jūs nonākat vēl sarežģītākā situācijā. Šajā gadījumā katram objektam ir jābūt divām kopēšanas funkcijām: viena dziļai kopijai un sekla. Visbeidzot, pat ja dziļi kopējamais objekts satur vairākas atsauces uz citu objektu, pēdējais objekts tomēr jākopē tikai vienu reizi. Tas novērš objektu pavairošanu un novērš īpašo situāciju, kad apļveida atsauce rada bezgalīgu kopiju loku.

Serializācija

1998. gada janvārī JavaWorld uzsāka savu Java pupiņas Marka Džonsona sleja ar rakstu par serializāciju "Dariet to" Nescafé "veidā - ar liofilizētiem JavaBeans." Rezumējot, serializācija ir spēja pārvērst objektu grafiku (ieskaitot atsevišķa objekta deģenerēto gadījumu) par baitu masīvu, ko var atkal pārvērst par līdzvērtīgu objektu grafiku. Tiek teikts, ka objekts ir seriālizējams, ja to īsteno kāds no tā priekštečiem java.io.Serializējams vai java.io.Externalizable. Serializējamo objektu var serializēt, nododot to writeObject () metode ObjectOutputStream objekts. Tādējādi tiek izrakstīti objekta primitīvie datu tipi, masīvi, virknes un citas objekta atsauces. The writeObject () metode tiek izsaukta arī uz minētajiem objektiem, lai tos seriālizētu. Turklāt katram no šiem objektiem ir viņu atsauces un objekti sērijveidā; šis process turpinās un turpinās, līdz viss grafiks tiek šķērsots un seriālizēts. Vai tas izklausās pazīstami? Šo funkcionalitāti var izmantot, lai iegūtu dziļu kopiju.

Dziļa kopija, izmantojot serializāciju

Dziļās kopijas izgatavošanas, izmantojot sērijveidošanu, darbības ir šādas:

  1. Pārliecinieties, vai visas objekta diagrammas klases ir seriālizējamas.

  2. Izveidojiet ievades un izvades straumes.

  3. Izmantojiet ievades un izvades straumes, lai izveidotu objekta ievades un objekta izvades straumes.

  4. Nododiet objektu, kuru vēlaties kopēt, objekta izvades straumē.

  5. Izlasiet jauno objektu no objekta ievades straumes un atgrieziet to atpakaļ nosūtītā objekta klasē.

Esmu uzrakstījis klasi ar nosaukumu ObjectCloner kas īsteno divus līdz piecus soļus. Līnija ar atzīmi "A" izveido a ByteArrayOutputStream ko izmanto, lai izveidotu ObjectOutputStream līnijā B. Maģija tiek veikta C līnijā. The writeObject () metode rekursīvi šķērso objekta grafiku, ģenerē jaunu objektu baita formā un nosūta to ByteArrayOutputStream. D līnija nodrošina, ka viss objekts ir nosūtīts. Pēc tam E līnijas kods izveido a ByteArrayInputStream un aizpilda to ar ByteArrayOutputStream. F līnija momentāno an ObjectInputStream izmantojot ByteArrayInputStream izveidots E rindā, un objekts tiek deserializēts un atgriezts pie izsaukšanas metodes līnijā G. Šeit ir kods:

importēt java.io. *; importēt java.util. *; importēt java.awt. *; public class ObjectCloner {// lai neviens nevarētu nejauši izveidot ObjectCloner objektu private ObjectCloner () {} // atgriež objekta statisko publisko objekta dziļu kopiju Object deepCopy (Object oldObj) izmet izņēmumu {ObjectOutputStream oos = null; ObjectInputStream ois = nulle; mēģiniet {ByteArrayOutputStream bos = new ByteArrayOutputStream (); // A oos = new ObjectOutputStream (bos); // B // serializēt un nodot objektu oos.writeObject (oldObj); // C oos.flush (); // D ByteArrayInputStream bin = new ByteArrayInputStream (bos.toByteArray ()); // E ois = new ObjectInputStream (bin); // F // atgriež jauno objektu return ois.readObject (); // G} catch (izņēmums e) {System.out.println ("Exception in ObjectCloner =" + e); metiens (e); } beidzot {oos.close (); ois.slēgt (); }}} 

Visi izstrādātāji, kuriem ir piekļuve ObjectCloner pirms šī koda palaišanas ir jāpārliecinās, vai visas objekta grafika klases ir seriālizējamas. Vairumā gadījumu tas jau bija jādara; ja nē, to vajadzētu būt samērā viegli izdarīt ar piekļuvi pirmkodam. Lielākā daļa JDK nodarbību ir seriālizējamas; tikai tie, kas ir atkarīgi no platformas, piemēram, FileDescriptor, nav. Arī visas klases, kuras iegūstat no trešās puses piegādātāja un kuras ir saderīgas ar JavaBean, pēc definīcijas ir seriālizējamas. Protams, ja jūs paplašināt klasi, kas ir seriālizējama, tad jaunā klase ir arī seriālizējama. Tā kā visas šīs sērijveidojamās klases peld apkārt, iespējams, ka vienīgās, kas jums var būt nepieciešamas serializēt, ir jūsu pašu, un tas ir kūkas gabals, salīdzinot ar katras klases iziešanu un pārrakstīšanu klons () veikt dziļu kopiju.

Vienkāršs veids, kā uzzināt, vai objekta diagrammā ir kādas nerserializējamas klases, ir pieņemt, ka tās visas ir seriālizējamas un palaistas ObjectCloner's deepCopy () metodi. Ja ir objekts, kura klasi nevar serizēt, tad a java.io.NotSerializableException tiks izmests, pastāstot, kura klase izraisīja problēmu.

Ātras ieviešanas piemērs ir parādīts zemāk. Tas rada vienkāršu objektu, v1, kas ir a Vector kas satur a Punkts. Pēc tam šis objekts tiek izdrukāts, lai parādītu tā saturu. Sākotnējais objekts, v1, pēc tam tiek kopēts uz jaunu objektu, vJauns, kas tiek drukāts, lai parādītu, ka tajā ir tāda pati vērtība kā v1. Tālāk saturs v1 tiek mainīti, un visbeidzot abi v1 un vJauns tiek drukāti, lai varētu salīdzināt to vērtības.

importēt java.util. *; importēt java.awt. *; public class Driver1 {static public void main (String [] args) {try {// iegūt metodi no komandrindas String meth; ja ((args.length == 1) && ((args [0]. vienāds ("dziļš")) || (args [0]. vienāds ("sekls")))) {meth = args [0]; } else {System.out.println ("Lietojums: java Driver1 [dziļš, sekls]"); atgriešanās; } // izveidot oriģinālu objektu Vector v1 = new Vector (); Punkts p1 = jauns punkts (1,1); v1.addElement (p1); // redzēt, kas tas ir System.out.println ("Original =" + v1); Vektors vNew = null; ja (meth.equals ("dziļa")) {// dziļa kopija vNew = (Vector) (ObjectCloner.deepCopy (v1)); // A} else if (meth.equals ("sekla")) {// sekla kopija vNew = (Vector) v1.clone (); // B} // pārbaudiet, vai tā ir tā pati System.out.println ("New =" + vNew); // mainīt sākotnējā objekta saturu p1.x = 2; p1.y = 2; // redzēt, kas tagad katrā ir System.out.println ("Original =" + v1); System.out.println ("Jauns =" ​​+ vNew); } catch (izņēmums e) {System.out.println ("Exception in main =" + e); }}} 

Lai izsauktu dziļo kopiju (A rinda), izpildiet java.exe draiveris1 dziļi. Kad dziļā kopija darbojas, mēs iegūstam šādu izdruku:

Oriģināls = [java.awt.Point [x = 1, y = 1]] Jauns = [java.awt.Point [x = 1, y = 1]] Oriģināls = [java.awt.Point [x = 2, y = 2]] Jauns = [java.awt.Point [x = 1, y = 1]] 

Tas parāda, ka tad, kad oriģināls Punkts, 1. lpp, tika mainīts, jaunais Punkts izveidots dziļās kopijas rezultātā, tas netika ietekmēts, jo tika kopēts viss grafiks. Salīdzinājumam izmantojiet seklo kopiju (B rinda), izpildot java.exe Driver1 sekla. Kad sekla kopija darbojas, mēs iegūstam šādu izdruku:

Oriģināls = [java.awt.Point [x = 1, y = 1]] Jauns = [java.awt.Point [x = 1, y = 1]] Oriģināls = [java.awt.Point [x = 2, y = 2]] Jauns = [java.awt.Point [x = 2, y = 2]] 

Tas parāda, ka tad, kad oriģināls Punkts tika mainīts, jaunais Punkts tika mainīts arī. Tas ir saistīts ar faktu, ka sekla kopija veido tikai atsauces, nevis to objektu kopijas, uz kurām tās attiecas. Šis ir ļoti vienkāršs piemērs, bet, manuprāt, tas ilustrē, hm, punktu.

Īstenošanas jautājumi

Tagad, kad esmu sludinājis par visiem dziļās kopēšanas tikumiem, izmantojot sērijveidošanu, apskatīsim dažas lietas, no kurām jāpievērš uzmanība.

Pirmais problemātiskais gadījums ir klase, kuru nevar serizēt un kuru nevar rediģēt. Tas varētu notikt, piemēram, ja izmantojat trešās puses klasi, kurai nav avota koda. Šajā gadījumā jūs varat to pagarināt, lai paplašinātā klase tiktu ieviesta Serializējams, pievienojiet visus (vai visus) nepieciešamos konstruktorus, kuri vienkārši izsauc saistīto superkonstruktoru, un izmantojiet šo jauno klasi visur, kur veicāt veco (šeit ir piemērs tam).

Tas var šķist daudz darba, bet, ja vien sākotnējā klase nav klons () metode ievieš dziļu kopiju, jūs darīsit kaut ko līdzīgu, lai to ignorētu klons () metode vienalga.

Nākamais jautājums ir šīs tehnikas izpildlaika ātrums. Kā jūs varat iedomāties, kontaktligzdas izveidošana, objekta sērijveidošana, izlaišana caur ligzdu un deserializēšana ir lēna, salīdzinot ar esošo objektu izsaukšanas metodēm. Šeit ir daži avota kodi, kas mēra laiku, kas nepieciešams, lai veiktu abas dziļās kopēšanas metodes (izmantojot sērijveida un klons ()) dažās vienkāršās klasēs un izveido etalonus dažādiem atkārtojumu skaitiem. Rezultāti, kas parādīti milisekundēs, ir parādīti zemāk esošajā tabulā:

Milisekundes, lai dziļi kopētu vienkāršu klases grafiku n reizes
Procedūra \ atkārtojumi (n)100010000100000
klons10101791
serializācija183211346107725

Kā redzat, veiktspējā ir liela atšķirība. Ja jūsu rakstītais kods ir kritisks veiktspējai, jums, iespējams, nāksies iekost aizzīmi un ar roku kodēt dziļu kopiju. Ja jums ir sarežģīts grafiks un jums tiek dota viena diena, lai ieviestu dziļu kopiju, un kods tiks palaists kā pakešdarbs svētdienas vienā rītā, tad šī metode dod jums vēl vienu iespēju, kas jāapsver.

Cits jautājums ir par klases gadījumu, kura objektu gadījumi virtuālajā mašīnā ir jākontrolē. Tas ir īpašs Singletona modeļa gadījums, kurā klasei ir tikai viens objekts VM. Kā jau tika apspriests iepriekš, sērijveidojot objektu, jūs izveidojat pilnīgi jaunu objektu, kas nebūs unikāls. Lai apietu šo noklusējuma uzvedību, varat izmantot readResolve () metode, lai piespiestu straumi atgriezt piemērotu objektu, nevis to, kas tika seriālizēts. Šajā īpaši Gadījumā atbilstošais objekts ir tas pats, kas tika sērijveidots. Šeit ir piemērs, kā ieviest readResolve () metodi. Jūs varat uzzināt vairāk par readResolve () kā arī citu informāciju par seriālizāciju Sun vietnē, kas veltīta Java objektu serializācijas specifikācijai (skat. resursus).

Viena pēdējā uzmanība jāpievērš pārejošu mainīgo gadījumam. Ja mainīgais ir atzīmēts kā pārejošs, tas netiks sertificēts, un tāpēc tas un tā diagramma netiks kopēti. Tā vietā pārejošā mainīgā vērtība jaunajā objektā būs Java valodas noklusējuma vērtības (null, false un zero). Nebūs kompilēšanas laika vai izpildlaika kļūdu, kā rezultātā var rasties grūti atkļūdota darbība. Tikai apzinoties to, var ietaupīt daudz laika.

Dziļās kopēšanas paņēmiens var ietaupīt programmētāju daudz darba stundu, bet var izraisīt iepriekš aprakstītās problēmas. Kā vienmēr, pirms izlemjat, kuru metodi izmantot, noteikti nosveriet priekšrocības un trūkumus.

Secinājums

Sarežģīta objekta diagrammas dziļas kopijas ieviešana var būt grūts uzdevums. Iepriekš parādītais paņēmiens ir vienkārša alternatīva tradicionālajai pārrakstīšanas procedūrai klons () metode katram grafika objektam.

Deivs Millers ir vecākais arhitekts konsultāciju firmā Javelin Technology, kur viņš strādā ar Java un interneta lietojumprogrammām. Viņš ir strādājis tādos uzņēmumos kā Hughes, IBM, Nortel un MCIWorldcom objektorientētos projektos un pēdējos trīs gadus strādājis tikai ar Java.

Uzziniet vairāk par šo tēmu

  • Sun Java vietnē ir sadaļa, kas veltīta Java objektu serializācijas specifikācijai

    //www.javasoft.com/products/jdk/1.2/docs/guide/serialization/spec/serialTOC.doc.html

Šo stāstu “Java Tip 76: Alternative to deep copy technika” sākotnēji publicēja JavaWorld.

$config[zx-auto] not found$config[zx-overlay] not found