Ohjelmointi

Mikä versio on Java-koodisi?

23. toukokuuta 2003

K:

A:

public class Hei {public static void main (String [] args) {StringBuffer tervehdys = uusi StringBuffer ("hei"); StringBuffer joka = uusi StringBuffer (argumentit [0]). Liitä ("!"); tervehdys. liittää (kuka); System.out.println (tervehdys); }} // luokan loppu 

Aluksi kysymys näyttää melko triviaalilta. Niin vähän koodia on mukana Hei luokka, ja mitä vain, se käyttää vain Java 1.0: sta peräisin olevia toimintoja. Joten luokan pitäisi juosta melkein mihin tahansa JVM: ään ilman ongelmia, eikö?

Älä ole niin varma. Käännä se Java 2 Platform, Standard Edition (J2SE) 1.4.1: n javacilla ja suorita se Java Runtime Environment (JRE): n aiemmassa versiossa:

> ... \ jdk1.4.1 \ bin \ javac Hello.java> ... \ jdk1.3.1 \ bin \ java Hello world Exception in thread "main" java.lang.NoSuchMethodError at Hello.main (Hello.java:20) ) 

Odotetun "hei, maailma!", Sijaan tämä koodi heittää ajonaikaisen virheen, vaikka lähde on 100-prosenttisesti Java 1.0 -yhteensopiva! Eikä virhe ole täsmälleen odotettavissa oleva: luokan version ristiriitaisuuden sijaan se jotenkin valittaa puuttuvasta menetelmästä. Ymmällään? Jos näin on, löydät täydellisen selityksen myöhemmin tästä artikkelista. Ensinnäkin laajennetaan keskustelua.

Miksi vaivautua eri Java-versioiden kanssa?

Java on melko alustasta riippumaton ja enimmäkseen ylöspäin yhteensopiva, joten on yleistä koota pala koodia tietyllä J2SE-versiolla ja odottaa sen toimivan myöhemmissä JVM-versioissa. (Java-syntaksimuutokset tapahtuvat yleensä ilman merkittäviä muutoksia tavukoodikäskyjoukkoihin.) Tässä tilanteessa on kysymys: Voitko luoda jonkinlaisen Java-perusversion, jota käännetty sovelluksesi tukee, vai onko kääntäjän oletustoiminta hyväksyttävä? Selitän suositukseni myöhemmin.

Toinen melko yleinen tilanne on käyttää korkeamman version versiota kääntäjää kuin aiottu asennusalusta. Tässä tapauksessa et käytä äskettäin lisättyjä sovellusliittymiä, vaan haluat vain hyötyä työkaluparannuksista. Katso tätä koodinpätkää ja yritä arvata, mitä sen pitäisi tehdä ajon aikana:

public class ThreadSurprise {public static void main (String [] args) heittää poikkeuksen {Thread [] threads = new Thread [0]; langat [-1] unessa (1); // Pitäisikö tämän heittää? }} // luokan loppu 

Pitäisikö tämän koodin heittää ArrayIndexOutOfBoundsException tai ei? Jos kokoat ThreadSurprise käyttämällä erilaisia ​​Sun Microsystems JDK / J2SDK (Java 2 Platform, Standard Development Kit) -versioita, käyttäytyminen ei ole johdonmukaista:

