Programiranje

Katera različica je vaša koda Java?

23. maja 2003

V:

A:

javni razred Pozdravljeni {public static void main (String [] args) {StringBuffer pozdrav = new StringBuffer ("hello,"); StringBuffer who = nov StringBuffer (args [0]). Append ("!"); pozdrav.append (kdo); System.out.println (pozdrav); }} // Konec predavanja 

Vprašanje se sprva zdi precej malenkostno. Tako malo kode je vključenih v zdravo razreda in karkoli obstaja, uporablja samo funkcije, ki segajo v Javo 1.0. Torej bi moral razred teči v skoraj vseh JVM brez težav, kajne?

Ne bodi tako prepričan. Sestavite ga s pomočjo javaca iz platforme Java 2 Platform, Standard Edition (J2SE) 1.4.1 in ga zaženite v starejši različici Java Runtime Environment (JRE):

> ... \ jdk1.4.1 \ bin \ javac Hello.java> ... \ jdk1.3.1 \ bin \ java Hello world Izjema v niti "main" java.lang.NoSuchMethodError at Hello.main (Hello.java:20 ) 

Namesto pričakovanega "zdravo, svet!", Ta koda vrže napako med izvajanjem, čeprav je vir 100-odstotno združljiv z Java 1.0! In napaka tudi ni ravno tisto, kar bi lahko pričakovali: namesto neusklajenosti različice razreda se nekako pritoži nad manjkajočo metodo. Zmeden? V tem primeru boste našli popolno razlago v nadaljevanju tega članka. Najprej razširimo razpravo.

Zakaj bi se mučili z različnimi različicami Jave?

Java je precej neodvisna od platforme in večinoma združljiva navzgor, zato je običajno, da sestavite del kode z uporabo dane različice J2SE in pričakujete, da bo delovala v kasnejših različicah JVM. (Spremembe sintakse Java se običajno pojavijo brez bistvenih sprememb nabora bajtnih kod.) Vprašanje v tej situaciji je: Ali lahko vzpostavite nekakšno osnovno različico Java, ki jo podpira vaša prevedena aplikacija, ali je privzeto vedenje prevajalnika sprejemljivo? Svoje priporočilo bom razložil kasneje.

Druga dokaj pogosta situacija je uporaba prevajalnika z več različicami od predvidene razmestitvene platforme. V tem primeru ne uporabljate nedavno dodanih API-jev, ampak želite le izkoristiti izboljšave orodij. Oglejte si ta delček kode in poskusite uganiti, kaj naj naredi med izvajanjem:

