Programiranje

V pogodbi je! Različice objektov za JavaBeans

V zadnjih dveh mesecih smo se poglobili v to, kako serializirati predmete v Javi. (Glejte "Serializacija in specifikacija JavaBeans" in "Naredite to na način` Nescafé '- z liofiliziranim JavaBeans. ") V tem mesečnem članku se domneva, da ste te članke že prebrali ali pa razumete teme, ki jih pokrivajo. Morali bi razumeti, kaj je serializacija in kako uporabljati Serializabilno in kako uporabljati java.io.ObjectOutputStream in java.io.ObjectInputStream razredih.

Zakaj potrebujete različico

Kaj računalnik počne, določa njegova programska oprema in programsko opremo je zelo enostavno spremeniti. Ta prilagodljivost, ki se običajno šteje za sredstvo, ima svoje obveznosti. Včasih se zdi, da programska oprema je preveč enostavno spremeniti. Nedvomno ste naleteli na vsaj eno od naslednjih situacij:

  • Datoteka dokumenta, ki ste jo prejeli po e-pošti, se v vašem urejevalniku besedil ne bo pravilno brala, ker je vaša starejša različica z nezdružljivo obliko datoteke

  • Spletna stran deluje različno v različnih brskalnikih, ker različne različice brskalnikov podpirajo različne nabore funkcij

  • Aplikacija se ne bo zagnala, ker imate napačno različico določene knjižnice

  • Vaš C ++ se ne bo prevedel, ker sta glava in izvorne datoteke nezdružljive različice

Vse te situacije povzročajo nezdružljive različice programske opreme in / ali podatki, s katerimi programska oprema manipulira. Tako kot zgradbe, osebne filozofije in struge se tudi programi nenehno spreminjajo kot odziv na spreminjajoče se okoliščine. (Če menite, da se stavbe ne spreminjajo, preberite izjemno knjigo Stewarta Branda Kako se zgradbe učijo, razprava o tem, kako se strukture spreminjajo skozi čas. Za več informacij glejte Vire.) Brez strukture za nadzor in upravljanje te spremembe se kateri koli programski sistem katere koli uporabne velikosti sčasoma spremeni v kaos. Cilj v programski opremi različice je zagotoviti, da različica programske opreme, ki jo trenutno uporabljate, daje pravilne rezultate, ko naleti na podatke, ki jih ustvarijo druge različice same.

Ta mesec bomo razpravljali o delovanju različic razreda Java, da bomo lahko zagotovili nadzor nad različicami JavaBeans. Struktura različic za razrede Java vam omogoča, da mehanizmu za serializacijo označite, ali je določena različica razreda Java berljiva z določenim podatkovnim tokom (to je serializiran objekt). Govorili bomo o "združljivih" in "nezdružljivih" spremembah razredov ter o tem, zakaj te spremembe vplivajo na različice. Preučili bomo cilje strukture različic in kako java.io paket ustreza tem ciljem. In naučili se bomo v našo kodo vključiti zaščitne ukrepe, s katerimi bomo zagotovili, da bodo podatki, ko beremo tokove predmetov različnih različic, vedno skladni po branju predmeta.

Odpor različice

V programski opremi obstajajo različne vrste težav z različicami, ki se nanašajo na združljivost med kosi podatkov in / ali izvedljivo kodo:

  • Različne različice iste programske opreme lahko med seboj ne obdelujejo formatov za shranjevanje podatkov

  • Programi, ki med izvajanjem naložijo izvršljivo kodo, morajo biti sposobni prepoznati pravilno različico predmeta programske opreme, naložljive knjižnice ali datoteke predmeta, da lahko opravijo delo

  • Metode in polja razreda morajo ohranjati enak pomen, kot se razvija razred, ali pa se obstoječi programi lahko zlomijo tam, kjer se uporabljajo te metode in polja

  • Izvorna koda, datoteke z glavo, dokumentacija in skripti za gradnjo morajo biti usklajeni v okolju za gradnjo programske opreme, da se zagotovi, da so binarne datoteke zgrajene iz pravilnih različic izvornih datotek.

Ta članek o različicah objektov Java obravnava samo prve tri, to je nadzor nad različicami binarnih objektov in njihovo semantiko v izvajalnem okolju. (Za različico izvorne kode je na voljo ogromno programske opreme, vendar tega tukaj ne pokrivamo.)

Pomembno je vedeti, da zaporedni tokovi predmetov Java ne vsebujejo bajt kod. Vsebujejo samo informacije, potrebne za rekonstrukcijo predmeta ob predpostavki za izdelavo predmeta imate na voljo datoteke razredov. Kaj pa se zgodi, če so datoteke razredov dveh navideznih strojev Java (JVM) (zapisovalnika in bralnika) različnih različic? Kako naj vemo, ali so združljivi?