  • Versio 1.1 ja aiemmat kääntäjät luovat koodin, joka ei heitä
  • Versio 1.2 heittää
  • Versio 1.3 ei heitä
  • Versio 1.4 heittää

Hienovarainen asia on tässä Thread.sleep () on staattinen menetelmä eikä vaadi a Lanka esimerkiksi. Silti Java-kielimääritys vaatii kääntäjää päättelemään kohderyhmän paitsi vasemmanpuoleisesta lausekkeesta langat [-1] unessa (1);, mutta myös arvioi itse lauseke (ja hylkää tällaisen arvioinnin tulos). Viittaa indeksin -1 arvoon langat taulukon osa tällaista arviointia? Java-kielimäärityksen sanamuoto on hieman epämääräinen. Yhteenveto J2SE 1.4: n muutoksista viittaa siihen, että epäselvyys ratkaistiin lopulta vasemmanpuoleisen lausekkeen täydellisen arvioinnin hyväksi. Loistava! Koska J2SE 1.4-kääntäjä näyttää parhaalta valinnalta, haluan käyttää sitä kaikessa Java-ohjelmoinnissani, vaikka kohdekäynnistysalustani olisi aikaisempi versio, vain hyötyäkseni tällaisista korjauksista ja parannuksista. (Huomaa, että kirjoitushetkellä kaikki sovelluspalvelimet eivät ole J2SE 1.4 -alustalla sertifioituja.)

Vaikka viimeinen koodiesimerkki oli jonkin verran keinotekoinen, se toimi havainnollistamaan asiaa. Muita syitä käyttää viimeaikaista J2SDK-versiota ovat halu hyödyntää javadocia ja muita työkaluparannuksia.

Lopuksi ristikokoelma on melko elämäntapa sulautetussa Java- ja Java-pelikehityksessä.

Hei luokan palapeli selitetty

Hei Tämä artikkeli aloitettu esimerkki on esimerkki virheellisestä ristikoostamisesta. J2SE 1.4 lisäsi uuden menetelmän StringBuffer API: liitä (StringBuffer). Kun javac päättää kääntämisen tervehdys. liitä (kuka) tavukoodiksi, se etsii StringBuffer luokan määritelmä bootstrap-luokan polulla ja valitsee tämän uuden menetelmän liitä (objekti). Vaikka lähdekoodi on täysin Java 1.0 -yhteensopiva, tuloksena oleva tavukoodi vaatii J2SE 1.4-ajon.

Huomaa, kuinka helppoa on tehdä tämä virhe. Kokoamisvaroituksia ei ole, ja virhe havaitaan vain ajon aikana. Oikea tapa käyttää javacia J2SE 1.4: sta Java 1.1 -yhteensopivan luomiseksi Hei luokka on:

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

Oikea javac-loitsu sisältää kaksi uutta vaihtoehtoa. Tarkastellaan, mitä he tekevät ja miksi ne ovat välttämättömiä.

Jokaisella Java-luokassa on version leima

Et ehkä ole tietoinen siitä, mutta jokainen .luokka Luomaasi tiedostossa on versioleima: kaksi allekirjoittamatonta lyhyttä kokonaislukua tavun siirtymästä 4 heti 0xCAFEBABE maaginen numero. Ne ovat luokkamuodon pää- / sivuversion numerot (katso luokkatiedostomuodon määrittely), ja niillä on apuohjelmia sen lisäksi, että ne ovat vain laajennuskohtia tälle muodonmääritykselle. Jokainen Java-alustan versio määrittää joukon tuettuja versioita. Tässä on taulukko tuetuista alueista tällä hetkellä kirjoittaessani (minun versioni tästä taulukosta eroaa hieman Sunin asiakirjoissa olevista tiedoista - päätin poistaa joitain alueiden arvoja, jotka ovat merkityksellisiä vain Sunin kääntäjän erittäin vanhoille (ennen versiota 1.0.2) versioille) :

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

Yhteensopiva JVM kieltäytyy lataamasta luokkaa, jos luokan versioleima on JVM: n tukialueen ulkopuolella. Huomaa edellisestä taulukosta, että myöhemmät JVM: t tukevat aina koko versiota edellisestä versiotasosta ja myös laajentavat sitä.

Mitä tämä tarkoittaa sinulle Java-kehittäjänä? Ottaen huomioon kyvyn hallita tätä versioleimaa käännöksen aikana, voit pakottaa sovelluksesi vaatiman Java-suorituksen vähimmäisversion. Juuri tämä -kohde kääntäjä-vaihtoehto tekee. Tässä on luettelo javac-kääntäjien lähettämistä versioleimoista useista JDK: sta / J2SDK: sta oletuksena (huomaa, että J2SDK 1.4 on ensimmäinen J2SDK, jossa javac muuttaa oletuskohteen arvosta 1.1 arvoon 1.2):

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

Ja tässä on erilaisten määrittelyn vaikutus -kohdes:

-tavoite 1.1: 45.3 -tavoite 1.2: 46.0 -tavoite 1.3: 47.0 -tavoite 1.4: 48.0 

Esimerkiksi seuraavassa käytetään URL.getPath () J2SE 1.3: een lisätty menetelmä:

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

Koska tämä koodi vaatii vähintään J2SE 1.3: n, minun pitäisi käyttää -tavoite 1.3 rakennettaessa sitä. Miksi pakottaa käyttäjät käsittelemään java.lang.NoSuchMethodError yllätyksiä, joita tapahtuu vasta, kun he ovat vahingossa ladanneet luokan 1.2 JVM: ään? Toki voisin asiakirja että sovellukseni vaatii J2SE 1.3: n, mutta se olisi puhtaampaa ja kestävämpää panna täytäntöön sama binaaritasolla.

En usko, että JVM-tavoitteen asettamista käytetään laajasti yritysohjelmistojen kehittämisessä. Kirjoitin yksinkertaisen hyödyllisyysluokan DumpClassVersions (saatavana tämän artikkelin latauksella), joka voi skannata tiedostoja, arkistoja ja hakemistoja Java-luokilla ja ilmoittaa kaikista havaituista luokan version leimoista. Joitakin suosittujen avoimen lähdekoodin projektien tai jopa JDK: n / J2SDK: n ydinkirjastojen nopea selaus ei näytä mitään erityistä järjestelmää luokan versioille.

Bootstrap- ja laajennusluokan hakupolut

Käännettäessä Java-lähdekoodia kääntäjän on tiedettävä niiden tyyppien määrittely, joita se ei ole vielä nähnyt. Tämä sisältää sovellusluokkasi ja ydinluokat, kuten java.lang.StringBuffer. Kuten olen varma, että tiedät, jälkimmäistä luokkaa käytetään usein käännettäessä lausekkeita, jotka sisältävät Merkkijono ketjutus ja vastaavat.

Pinta-alaltaan samanlainen prosessi kuin tavallinen sovelluksen luokkakuorma etsii luokan määritelmän: ensin bootstrap-luokan polulle, sitten laajennetun luokan polulle ja lopuksi käyttäjän luokan polulle (-polku). Jos jätät kaiken oletusarvoihin, "koti" javacin J2SDK: n määritelmät tulevat voimaan - mikä ei ehkä ole oikein, kuten Hei esimerkki.

Voit ohittaa käynnistyshihnan ja laajennusluokan hakupolut käyttämällä -kenkäpolku ja -lisäosat javac-vaihtoehdot. Tämä kyky täydentää -kohde vaihtoehto siinä mielessä, että vaikka jälkimmäinen asettaa vaaditun vähimmäisversion JVM: stä, ensimmäinen valitsee generoidun koodin käytettävissä olevat ydinluokan sovellusliittymät.

Muista, että javac itse kirjoitettiin Java-kielellä. Kaksi juuri mainitsemaani vaihtoehtoa vaikuttavat tavuhakemistojen luomiseen luokan haulla. He tekevät ei vaikuttaa bootstrapiin ja laajennuksiin, joita JVM käyttää javacin suorittamiseen Java-ohjelmana (jälkimmäinen voidaan tehdä -J vaihtoehto, mutta sen tekeminen on melko vaarallista ja johtaa käyttäytymiseen, jota ei tueta). Toisin sanoen javac ei todellakaan lataa yhtään luokkaa -kenkäpolku ja -lisäosat; se vain viittaa niiden määritelmiin.

Javacin ristikokoelmatukea koskevan äskettäin hankitun ymmärryksen avulla katsotaanpa, kuinka sitä voidaan käyttää käytännön tilanteissa.

Skenaario 1: Kohdista yhden tukiaseman J2SE-alustalle

Tämä on hyvin yleinen tapaus: useita J2SE-versioita tukee sovellustasi, ja niin tapahtuu, että voit toteuttaa kaiken tietyn tietyn ytimen API: n kautta (kutsun sitä pohja) J2SE-alustan versio. Ylös yhteensopivuus hoitaa loput. Vaikka J2SE 1.4 on uusin ja suurin versio, et näe mitään syytä sulkea pois käyttäjiä, jotka eivät voi vielä suorittaa J2SE 1.4: ta.

Ihanteellinen tapa laatia hakemuksesi on:

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

Kyllä, tämä tarkoittaa, että sinun on ehkä käytettävä kahta erilaista J2SDK-versiota rakennuskoneellasi: sen, jonka valitset sen javacille, ja sen, joka on tukiasemasi J2SE-alusta. Tämä näyttää ylimääräiseltä asennustyöltä, mutta se on itse asiassa pieni hinta, joka maksetaan vankasta rakenteesta. Tärkeintä tässä on nimenomaisesti sekä luokan version leimojen että bootstrap-luokan polun hallinta eikä turvautuminen oletusarvoihin. Käytä -verbose vaihtoehto tarkistaa, mistä ydinluokan määritelmät tulevat.

Sivukommenttina mainitsen, että kehittäjien on tavallista nähdä rt.jar heidän J2SDK: ltaan -polku line (tämä voi olla tapana JDK 1.1 päivästä, kun joudut lisäämään luokat. zip luokkareitille). Jos seurait yllä olevaa keskustelua, ymmärrät nyt, että tämä on täysin turhaa ja pahimmassa tapauksessa voi häiritä asian asianmukaista järjestystä.

Skenaario 2: Vaihda koodi suorituksen aikana havaitun Java-version perusteella

Tässä haluat olla kehittyneempi kuin skenaariossa 1: Sinulla on perustukea tukeva Java-alustan versio, mutta jos koodisi suoritetaan korkeammalla Java-versiolla, haluat mieluummin käyttää uudempia sovellusliittymiä. Voit esimerkiksi tulla toimeen java.io. * API: t, mutta ei haittaa hyötyä java.nio. * parannuksia uudemmassa JVM: ssä, jos tilaisuus tulee esiin.

Tässä skenaariossa peruskokoelumenetelmä muistuttaa skenaarion 1 lähestymistapaa, paitsi että bootstrap J2SDK: n pitäisi olla korkein käyttämäsi versio:

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

Tämä ei kuitenkaan riitä; sinun on myös tehtävä jotain fiksua Java-koodissasi, jotta se toimii oikein eri J2SE-versioissa.

Yksi vaihtoehto on käyttää Java-esiprosessoria (vähintään # ifdef / # else / # endif tuki) ja luo tosiasiallisesti erilaisia ​​koontiversioita J2SE-alustan eri versioille. Vaikka J2SDK: lta puuttuu asianmukainen esikäsittelytuki, tällaisista työkaluista ei ole pulaa Webissä.

Useiden jakelujen hallinta eri J2SE-alustoille on kuitenkin aina ylimääräinen taakka. Ylimääräisen ennakoinnin avulla voit päästä eroon sovelluksesi yhden koontiversion jakamisesta. Tässä on esimerkki siitä, miten se tehdään (URL-testi1 on yksinkertainen luokka, joka poimii URL-osoitteesta useita mielenkiintoisia bittejä):

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