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 -tikslas
s:
- 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):