Definicijo razreda lahko razumemo kot "pogodbo" med razredom in kodo, ki kliče razred. Ta pogodba vključuje razred API (vmesnik za programiranje aplikacij). Spreminjanje API-ja je enakovredno spreminjanju pogodbe. (Kot bomo videli, lahko druge spremembe razreda pomenijo tudi spremembe pogodbe.) Ko se razred razvija, je pomembno ohraniti vedenje prejšnjih različic razreda, da ne bi prekinili programske opreme na mestih, ki so bila odvisna od dano vedenje.

Primer spremembe različice

Predstavljajte si, da ste imenovali metodo getItemCount () v razredu, kar je pomenilo dobite skupno število elementov, ki jih vsebuje ta predmet, in ta metoda je bila uporabljena na ducat mestih v celotnem sistemu. Potem si predstavljajte, da se boste pozneje spremenili getItemCount () pomeniti dobite največje število predmetov, ki jih ima ta predmet kdaj vsebovano. Vaša programska oprema se bo najverjetneje pokvarila v večini krajev, kjer je bila uporabljena ta metoda, ker nenadoma metoda sporoča različne informacije. V bistvu ste prekinili pogodbo; zato vam prav služi, da ima vaš program zdaj napake.

Ni načina, da popolnoma ne dovolimo sprememb, da bi popolnoma avtomatizirali zaznavanje tovrstnih sprememb, ker se to zgodi na ravni, kot je program pomeni, ne samo na ravni izražanja tega pomena. (Če si omislite način, kako to narediti enostavno in na splošno, boste bogatejši od Billa.) V odsotnosti popolne, splošne in avtomatizirane rešitve tega problema, kaj lahko da bi se izognili vstopu v vročo vodo, ko spremenimo razred (kar seveda moramo)?

Na to vprašanje je najlažje odgovoriti, če se razred spremeni nasploh, ne bi smeli "zaupati", da ohrani pogodbo. Navsezadnje je programer morda kaj storil za razred in kdo ve, ali razred še vedno deluje tako, kot je oglaševan? To rešuje težavo z različicami, vendar je nepraktična rešitev, ker je preveč omejujoča. Če je razred spremenjen za izboljšanje zmogljivosti, recimo, ni razloga, da bi zavrnili uporabo nove različice razreda preprosto zato, ker se ne ujema s staro. Razred se lahko spremeni brez sprememb pogodbe.

Po drugi strani pa nekatere spremembe razredov praktično zagotavljajo kršitev pogodbe: na primer brisanje polja. Če izbrišete polje iz razreda, boste še vedno lahko brali tokove, napisane v prejšnjih različicah, ker lahko bralec vedno prezre vrednost tega polja. Toda pomislite, kaj se zgodi, ko napišete tok, ki naj bi ga prebrale prejšnje različice razreda. Vrednost za to polje v toku ne bo in starejša različica bo temu polju, ko bere tok, dodelila (morda logično neskladna) privzeto vrednost. Voilà!: Imate pokvarjen razred.

Združljive in nezdružljive spremembe

Trik pri upravljanju združljivosti različic objektov je ugotoviti, katere vrste sprememb lahko povzročijo nezdružljivosti med različicami in katere ne, in te primere obravnavati drugače. V jeziku Java se imenujejo spremembe, ki ne povzročajo težav z združljivostjo združljiv spremembe; tisti, ki se lahko imenujejo nezdružljivo spremembe.

Oblikovalci mehanizma serializacije za Javo so imeli pri ustvarjanju sistema v mislih naslednje cilje:

  1. Določiti način, kako lahko novejša različica razreda bere in piše tokove, ki jih prejšnja različica razreda lahko tudi "razume" in pravilno uporablja

  2. Zagotoviti privzeti mehanizem, ki serializira predmete z dobro zmogljivostjo in primerno velikostjo. To je mehanizem serializacije smo že razpravljali v prejšnjih stolpcih JavaBeans, omenjenih na začetku tega članka

  3. Da bi zmanjšali delo, povezano z različicami, na razredih, ki ne potrebujejo različic. V idealnem primeru je treba informacije o različicah razredu dodati le, če so dodane nove različice

  4. Formatiranje toka predmeta tako, da jih lahko preskočite, ne da bi naložili datoteko razreda predmeta. Ta zmožnost omogoča odjemalskemu objektu, da prečka tok predmeta, ki vsebuje predmete, ki jih ne razume

Poglejmo, kako mehanizem serializacije obravnava te cilje glede na zgoraj opisane razmere.

Spravljive razlike

Nekatere spremembe v datoteki razreda so odvisne od tega, da ne spremenite pogodbe med razredom in ne glede na to, kako jo lahko imenujejo drugi razredi. Kot smo že omenili, se v dokumentaciji Java imenujejo združljive spremembe. V datoteko razreda lahko vnesete poljubno število združljivih sprememb, ne da bi spremenili pogodbo. Z drugimi besedami, dve različici razreda, ki se razlikujeta le po združljivih spremembah, sta združljivi razredi: Novejša različica bo še naprej brala in zapisovala tokove predmetov, ki so združljivi s prejšnjimi različicami.

