Programmēšana

Kāda ir jūsu Java koda versija?

2003. gada 23. maijs

J:

A:

publiskā klase Sveiki {public static void main (String [] args) {StringBuffer greeting = new StringBuffer ("sveiki,"); StringBuffer kurš = jauns StringBuffer (argumenti [0]). Append ("!"); sveiciens.pievienot (kurš); System.out.println (sveiciens); }} // Nodarbības beigas 

Sākumā jautājums šķiet diezgan niecīgs. Tik maz koda ir iesaistīts Sveiki klasi, un neatkarīgi no tā, kas tur ir, tiek izmantota tikai Java 1.0 versijas funkcionalitāte. Tātad klasei vajadzētu darboties gandrīz jebkurā JVM bez problēmām, vai ne?

Neesat tik pārliecināts. Apkopojiet to, izmantojot javac no Java 2 Platform, Standard Edition (J2SE) 1.4.1, un palaidiet to iepriekšējā Java Runtime Environment (JRE) versijā:

> ... \ jdk1.4.1 \ bin \ javac Hello.java> ... \ jdk1.3.1 \ bin \ java Sveika pasaule Izņēmums pavedienā "main" java.lang.NoSuchMethodError vietnē Hello.main (Hello.java:20 ) 

Gaidītā "sveika, pasaule!" Vietā šis kods rada izpildlaika kļūdu, kaut arī avots ir 100% saderīgs ar Java 1.0! Un arī kļūda nav gluži tā, kā jūs varētu sagaidīt: klases versiju neatbilstības vietā tā kaut kā sūdzas par trūkstošo metodi. Nesaprašanā? Ja tā, tad pilnu skaidrojumu atradīsit vēlāk šajā rakstā. Pirmkārt, paplašināsim diskusiju.

Kāpēc uztraukties ar dažādām Java versijām?

Java ir diezgan neatkarīga no platformas un lielākoties ir saderīga uz augšu, tāpēc ir ierasts koda fragmentu sastādīt, izmantojot norādīto J2SE versiju, un sagaidīt, ka tas darbosies jaunākās JVM versijās. (Java sintakses izmaiņas parasti notiek bez būtiskām izmaiņām baitu koda instrukciju komplektā.) Jautājums šajā situācijā ir šāds: vai jūs varat izveidot sava veida Java bāzes versiju, kuru atbalsta jūsu kompilētā lietojumprogramma, vai arī noklusētā kompilatora darbība ir pieņemama? Es savu ieteikumu paskaidrošu vēlāk.

Vēl viena diezgan izplatīta situācija ir augstākas versijas kompilatora izmantošana nekā paredzētā izvietošanas platforma. Šajā gadījumā jūs neizmantojat nevienu nesen pievienoto API, bet tikai vēlaties gūt labumu no rīku uzlabojumiem. Apskatiet šo koda fragmentu un mēģiniet uzminēt, kas tam būtu jādara izpildlaikā:

public class ThreadSurprise {public static void main (String [] args) met Izņēmumu {Thread [] threads = new Thread [0]; diegi [-1] .miegs (1); // Vai vajadzētu mest? }} // Nodarbības beigas 

Ja šim kodam būtu jāmet ArrayIndexOutOfBoundsException vai nē? Ja jūs apkopojat ThreadSurprise izmantojot dažādas Sun Microsystems JDK / J2SDK (Java 2 Platform, Standard Development Kit) versijas, uzvedība nebūs konsekventa:

  • 1.1 versijas un iepriekšējie kompilatori ģenerē kodu, kas nemet
  • 1.2 versijas metieni
  • Versija 1.3 nemet
  • 1.4 versijas metieni

Smalkais punkts šeit ir tāds Thread.sleep () ir statiska metode, un tai nav nepieciešama a Vītne instancē vispār. Tomēr Java valodas specifikācija liek kompilatoram secināt mērķa klasi ne tikai no kreisās izteiksmes diegi [-1] .miegs (1);, bet arī novērtējiet pašu izteicienu (un atmetiet šāda novērtējuma rezultātu). Ir atsauces indekss -1 diegi masīva daļa no šāda novērtējuma? Java valodas specifikācijas formulējums ir nedaudz neskaidrs. J2SE 1.4 izmaiņu kopsavilkums nozīmē, ka neskaidrība beidzot tika atrisināta par labu kreisās puses izteiksmes pilnīgai novērtēšanai. Lieliski! Tā kā J2SE 1.4 kompilators šķiet labākā izvēle, es vēlos to izmantot visām manām Java programmēšanas programmām, pat ja mana mērķa izpildlaika platforma ir vecāka versija, lai tikai gūtu labumu no šādiem labojumiem un uzlabojumiem. (Ņemiet vērā, ka rakstīšanas laikā visi lietojumprogrammu serveri nav sertificēti J2SE 1.4 platformā.)

Lai gan pēdējais koda piemērs bija nedaudz mākslīgs, tas kalpoja, lai ilustrētu punktu. Citi iemesli izmantot neseno J2SDK versiju ietver vēlmi gūt labumu no javadoc un citiem rīku uzlabojumiem.

Visbeidzot, savstarpēja kompilācija ir diezgan dzīvesveids iegultās Java izstrādē un Java spēļu izstrādē.

Sveiki klases mīkla paskaidrota

The Sveiki piemērs, ar kuru sākts šis raksts, ir nepareizas savstarpējas sastādīšanas piemērs. J2SE 1.4 pievienoja jaunu metodi StringBuffer API: pievienot (StringBuffer). Kad javac izlemj, kā tulkot greeting.append (kurš) baitu kodā, tas meklē StringBuffer klases definīciju bootstrap classpath un tā vietā izvēlas šo jauno metodi pievienot (objekts). Lai arī pirmkods ir pilnībā saderīgs ar Java 1.0, iegūtajam baitu kodam ir nepieciešams J2SE 1.4 izpildlaiks.

Ievērojiet, cik viegli ir pieļaut šo kļūdu. Kompilācijas brīdinājumu nav, un kļūda ir atklājama tikai izpildlaika laikā. Pareizais veids, kā izmantot javac no J2SE 1.4, lai izveidotu Java 1.1 saderīgu Sveiki klase ir:

> ... \ jdk1.4.1 \ bin \ javac -target 1.1-bootclasspath ... \ jdk1.1.8 \ lib \ class.zip Hello.java 

Pareizajā javac burtnīcā ir divas jaunas iespējas. Pārbaudīsim, ko viņi dara un kāpēc tie ir nepieciešami.

Katrā Java klasē ir versijas zīmogs

Varbūt jūs to nezināt, bet katrs .klase jūsu izveidotajā failā ir versijas zīmogs: divi neparakstīti īsie veseli skaitļi, sākot no 4 baita nobīdes, tieši aiz 0xCAFEBABE burvju numurs. Tie ir klases formāta galvenie / mazie versiju numuri (skat. Klases faila formāta specifikāciju), un tiem ir noderīgums, turklāt tie ir tikai formāta definīcijas paplašināšanas punkti. Katrā Java platformas versijā ir norādīts atbalstīto versiju diapazons. Šeit ir atbalstīto diapazonu tabula šajā rakstīšanas laikā (mana šīs tabulas versija nedaudz atšķiras no datiem, kas ir Sun dokumentos - es izvēlējos noņemt dažas diapazona vērtības, kas attiecas tikai uz ļoti vecām (pirms 1.0.2) Sun kompilatora versijām) :

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

Atbilstošs JVM atteiksies ielādēt klasi, ja klases versijas zīmogs atrodas ārpus JVM atbalsta diapazona. Piezīme no iepriekšējās tabulas, ka vēlāk JVM vienmēr atbalsta visu versiju diapazonu no iepriekšējā versijas līmeņa un arī to paplašina.

Ko tas nozīmē jums kā Java izstrādātājam? Ņemot vērā iespēju kompilēšanas laikā kontrolēt šo versijas zīmogu, varat ieviest minimālo Java izpildlaika versiju, kas nepieciešama jūsu lietojumprogrammai. Tas ir tieši tas, ko -mērķis kompilatora opcija. Šeit ir saraksts ar versiju zīmogiem, kurus javac kompilatori emitējuši no dažādiem JDK / J2SDK pēc noklusējuma (ievērojiet, ka J2SDK 1.4 ir pirmais J2SDK, kurā javac maina noklusējuma mērķi no 1.1 uz 1.2):

JDK 1.1: 45.3 J2SDK 1.2: 45.3 J2SDK 1.3: 45.3 J2SDK 1.4: 46.0 

Un šeit ir ietekme, norādot dažādus -mērķiss:

- mērķis 1.1: 45.3 - mērķis 1.2: 46.0 - mērķis 1.3: 47.0 - mērķis 1.4: 48.0 

Kā piemēru sekojošais izmanto URL.getPath () metode, kas pievienota J2SE 1.3:

 URL url = jauns URL ("//www.javaworld.com/columns/jw-qna-index.shtml"); System.out.println ("URL ceļš:" + url.getPath ()); 

Tā kā šim kodam ir nepieciešama vismaz J2SE 1.3, man vajadzētu izmantot mērķis 1.3 to būvējot. Kāpēc piespiest savus lietotājus tikt galā? java.lang.NoSuchMethodError pārsteigumi, kas rodas tikai tad, kad viņi kļūdaini ir ielādējuši klasi 1.2 JVM? Protams, es varētu dokumentu ka manai lietojumprogrammai ir nepieciešama J2SE 1.3, taču tā būtu tīrāka un izturīgāka izpildīt tas pats binārā līmenī.

Es nedomāju, ka mērķa JVM iestatīšanas prakse tiek plaši izmantota uzņēmuma programmatūras izstrādē. Es uzrakstīju vienkāršu lietderības klasi DumpClassVersions (pieejams ar šī raksta lejupielādi), kas var skenēt failus, arhīvus un direktorijus ar Java klasēm un ziņot par visiem sastaptajiem klases versiju zīmogiem. Dažas ātras pārlūkošanas laikā populāri atvērtā pirmkoda projekti vai pat dažādu JDK / J2SDK pamata bibliotēkas neparādīs īpašu sistēmu klases versijām.

Sāknēšanas siksnas un paplašināšanas klases uzmeklēšanas ceļi

Tulkojot Java pirmkodu, sastādītājam jāzina to veidu definīcija, kurus tas vēl nav redzējis. Tas ietver jūsu lietojumprogrammu klases un pamatklases, piemēram, java.lang.StringBuffer. Es esmu pārliecināts, ka jūs zināt, ka pēdējo klasi bieži lieto, lai tulkotu izteicienus, kas satur Stīga savienošana un tamlīdzīgi.

Virspusēji līdzīgs procesam, kas līdzīgs parastajai lietojumprogrammu classloading, tiek uzmeklēta klases definīcija: vispirms bootstrap classpath, tad paplašinājuma classpath un visbeidzot lietotāja classpath (-ceļš). Ja jūs atstājat visu noklusējuma ziņā, stāsies spēkā "mājas" javac J2SDK definīcijas - kas, iespējams, nav pareizi, kā parādīts Sveiki piemērs.

Lai ignorētu sāknēšanas un paplašināšanas klases uzmeklēšanas ceļus, izmantojiet -bootclasspath un -papildus attiecīgi javac opcijas. Šī spēja papildina -mērķis opcija tādā nozīmē, ka, kamēr otrais nosaka minimālo nepieciešamo JVM versiju, pirmais izvēlas ģenerētās kodam pieejamās pamata klases API.

Atcerieties, ka pats javaks bija rakstīts Java valodā. Divas tikko minētās opcijas ietekmē klases meklēšanu baitu kodu ģenerēšanai. Viņi dara ietekmē sāknēšanas joslu un paplašinājumu klases ceļus, kurus JVM izmanto, lai izpildītu javac kā Java programmu (to varētu izdarīt, izmantojot -J bet tas ir diezgan bīstami un rada neatbalstītu uzvedību). Citiem vārdiem sakot, javac faktiski neielādē nevienu klasi no -bootclasspath un -papildus; tas tikai atsaucas uz viņu definīcijām.

Ņemot vērā nesen iegūto izpratni par javac savstarpējās apkopošanas atbalstu, redzēsim, kā to var izmantot praktiskās situācijās.

1. scenārijs: mērķējiet uz vienas bāzes J2SE platformu

Šis ir ļoti izplatīts gadījums: vairākas J2SE versijas atbalsta jūsu lietojumprogrammu, un tā notiek, ka visu varat ieviest, izmantojot noteikta API pamata API bāze) J2SE platformas versija. Saderība ar augšu rūpējas par pārējo. Lai gan J2SE 1.4 ir jaunākā un labākā versija, jūs neredzat iemeslu izslēgt lietotājus, kuri vēl nevar palaist J2SE 1.4.

Ideāls veids, kā sastādīt lietojumprogrammu, ir:

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

Jā, tas nozīmē, ka būvēšanas mašīnā, iespējams, būs jāizmanto divas dažādas J2SDK versijas: tā, kuru izvēlaties sava javac versijai, un tā, kas ir jūsu bāzes atbalstītā J2SE platforma. Tas, šķiet, ir papildu iestatīšanas darbs, taču patiesībā tā ir maza cena, kas jāmaksā par spēcīgu uzbūvi. Galvenais šeit ir nepārprotami kontrolēt gan klases versijas zīmogus, gan bootstrap klases ceļu, nevis paļauties uz noklusējumiem. Izmantojiet -verbose iespēja pārbaudīt, no kurienes nāk pamatklases definīcijas.

Kā blakus komentāru es pieminēšu, ka ir ierasts redzēt izstrādātājus rt.jar no viņu J2SDK uz -ceļš līnija (tas varētu būt ieradums no JDK 1.1 dienām, kad jums bija jāpievieno klases.zip uz kompilācijas klases ceļu). Ja sekojat iepriekšējai diskusijai, tagad saprotat, ka tas ir pilnīgi lieks un sliktākajā gadījumā var traucēt pareizu lietu kārtību.

2. scenārijs: pārslēdziet kodu, pamatojoties uz Java izpildes laikā noteikto versiju

Šeit jūs vēlaties būt sarežģītāks nekā 1. scenārijā: jums ir bāzes atbalstīta Java platformas versija, taču, ja jūsu kods darbojas augstākā Java versijā, jūs vēlaties izmantot jaunākas API. Piemēram, jūs varat iztikt ar java.io. * API, taču nebūtu iebildumu gūt labumu java.nio. * uzlabojumi jaunākā JVM, ja tāda ir iespēja.

Šajā scenārijā pamata apkopošanas pieeja atgādina 1. scenārija pieeju, izņemot to, ka jūsu bootstrap J2SDK vajadzētu būt augstākais versija, kas jums jāizmanto:

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

Ar to tomēr nepietiek; jums arī jādara kaut kas gudrs jūsu Java kodā, lai tas pareizi rīkotos dažādās J2SE versijās.

Viena no alternatīvām ir Java priekšapstrādes procesora izmantošana (vismaz ar # ifdef / # else / # endif atbalsts) un faktiski ģenerē dažādus būvējumus dažādām J2SE platformu versijām. Lai gan J2SDK trūkst pienācīga priekšapstrādes atbalsta, šādu rīku tīmeklī netrūkst.

Tomēr vairāku izplatījumu pārvaldīšana dažādām J2SE platformām vienmēr ir papildu slogs. Izmantojot zināmu papildu tālredzību, jūs varat atbrīvoties, izplatot vienu lietojumprogrammas būvējumu. Šeit ir piemērs, kā to izdarīt (URLTest1 ir vienkārša klase, kas no URL iegūst dažādus interesantus bitus):

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