Programavimas

Kokia jūsų „Java“ kodo versija?

2003 m. Gegužės 23 d

Klausimas:

A:

public class Sveiki {public static void main (String [] args) {StringBuffer sveikinimas = new StringBuffer ("labas"); StringBuffer = naujas StringBuffer (argumentai [0]). Pridėti ("!"); pasisveikinimas.pateikti (kas); System.out.println (sveikinimas); }} // Klasės pabaiga 

Iš pradžių klausimas atrodo gan nereikšmingas. Tiek mažai kodo yra įtraukta į Sveiki klasę, ir kas ten bebūtų, naudojama tik „Java 1.0“ versija. Taigi klasė turėtų dalyvauti beveik bet kuriame JVM be problemų, ar ne?

Nebūk toks tikras. Sudarykite naudodami „javac“ iš „Java 2 Platform, Standard Edition“ (J2SE) 1.4.1 ir paleiskite ją ankstesnėje „Java Runtime Environment“ (JRE) versijoje:

> ... \ jdk1.4.1 \ bin \ javac Hello.java> ... \ jdk1.3.1 \ bin \ java Sveikas pasaulis Išimtis temoje „main“ java.lang.NoSuchMethodError at Hello.main (Hello.java:20 ) 

Vietoj laukiamo „labas, pasauli!“, Šis kodas pateikia vykdymo laiko klaidą, nors šaltinis yra 100 proc. Suderinamas su „Java 1.0“! Ir klaida nėra tokia, kokios galite tikėtis: vietoj klasės versijos neatitikimo ji kažkaip skundžiasi dėl trūkstamo metodo. Suglumusi? Jei taip, išsamų paaiškinimą rasite vėliau šiame straipsnyje. Pirmiausia išplėskime diskusiją.

Kodėl verta vargintis dėl skirtingų „Java“ versijų?

„Java“ yra gana nepriklausoma nuo platformos ir dažniausiai suderinama su aukštyn, todėl įprasta kodą sudaryti naudojant nurodytą J2SE versiją ir tikimasi, kad jis veiks vėlesnėse JVM versijose. („Java“ sintaksės pokyčiai paprastai vyksta be jokių esminių baitų kodo instrukcijų rinkinio pakeitimų.) Šioje situacijoje kyla klausimas: ar galite sukurti kokią nors pagrindinę „Java“ versiją, kurią palaiko jūsų kompiliuota programa, ar priimtina numatytoji kompiliatoriaus elgsena? Vėliau paaiškinsiu savo rekomendaciją.

Kita gana dažna situacija yra naudoti aukštesnės versijos kompiliatorių nei numatyta diegimo platforma. Tokiu atveju nenaudojate jokių neseniai pridėtų API, o tik norite pasinaudoti įrankių patobulinimais. Pažvelkite į šį kodo fragmentą ir pabandykite atspėti, ką jis turėtų atlikti vykdymo metu:

public class ThreadSurprise {public static void main (String [] args) meta išimtį {Thread [] threads = new Thread [0]; siūlai [-1] .miega (1); // Ar tai turėtų mesti? }} // Klasės pabaiga 

Ar šis kodas turėtų mesti „ArrayIndexOutOfBoundsException“ arba ne? Jei kompiliuoji ThreadSurprise naudojant skirtingas „Sun Microsystems JDK / J2SDK“ („Java 2 Platform“, „Standard Development Kit“) versijas, elgesys nebus nuoseklus:

  • 1.1 versijos ir ankstesni kompiliatoriai sukuria nemetantį kodą
  • 1.2 versijos metimai
  • 1.3 versija nemeta
  • 1.4 versijos metimai

Subtilus dalykas yra tas Thread.sleep () yra statinis metodas ir jam nereikia a Siūlas apskritai. Vis dėlto „Java“ kalbos specifikacija reikalauja, kad kompiliatorius iš kairės išraiškos reikštų ne tik tikslinę klasę siūlai [-1] .miega (1);, bet ir įvertinkite pačią išraišką (ir atmeskite tokio vertinimo rezultatą). Ar nurodomas indekso -1 indeksas siūlai masyvo tokio vertinimo dalis? „Java“ kalbos specifikacijos formuluotės yra šiek tiek miglotos. J2SE 1.4 pakeitimų santrauka reiškia, kad neaiškumas buvo galutinai išspręstas, norint visiškai įvertinti kairiosios rankos išraišką. Puiku! Kadangi „J2SE 1.4“ kompiliatorius atrodo geriausias pasirinkimas, noriu jį naudoti visoms savo „Java“ programavimo programoms, net jei mano tikslinė vykdymo laiko platforma yra ankstesnė versija, kad tik galėčiau pasinaudoti tokiais pataisymais ir patobulinimais. (Atkreipkite dėmesį, kad rašymo metu ne visi programų serveriai yra sertifikuoti J2SE 1.4 platformoje.)

Nors paskutinis kodo pavyzdys buvo šiek tiek dirbtinis, jis iliustravo tam tikrą tašką. Kitos priežastys, kodėl reikia naudoti naujausią „J2SDK“ versiją, yra noras pasinaudoti „javadoc“ ir kitais įrankių patobulinimais.

Galiausiai kryžminis kompiliavimas yra gana įprastas būdas plėtojant įterptąją „Java“ ir „Java“ žaidimus.

Labas klasės galvosūkis paaiškintas

Sveiki Straipsnio pradžios pavyzdys yra neteisingo kryžminio kompiliavimo pavyzdys. J2SE 1.4 pridėjo naują metodą „StringBuffer“ API: pridėti („StringBuffer“). Kai „javac“ nusprendžia, kaip išversti sveikinimas. pridėti (kas) į baito kodą, jis ieško „StringBuffer“ klasės apibrėžimą bootstrap classpath ir pasirenka šį naują metodą vietoj pridėti (objektas). Nors šaltinio kodas yra visiškai suderinamas su „Java 1.0“, gautam baito kodui reikalingas J2SE 1.4 vykdymo laikas.

Atkreipkite dėmesį, kaip lengva padaryti šią klaidą. Nėra jokių kompiliavimo įspėjimų, o klaidą galima aptikti tik vykdymo metu. Teisingas būdas naudoti „javac“ iš J2SE 1.4, kad būtų sukurta suderinama „Java 1.1“ Sveiki klasė yra:

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

Teisingame „javac“ užkalbėjime yra dvi naujos parinktys. Panagrinėkime, ką jie daro ir kodėl jie reikalingi.

Kiekvienoje „Java“ klasėje yra versijos antspaudas

Galbūt jūs to nežinote, bet kiekvienas .klasė jūsų sugeneruotame faile yra versijos antspaudas: du nepasirašyti trumpi sveiki skaičiai, prasidedantys 4 baito poslinkiu, iškart po 0xCAFEBABE stebuklingas skaičius. Jie yra pagrindiniai / mažesnieji klasės formato versijų numeriai (žr. „Klasės failo formato specifikaciją“), be to, jie turi naudos, be to, kad yra tik šio formato apibrėžimo plėtiniai. Kiekvienoje „Java“ platformos versijoje nurodomas palaikomų versijų diapazonas. Čia yra palaikomų diapazonų lentelė šiuo metu (mano šios lentelės versija šiek tiek skiriasi nuo „Sun“ dokumentuose esančių duomenų - nusprendžiau pašalinti kai kurias diapazono reikšmes, susijusias tik su labai senomis (iki 1.0.2) „Sun“ kompiliatoriaus versijomis) :

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 

Atitinkamas JVM atsisakys įkelti klasę, jei klasės versijos antspaudas nepatenka į JVM palaikymo diapazoną. Pastaba iš ankstesnės lentelės, kad vėliau JVM visada palaiko visą versijų diapazoną nuo ankstesnio versijos lygio ir jį taip pat praplečia.

Ką tai reiškia jums, kaip „Java“ kūrėjui? Turėdami galimybę valdyti šį versijos antspaudą kompiliavimo metu, galite priversti įdiegti minimalią „Java“ vykdymo laiko versiją, kurios reikalauja jūsų programa. Būtent tai ir yra -tikslas kompiliatoriaus parinktis. Čia pateikiamas versijų antspaudų, kuriuos iš įvairių „JDK“ / „J2SDK“ išleido javac kompiliatoriai, sąrašas pagal nutylėjimą (atkreipkite dėmesį, kad J2SDK 1.4 yra pirmasis J2SDK, kuriame „javac“ pakeičia numatytąjį tikslą iš 1.1 į 1.2):

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

Ir čia yra poveikis nurodant įvairius -tikslass:

- 1,1: 45,3, - 1,2: 46,0, - 1,3: 47,0, - 1,4: 48,0 

Kaip pavyzdį, toliau naudojamas URL.getPath () metodas pridėtas J2SE 1.3:

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

Kadangi šiam kodui reikalinga bent J2SE 1.3, turėčiau naudoti 1.3 tikslas jį statant. Kam versti savo vartotojus spręsti java.lang.NoSuchMethodError netikėtumų, kurie įvyksta tik tada, kai jie klaidingai pakrauna klasę į 1.2 JVM? Aišku, galėčiau dokumentas kad mano programai reikalinga J2SE 1.3, tačiau ji būtų švaresnė ir patikimesnė vykdyti tas pats ir dvejetainiu lygiu.

Nemanau, kad tikslinės JVM nustatymo praktika yra plačiai naudojama kuriant įmonės programinę įrangą. Parašiau paprastą naudingumo klasę „DumpClassVersions“ (galima atsisiųsti iš šio straipsnio), kurie gali nuskaityti failus, archyvus ir katalogus su „Java“ klasėmis ir pranešti apie visus aptiktus klasės versijų antspaudus. Greitai naršant populiarius atvirojo kodo projektus ar net pagrindines bibliotekas iš įvairių JDK / J2SDK, nebus rodoma jokia konkreti klasių versijų sistema.

Įkrovos juostos ir išplėtimo klasės paieškos keliai

Verčiant „Java“ šaltinio kodą, kompiliatorius turi žinoti dar nematytų tipų apibrėžimą. Tai apima jūsų taikymo klases ir pagrindines klases, pvz java.lang.StringBuffer. Kaip tikiu, jūs žinote, pastaroji klasė dažnai naudojama verčiant posakius, kuriuose yra Stygos sujungimas ir panašiai.

Procesas, paviršutiniškai panašus į įprastą programos „classloading“, ieško klasės apibrėžimo: pirmiausia įkrovos „classpath“, paskui „classpath“ plėtinio ir galiausiai vartotojo classpath (-klakas). Jei paliksite viską pagal numatytuosius nustatymus, įsigalios „namų“ javac J2SDK apibrėžimai, kurie gali būti neteisingi, kaip parodyta Sveiki pavyzdys.

Norėdami nepaisyti įkrovos juostos ir plėtinio klasės paieškos kelių, naudokite -paleidimo takas ir -papildomi „javac“ parinktys. Šis gebėjimas papildo -tikslas parinktį ta prasme, kad nors pastaroji nustato mažiausią reikalaujamą JVM versiją, pirmoji pasirenka pagrindinės klasės API, prieinamas sugeneruotam kodui.

Atminkite, kad pats „javac“ buvo parašytas „Java“. Dvi mano ką tik paminėtos parinktys daro įtaką klasės paieškai baitų kodų generavimui. Jie daro ne paveikti įkrovos juostos ir išplėtimo klasių kelius, kuriuos JVM naudoja vykdydamas „Java“ kaip „Java“ programą (pastarąją galima padaryti per -J bet tai padaryti yra gana pavojinga ir atsiranda nepalaikomas elgesys). Kitaip tariant, „javac“ iš tikrųjų neįkelia jokių klasių iš -paleidimo takas ir -papildomi; jis tik nurodo jų apibrėžimus.

Turėdami naujai įgytą supratimą apie „javac“ kryžminio kompiliavimo palaikymą, pažiūrėkime, kaip tai galima panaudoti praktinėse situacijose.

1 scenarijus: nukreipkite į vieną bazę „J2SE“ platformą

Tai labai dažnas atvejis: kelios „J2SE“ versijos palaiko jūsų programą, ir taip atsitinka, kad viską galite įdiegti naudodamiesi pagrindinėmis tam tikros API (aš ją pavadinsiu) bazė) „J2SE“ platformos versija. Didesnis suderinamumas rūpinasi likusiais dalykais. Nors „J2SE 1.4“ yra naujausia ir geriausia versija, nematote priežasties atmesti vartotojus, kurie dar negali paleisti „J2SE 1.4“.

Idealus būdas parengti paraišką yra:

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

Taip, tai reiškia, kad jūsų statybinėje mašinoje gali tekti naudoti dvi skirtingas „J2SDK“ versijas: tą, kurią pasirenkate „javac“, ir tą, kuri yra jūsų bazės palaikoma „J2SE“ platforma. Tai atrodo papildomos sąrankos pastangos, tačiau iš tikrųjų tai yra maža kaina, kurią reikia sumokėti už tvirtą pastatymą. Čia svarbiausia aiškiai valdyti tiek klasės versijos antspaudus, tiek „bootstrap“ klasės kelią ir nepasikliauti numatytaisiais nustatymais. Naudoti -verbose galimybė patikrinti, iš kur gaunami pagrindiniai klasės apibrėžimai.

Kaip šalutinį komentarą paminėsiu, kad dažniausiai tenka pastebėti kūrėjus rt.jar iš savo J2SDK -klakas eilutė (tai gali būti įprotis iš JDK 1.1 dienos, kai reikėjo pridėti klasės.zip į kompiliavimo klasės kelią). Jei stebėjote aukščiau pateiktą diskusiją, dabar suprantate, kad tai visiškai nereikalinga, o blogiausiu atveju gali trukdyti tinkamai tvarkytis.

2 scenarijus: perjunkite kodą pagal „Java“ versiją, aptiktą vykdymo metu

Čia norite būti sudėtingesni nei 1 scenarijuje: turite bazinę palaikomą „Java“ platformos versiją, tačiau jei jūsų kodas būtų vykdomas aukštesne „Java“ versija, norėtumėte pasinaudoti naujesnėmis API. Pavyzdžiui, galite susitvarkyti java.io. * API, bet neprieštarautų java.nio. * patobulinimai naujesniame JVM, jei bus galimybė.

Šiame scenarijuje pagrindinis kompiliavimo metodas primena 1 scenarijaus metodą, išskyrus tai, kad jūsų įkrovos juosta J2SDK turėtų būti aukščiausias versiją, kurią turite naudoti:

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

Tačiau to nepakanka; taip pat turite padaryti kažką protingo savo „Java“ kode, kad jis tinkamai elgtųsi skirtingose ​​„J2SE“ versijose.

Viena iš alternatyvų yra naudoti „Java“ išankstinį procesorių (bent jau su # ifdef / # else / # endif palaikymas) ir iš tikrųjų sukuria skirtingus „J2SE“ platformos versijų kūrinius. Nors J2SDK trūksta tinkamo išankstinio apdorojimo palaikymo, tokių priemonių internete netrūksta.

Tačiau kelių paskirstymų valdymas skirtingoms J2SE platformoms visada yra papildoma našta. Turėdami tam tikrą papildomą numatymą, galite išsisukti platindami vieną savo programos paketą. Štai pavyzdys, kaip tai padaryti (URLTest1 yra paprasta klasė, išskirianti įvairius įdomius bitus iš URL):