Predavanja java.io.ObjectInputStream in java.io.ObjectOutputStream ne zaupam vam. Zasnovani so tako, da so privzeto izjemno sumljivi glede kakršnih koli sprememb vmesnika datoteke razreda v svetu - kar pomeni, karkoli je vidno kateremu koli drugemu razredu, ki lahko uporablja razred: podpisi javnih metod in vmesnikov ter tipi in modifikatorji javnih polj. Pravzaprav so tako paranoični, da komaj kaj spremenite v zvezi s predavanjem, ne da bi povzročili java.io.ObjectInputStream zavrniti nalaganje toka, ki ga je napisala prejšnja različica vašega razreda.

Oglejmo si primer. nezdružljivosti razreda in nato rešite nastali problem. Recimo, da imate poklican predmet InventoryItem, ki ohranja številke delov in količino tega dela, ki je na voljo v skladišču. Preprosta oblika tega predmeta kot JavaBean bi lahko izgledala nekako takole:

001 002 uvoz java.beans. *; 003 uvoz java.io. *; 004 uvoz Tiskanje; 005 006 // 007 // Različica 1: preprosto shranite količino pri roki in številko dela 008 // 009 010 javni razred InventoryItem implementira Serializable, Printable {011 012 013 014 015 016 // polja 017 protected int iQuantityOnHand_; 018 zaščiten niz sPartNo_; 019 020 public InventoryItem () 021 {022 iQuantityOnHand_ = -1; 023 sPartNo_ = ""; 024} 025 026 public InventoryItem (String _sPartNo, int _iQuantityOnHand) 027 {028 setQuantityOnHand (_iQuantityOnHand); 029 setPartNo (_sPartNo); 030} 031 032 public int getQuantityOnHand () 033 {034 return iQuantityOnHand_; 035} 036 037 javna praznina setQuantityOnHand (int _iQuantityOnHand) 038 {039 iQuantityOnHand_ = _iQuantityOnHand; 040} 041 042 public String getPartNo () 043 {044 return sPartNo_; 045} 046 047 javna praznina setPartNo (String _sPartNo) 048 {049 sPartNo_ = _sPartNo; 050} 051 052 // ... izvaja tiskanje 053 public void print () 054 {055 System.out.println ("Del:" + getPartNo () + "\ nKoličina pri roki:" + 056 getQuantityOnHand () + "\ n \ n "); 057} 058}; 059 

(Imamo tudi preprost glavni program, imenovan Demo8a, ki bere in piše Elementi zaloge v datoteko in iz nje z uporabo tokov predmetov in vmesnika Za tiskanje, ki InventoryItem izvaja in Demo8a uporablja za tiskanje predmetov. Vir lahko najdete tukaj.) Zagon predstavitvenega programa daje razumne, če ne vznemirljive rezultate:

C: \ fib> java Demo8a w datoteka SA0091-001 33 Napisan predmet: Del: SA0091-001 Količina na roki: 33 C: \ fižol> java Demo8a r datoteka Branje predmeta: Del: SA0091-001 Količina na roki: 33 

Program pravilno serializira in deserializira objekt. Zdaj pa naredimo majhno spremembo v datoteki razreda. Uporabniki sistema so opravili popis in ugotovili neskladja med zbirko podatkov in dejanskim številom elementov. Zahtevali so možnost sledenja števila izgubljenih predmetov iz skladišča. Dodajmo eno samo javno polje InventoryItem ki označuje število predmetov, ki manjkajo v shrambi. V vrstico vstavimo naslednjo vrstico InventoryItem razred in prevede:

016 // polja 017 zaščitena int iQuantityOnHand_; 018 zaščiten niz sPartNo_; 019 public int iQuantityLost_; 

Datoteka se dobro prevede, a poglejte, kaj se zgodi, ko poskusimo prebrati tok iz prejšnje različice:

C: \ mj-java \ Column8> java Demo8a r datoteka IO Izjema: InventoryItem; Lokalni razred ni združljiv java.io.InvalidClassException: InventoryItem; Lokalni razred ni združljiv na java.io.ObjectStreamClass.setClass (ObjectStreamClass.java:219) na java.io.ObjectInputStream.inputClassDescriptor (ObjectInputStream.java:639) na java.io.ObjectInputStream.readObject (ObjectStream.readObject. java.io.ObjectInputStream.inputObject (ObjectInputStream.java:820) na java.io.ObjectInputStream.readObject (ObjectInputStream.java:284) na Demo8a.main (Demo8a.java:56) 

Joj, stari! Kaj se je zgodilo?

java.io.ObjectInputStream ne piše predmetov razreda, ko ustvarja tok bajtov, ki predstavljajo predmet. Namesto tega piše a java.io.ObjectStreamClass, ki je a opis razreda. Ciljni nalagalnik razredov JVM s tem opisom poišče in naloži bytecode za razred. Ustvari in vključuje 64-bitno celo število, imenovano a SerialVersionUID, ki je nekakšen ključ, ki enolično identificira različico datoteke razreda.

The SerialVersionUID je ustvarjen z izračunom 64-bitnega varnega zgoščenega števila naslednjih informacij o razredu. Mehanizem serializacije želi biti sposoben zaznati katero koli od naslednjih stvari:

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