javni razred ThreadSurprise {public static void main (String [] args) vrže izjemo {Thread [] niti = nova nit [0]; niti [-1] .sleep (1); // Ali naj to vrže? }} // Konec predavanja 

Če ta koda vrže ArrayIndexOutOfBoundsException ali ne? Če prevajate ThreadSurprise z različnimi različicami Sun Microsystems JDK / J2SDK (Java 2 Platform, Standard Development Kit) vedenje ne bo skladno:

  • Prevajalniki različice 1.1 in starejših ustvarijo kodo, ki je ne vrže
  • Različica 1.2 meče
  • Različica 1.3 ne vrže
  • Različica 1.4 vrže

Tukaj je subtilna točka Thread.sleep () je statična metoda in ne potrebuje Navoj primer. Kljub temu pa specifikacija jezika Java od prevajalnika zahteva, da ne izhaja le iz ciljnega razreda iz levega izraza niti [-1] .sleep (1);, ampak tudi ovrednotite sam izraz (in zavrzite rezultat takšnega vrednotenja). Se sklicuje na indeks -1 datoteke niti del take ocene? Besedilo v specifikaciji jezika Java je nekoliko nejasno. Povzetek sprememb za J2SE 1.4 pomeni, da je bila dvoumnost končno razrešena v korist popolne ocene levega izraza. Super! Ker se zdi, da je prevajalnik J2SE 1.4 najboljša izbira, ga želim uporabiti za celotno programiranje Java, tudi če je moja ciljna izvajalna platforma starejša različica, samo da bi imel koristi od takšnih popravkov in izboljšav. (Upoštevajte, da v času pisanja niso vsi aplikacijski strežniki certificirani na platformi J2SE 1.4.)

Čeprav je bil zadnji primer kode nekoliko umeten, je ponazoril točko. Drugi razlogi za uporabo najnovejše različice J2SDK vključujejo željo, da bi izkoristili izboljšave javadoca in drugih orodij.

Nazadnje, navzkrižno prevajanje je precej način življenja pri vdelanem razvoju Java in razvoju iger Java.

Razložena uganka za razred

The zdravo Primer, ki se je začel v tem članku, je primer nepravilne navzkrižne kompilacije. J2SE 1.4 je dodal novo metodo StringBuffer API: dodaj (StringBuffer). Ko se javac odloči, kako prevajati pozdrav.priloži (kdo) v bajtno kodo, poišče StringBuffer opredelitev razreda v poti učnih pasov bootstrap in namesto tega izbere to novo metodo dodaj (predmet). Čeprav je izvorna koda popolnoma združljiva z Java 1.0, nastala bajtna koda zahteva izvajalno okolje J2SE 1.4.

Upoštevajte, kako enostavno je narediti to napako. Opozorila o prevajanju ni, napako pa je mogoče zaznati samo med izvajanjem. Pravilen način uporabe javaca iz J2SE 1.4 za ustvarjanje Java 1.1 združljivega zdravo razred je:

> ... \ jdk1.4.1 \ bin \ javac -target 1.1 -bootclasspath ... \ jdk1.1.8 \ lib \ classes.zip Pozdravljeni.java 

Pravilni naklon javac vsebuje dve novi možnosti. Preučimo, kaj počnejo in zakaj so potrebni.

Vsak razred Java ima žig različice

Morda se tega ne zavedate, ampak vsak .razred datoteka, ki jo ustvarite, vsebuje žig različice: dve nepodpisani kratki celi števili, ki se začneta z odmikom bajtov 4, takoj za 0xCAFEBABE čarobno število. So glavne / manjše številke različic oblike razreda (glejte Specifikacija oblike zapisa datoteke razreda), poleg tega pa imajo tudi uporabne točke za to definicijo formata. Vsaka različica platforme Java določa vrsto podprtih različic. Tu je tabela podprtih obsegov v tem času pisanja (moja različica te tabele se nekoliko razlikuje od podatkov v Sun-ovih dokumentih - odločil sem se odstraniti nekatere vrednosti obsega, ki so pomembne samo za izredno stare (pred 1.0.2) različice Sun-ovega prevajalnika) :

Platforma Java 1.1: 45.3-45.65535 Platforma Java 1.2: 45.3-46.0 Platforma Java 1.3: 45.3-47.0 Platforma Java 1.4: 45.3-48.0 

Skladen JVM zavrne nalaganje razreda, če je žig različice razreda zunaj obsega podpore JVM. Upoštevajte iz prejšnje tabele, da poznejši JVM vedno podpirajo celoten obseg različic od prejšnje ravni različice in ga tudi razširijo.

Kaj to pomeni za vas kot razvijalca Jave? Glede na to, da lahko med prevajanjem nadzorujete ta žig različice, lahko uveljavite minimalno izvajalno različico Java, ki jo zahteva vaša aplikacija. Prav to je -cilj možnost prevajalnika. Tu je seznam žigov različic, ki so jih sestavljali javac prevajalniki iz različnih JDK / J2SDK privzeto (upoštevajte, da je J2SDK 1.4 prvi J2SDK, kjer javac spremeni svoj privzeti cilj z 1,1 na 1,2):

JDK 1,1: 45,3 J2SDK 1,2: 45,3 J2SDK 1,3: 45,3 J2SDK 1,4: 46,0 

In tu je učinek določanja različnih -ciljs:

-cilj 1.1: 45.3 -cil 1.2: 46.0 -cil 1.3: 47.0 -cil 1.4: 48.0 

Kot primer je v nadaljevanju uporabljen URL.getPath () metoda dodana v J2SE 1.3:

 URL URL = nov URL ("//www.javaworld.com/columns/jw-qna-index.shtml"); System.out.println ("Pot URL-ja:" + url.getPath ()); 

Ker ta koda zahteva vsaj J2SE 1.3, bi jo moral uporabiti -cilj 1.3 pri gradnji. Zakaj prisiliti moje uporabnike, da se ukvarjajo z njimi java.lang.NoSuchMethodError presenečenja, ki se zgodijo le, če so razred pomotoma naložili v 1.2 JVM? Seveda bi lahko dokument da moja aplikacija zahteva J2SE 1.3, vendar bi bila čistejša in zanesljivejša uveljaviti enako na binarni ravni.

Mislim, da se praksa določanja ciljnega JVM ne uporablja pogosto pri razvoju programske opreme za podjetja. Napisal sem preprost uporabniški razred DumpClassVersions (na voljo s prenosom tega članka), ki lahko skenira datoteke, arhive in imenike z razredi Java in poroča o vseh naletelih na različice razredov. Nekaj ​​hitrega brskanja po priljubljenih odprtokodnih projektih ali celo jedrnih knjižnicah iz različnih JDK / J2SDK ne bo pokazalo nobenega posebnega sistema za različice razredov.

Potek iskanja zagonskega traku in razreda razširitev

Pri prevajanju izvorne kode Java mora prevajalnik poznati definicijo vrst, ki jih še ni videl. To vključuje razrede aplikacij in osnovne razrede, kot so java.lang.StringBuffer. Kot sem prepričan, da veste, se slednji razred pogosto uporablja za prevajanje izrazov, ki vsebujejo Vrvica združevanje in podobno.

Proces, površno podoben običajnemu nalaganju razreda aplikacije, poišče definicijo razreda: najprej v poti učnega pasu bootstrap, nato pot razširitve razreda in na koncu v pot uporabniškega razreda (-razredna pot). Če vse prepustite privzetim, bodo začele veljati definicije iz J2SDK javac "home", kar morda ni pravilno, kot kaže zdravo primer.

Če želite preglasiti poti iskanja razreda zagonskega traku in razširitvenega razreda, uporabite -bootclasspath in -zunaj možnosti javac. Ta sposobnost dopolnjuje -cilj možnost v smislu, da medtem ko slednja nastavi minimalno zahtevano različico JVM, prva izbere API-je osnovnega razreda, ki so na voljo ustvarjeni kodi.

Ne pozabite, da je bil sam javac napisan v Javi. Dve možnosti, ki sem jih pravkar omenil, vplivata na iskanje razreda za generiranje bajtne kode. So ne vplivajo na zagonske in razširitvene poti razredov, ki jih JVM uporablja za izvajanje Javaca kot Java programa (slednje je mogoče storiti prek -J možnost, vendar je to zelo nevarno in povzroči nepodprto vedenje). Povedano drugače, javac dejansko ne naloži nobenega razreda iz -bootclasspath in -zunaj; sklicuje se le na njihove opredelitve.

Z novo pridobljenim razumevanjem podpore Javaca za navzkrižno kompilacijo poglejmo, kako je to mogoče uporabiti v praktičnih situacijah.

Scenarij 1: Ciljajte na eno osnovno platformo J2SE

To je zelo pogost primer: več različic J2SE podpira vašo aplikacijo in zgodi se, da lahko vse implementirate prek osnovnih API-jev določenega (imenoval ga bom osnova) Različica platforme J2SE. Za ostalo skrbi združljivost navzgor. Čeprav je J2SE 1.4 najnovejša in najboljša različica, ne vidite razloga za izključitev uporabnikov, ki še ne morejo zagnati J2SE 1.4.

Idealen način za sestavljanje vaše prijave je:

\ bin \ javac -target -bootclasspath \ jre \ lib \ rt.jar -classpath 

Da, to pomeni, da boste na svojem računalniku morda morali uporabiti dve različici J2SDK: tisto, ki jo izberete za svoj javac, in tisto, ki je vaša osnovna podprta platforma J2SE. To se zdi dodaten napor pri namestitvi, vendar je dejansko majhna cena za trdno zgradbo. Ključ tukaj je izrecno krmiljenje žigov različice razreda in poti učnega pasu bootstrap in ne zanašanje na privzete vrednosti. Uporabi -verbozna možnost preveriti, od kod prihajajo definicije jedrnega razreda.

Kot stranski komentar bom omenil, da je običajno videti, da razvijalci vključujejo rt.jar iz njihovih J2SDK-jev na -razredna pot vrstica (to je lahko navada iz JDK 1,1 dni, ko ste morali dodati razredi.zip do učiteljske poti prevajanja). Če ste spremljali zgornjo razpravo, zdaj razumete, da je to popolnoma odveč, v najslabšem primeru pa lahko moti pravilen red stvari.

2. scenarij: preklopite kodo na podlagi različice Java, zaznane med izvajanjem

Tukaj želite biti bolj izpopolnjeni kot v 1. scenariju: Imate osnovno podprto različico platforme Java, vendar če se vaša koda izvaja v višji različici Java, raje uporabite novejše API-je. Na primer, lahko greš z java.io. * API-ji, vendar jih ne bi motilo java.nio. * izboljšave v novejšem JVM, če se ukaže priložnost.

V tem primeru je osnovni pristop prevajanja podoben pristopu scenarija 1, le da bi moral biti vaš bootstrap J2SDK najvišji različico, ki jo morate uporabiti:

\ bin \ javac -target -bootclasspath \ jre \ lib \ rt.jar -classpath 

Vendar to ni dovolj; v svoji kodi Java morate narediti tudi nekaj pametnega, da bo v različnih različicah J2SE naredil prav.

Ena od možnosti je uporaba predprocesorja Java (z vsaj # ifdef / # else / # endif podporo) in dejansko generirajo različne gradnje za različne različice platforme J2SE. Čeprav J2SDK nima ustrezne podpore za predhodno obdelavo, takšnih orodij v spletu ne manjka.

Vendar je upravljanje več distribucij za različne platforme J2SE vedno dodatno breme. Z nekaj dodatnega predvidevanja se lahko izognete distribuciji ene same različice aplikacije. Tu je primer, kako to storiti (URLTest1 je preprost razred, ki iz URL-ja pridobi različne zanimive bite):

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