Programavimas

„Java“ programavimas. 2 dalis. Liejimo kaina

Šiame antrajame mūsų serijos straipsnyje apie „Java“ našumą dėmesys krypsta į liejimą - kas tai yra, ką jis kainuoja ir kaip mes galime (kartais) to išvengti. Šį mėnesį mes pradedame nuo greito klasių, objektų ir nuorodų pagrindų apžvalgos, tada peržiūrime keletą rimtų rezultatų (šoninėje juostoje, kad neįžeistumėt!) Ir gaires. operacijų tipai, kurie greičiausiai sukelia „Java“ virtualiosios mašinos (JVM) virškinimą. Galiausiai mes baigiame nuodugniai išnagrinėję, kaip išvengti bendrų klasės struktūrizavimo efektų, galinčių sukelti liejimą.

„Java“ programavimas: perskaitykite visą seriją!

  • 1 dalis. Sužinokite, kaip sumažinti programos pridėtines išlaidas ir pagerinti našumą valdant objektų kūrimą ir šiukšlių surinkimą
  • 2 dalis. Sumažinkite pridėtines ir vykdymo klaidas naudodami saugaus tipo kodą
  • 3 dalis. Pažiūrėkite, kaip kolekcijų alternatyvos vertina našumą, ir sužinokite, kaip maksimaliai išnaudoti kiekvieną tipą

Objektų ir nuorodų tipai „Java“

Praėjusį mėnesį aptarėme pagrindinį „Java“ primityvių tipų ir objektų skirtumą. Tiek pirmykščių tipų skaičių, tiek jų tarpusavio ryšius (ypač konversijas tarp tipų) nustato kalbos apibrėžimas. Kita vertus, objektai yra neriboto tipo ir gali būti susiję su bet kokiu kitu tipų skaičiumi.

Kiekvienas „Java“ programos klasės apibrėžimas apibrėžia naujo tipo objektą. Tai apima visas „Java“ bibliotekų klases, todėl bet kuri programa gali naudoti šimtus ar net tūkstančius skirtingų tipų objektų. Kai kuriuos iš šių tipų „Java“ kalbos apibrėžimas nurodo kaip turinčius tam tikrus specialius naudojimo būdus ar tvarkymą (pvz., java.lang.StringBuffer dėl java.lang.Stringas sujungimo operacijos). Be šių kelių išimčių, „Java“ kompiliatorius ir JVM, naudojami programai vykdyti, visus tipus traktuoja iš esmės vienodai.

Jei klasės apibrėžime nenurodyta (naudojant tęsiasi sąlyga klasės apibrėžimo antraštėje) kita klasė kaip tėvų ar superklasė, ji netiesiogiai pratęsia java.lang.Object klasė. Tai reiškia, kad kiekviena klasė galiausiai tęsiasi java.lang.Object, tiesiogiai arba per vieno ar kelių tėvų klasių lygius.

Patys objektai visada yra klasių ir objektų egzemplioriai tipo yra klasė, kuriai tai yra egzempliorius. „Java“ programoje mes niekada tiesiogiai nesusiduriame su objektais; mes dirbame su nuorodomis į objektus. Pavyzdžiui, eilutė:

 java.awt.Komponentas „myComponent“; 

nesukuria java.awt.Komponentas objektas; tai sukuria tipinį kintamąjį java.lang.Komponentas. Nors nuorodos turi tipus, kaip ir objektai, nėra tikslaus nuorodų ir objektų tipų atitikties - gali būti ir referencinė vertė niekinis, to paties tipo objektas kaip nuoroda, arba bet kurio poklasio (t. y. klasės, kilusio iš) objektas, nuorodos tipas. Šiuo konkrečiu atveju java.awt.Komponentas yra abstrakti klasė, todėl žinome, kad niekada negali būti tokio paties tipo objekto, kaip mūsų nuoroda, tačiau tikrai gali būti to nuorodos tipo poklasių objektų.

Polimorfizmas ir liejimas

Nuorodos tipas nustato, kaip nurodytas objektas - tai yra objektas, kuris yra nuorodos vertė - gali būti naudojamas. Pavyzdžiui, aukščiau pateiktame pavyzdyje koduokite naudodami „myComponent“ galėtų pasinaudoti bet kuriuo iš klasės apibrėžtų metodų java.awt.Komponentas, ar bet kuris jo superklasė, nurodytame objekte.

Tačiau metodą, kurį faktiškai vykdo skambutis, lemia ne pačios nuorodos tipas, o nurodyto objekto tipas. Tai yra pagrindinis polimorfizmas - poklasiai gali nepaisyti tėvų klasėje apibrėžtų metodų, siekiant įgyvendinti skirtingą elgesį. Mūsų kintamojo pavyzdžio atveju, jei nurodytas objektas iš tikrųjų buvo java.awt.Button, būsenos pokytis, atsirandantis dėl a „setLabel“ („Push Me“) skambutis būtų kitoks nei gautas, jei nurodytas objektas būtų java.awt.Label.

Be klasės apibrėžimų, „Java“ programos taip pat naudoja sąsajų apibrėžimus. Sąsaja nuo klasės skiriasi tuo, kad sąsajoje nurodomas tik elgesio rinkinys (o kai kuriais atvejais ir konstantos), o klasėje - įgyvendinimas. Kadangi sąsajos neapibrėžia įgyvendinimo, objektai niekada negali būti sąsajos egzemplioriai. Tačiau jie gali būti klasių, įgyvendinančių sąsają, egzemplioriai. Literatūra gali būti sąsajos tipų, tokiu atveju nurodyti objektai gali būti bet kurios klasės, kuri įgyvendina sąsają, pavyzdžiai (tiesiogiai arba per kurią nors iš protėvių klasių).

Liejimas yra naudojamas konvertuoti tarp tipų, visų pirma tarp tipinių tipų, liejimo operacijos tipui, kuris čia mus domina. Operacijos po upe (taip pat vadinama didėjančios konversijos „Java“ kalbos specifikacijoje) konvertuoti poklasio nuorodą į protėvių klasės nuorodą. Ši liejimo operacija paprastai yra automatinė, nes ji visada saugi ir ją gali tiesiogiai atlikti kompiliatorius.

Nusileidusios operacijos (taip pat vadinama siaurindamas konversijas „Java“ kalbos specifikacijoje) konvertuoti protėvių klasės nuorodą į poklasio nuorodą. Ši perdavimo operacija sukuria vykdymą, nes „Java“ reikalauja, kad aktoriai būtų tikrinami vykdymo metu, kad įsitikintumėte, jog jis galioja. Jei nurodytas objektas nėra nei „cast“ tikslinio tipo, nei to tipo poklasio pavyzdys, bandyti perduoti neleidžiama ir jis turi išmesti java.lang.ClassCastException.

egzempliorius „Java“ operatorius leidžia jums nustatyti, ar leidžiama atlikti konkrečią liejimo operaciją, iš tikrųjų nebandant operacijos. Kadangi patikrinimo atlikimo kaina yra daug mažesnė nei išimties, kurią sugeneravo neleistinas dalyvių bandymas, paprastai protinga naudoti egzempliorius išbandykite bet kada, kai nesate tikri, ar nuorodos tipas yra toks, kokio norėtumėte. Prieš tai darydami, turėtumėte įsitikinti, kad turite pagrįstą būdą, kaip elgtis su nepageidaujamo tipo nuoroda - kitaip jūs taip pat galite tiesiog leisti išmesti išimtį ir tvarkyti ją aukštesniame jūsų kodo lygyje.

Atsargiai vėjai

„Casting“ leidžia naudoti bendrąjį programavimą „Java“, kur kodas rašomas dirbti su visais klasių objektais, kilusiais iš kokios nors bazinės klasės (dažnai java.lang.Object, naudingumo klasėms). Tačiau liejimo naudojimas sukelia unikalų problemų rinkinį. Kitame skyriuje mes apžvelgsime poveikį našumui, tačiau pirmiausia apsvarstykime poveikį pačiam kodui. Štai pavyzdys, naudojant bendrąjį java.lang.vektorius kolekcijos klasė:

 privatus vektorius someNumbers; ... public void doSomething () {... int n = ... Sveikasis skaičius = (Sveikasis skaičius) someNumbers.elementAt (n); ...} 

Šis kodas pateikia galimas aiškumo ir priežiūros problemas. Jei kažkas, išskyrus pradinį kūrėją, tam tikru metu modifikuos kodą, jis gali pagrįstai manyti, kad galėtų pridėti a java.lang.Dvigubai į someNumbers kolekcijas, nes tai yra java.lang.Skaičius. Viskas būtų gerai, jei jis tai išbandytų, tačiau tam tikru neapibrėžtu įvykdymo momentu jis greičiausiai gautų java.lang.ClassCastException mesti, kai bandymas mesti į a java.lang.Integer buvo įvykdytas už jo pridėtinę vertę.

Čia problema ta, kad liejimo funkcija apeina „Java“ kompiliatoriuje įdiegtus saugos patikrinimus; programuotojas baigia ieškoti klaidų vykdymo metu, nes kompiliatorius jų neužfiksuos. Tai savaime nėra pražūtinga, tačiau tokio tipo naudojimo klaidos dažnai slepiasi gana protingai, kai bandote savo kodą, kad atskleistų save, kai programa pradedama gaminti.

Nenuostabu, kad technikos, leidžiančios kompiliatoriui aptikti tokio tipo naudojimo klaidas, palaikymas yra vienas iš labiausiai reikalaujamų „Java“ patobulinimų. Dabar „Java“ bendruomenės procese vykdomas projektas, kuriame tiriamas tik šio palaikymo pridėjimas: projekto numeris JSR-000014, „Bendrų tipų pridėjimas prie„ Java “programavimo kalbos“ (daugiau informacijos rasite toliau esančiame skyriuje „Ištekliai“). ateinantį kitą mėnesį, mes panagrinėsime šį projektą išsamiau ir aptarsime, kaip jis gali padėti, ir kur gali palikti mums norą daugiau.

Spektaklio klausimas

Jau seniai pripažinta, kad perdavimas gali pakenkti „Java“ našumui ir kad galite pagerinti našumą sumažindami gausiai naudojamo kodo perdavimą. Metodo iškvietimai, ypač skambučiai per sąsajas, taip pat dažnai minimi kaip galimi našumo trūkumai. Tačiau dabartinė JVM karta nuėjo toli nuo savo pirmtakų, todėl verta patikrinti, ar šie principai laikosi šiandien.

Šiam straipsniui sukūriau seriją bandymų, norėdamas sužinoti, kiek šie veiksniai yra svarbūs dabartinių JVM veikimui. Testo rezultatai apibendrinti į dvi šoninės juostos lenteles: 1 lentelę, nurodančią skambučio pridėtines išlaidas ir 2 lenteles liejimo pridėtines išlaidas. Visą testavimo programos šaltinio kodą taip pat galite rasti internete (daugiau informacijos rasite toliau esančiame skyriuje „Ištekliai“).

Apibendrinant šias išvadas skaitytojams, kurie nenori plačiau apžvelgti lentelių detalių, tam tikri metodų iškvietimai ir perdavimas vis dar yra gana brangūs, kai kuriais atvejais trunka beveik tiek pat laiko, kiek paprastas objekto paskirstymas. Jei įmanoma, reikėtų vengti tokio tipo operacijų kode, kurį reikia optimizuoti, kad jis veiktų.

Visų pirma skambučiai į nepaisomus metodus (metodai, kurie yra nepaisomi bet kurioje įkeltoje klasėje, o ne tik tikroje objekto klasėje) ir skambučiai per sąsajas yra daug brangesni nei paprastų metodų iškvietimai. Teste naudojama „HotSpot Server JVM 2.0“ beta versija netgi pavers daug paprastų metodų iškvietimų į tiesioginį kodą, išvengiant bet kokių pridėtinių išlaidų tokioms operacijoms. Tačiau „HotSpot“ rodo blogiausius rezultatus tarp išbandytų JVM, kai naudojami nepaisyti metodai ir skambučiai per sąsajas.

Atliekant atranką (žinoma, žemyn), išbandyti JVM paprastai palaiko pasiekiamą lygį. „HotSpot“ atlieka išskirtinį darbą daugelyje etaloninių bandymų ir, kaip ir metodo iškvietimai, daugeliu paprastų atvejų gali beveik visiškai pašalinti liejimo išlaidas. Esant sudėtingesnėms situacijoms, tokioms kaip „casting“ ir paskambinimas į nepaisomus metodus, visi išbandyti „JVM“ rodo pastebimą veikimo pablogėjimą.

Išbandyta „HotSpot“ versija taip pat parodė ypač prastą našumą, kai objektas iš eilės buvo perduodamas skirtingiems nuorodų tipams (užuot visada metamas to paties tipo). Tokia padėtis nuolat kyla tokiose bibliotekose kaip „Swing“, kuriose naudojama gili klasių hierarchija.

Daugeliu atvejų abiejų metodų iškvietimų ir perdavimo mastai yra nedideli, palyginti su objektų paskirstymo laikais, apžvelgtais praėjusio mėnesio straipsnyje. Tačiau šios operacijos dažnai bus naudojamos kur kas dažniau nei objektų paskirstymas, todėl jos vis tiek gali būti reikšmingas našumo problemų šaltinis.

Likusioje šio straipsnio dalyje aptarsime keletą konkrečių būdų, kaip sumažinti kodo perdavimo poreikį. Konkrečiai, mes pažvelgsime į tai, kaip liejimas dažnai atsiranda dėl to, kaip poklasiai sąveikauja su pagrindinėmis klasėmis, ir išnagrinėsime keletą būdų, kaip pašalinti šio tipo liejimą. Kitą mėnesį antroje šio liejimo dalyje aptarsime dar vieną dažną liejimo priežastį - bendrų kolekcijų naudojimą.

Bazinės klasės ir liejimas

Yra keletas įprastų „Java“ programų perdavimo būdų. Pavyzdžiui, liejimas dažnai naudojamas bendram kai kurių bazinės klasės funkcijų valdymui, kuris gali būti išplėstas keliais poklasiais. Šis kodas rodo šiek tiek sugalvotą šio naudojimo pavyzdį:

 // paprasta bazinė klasė su poklasiais public abstract class BaseWidget {...} public class SubWidget extends BaseWidget {... public void doSubWidgetSomething () {...}} ... // bazinė klasė su poklasiais, naudojant išankstinį rinkinį klasių viešosios abstrakčios klasės „BaseGorph“ {// valdiklis, susietas su šiuo „Gorph“ privačiu pagrindu „BaseWidget myWidget“; ... // nustatykite valdiklį, susietą su šiuo „Gorph“ (leidžiamas tik poklasiams), apsaugotą „void setWidget“ (valdiklis „BaseWidget“) {myWidget = valdiklis; } // gaukite valdiklį, susietą su šiuo „Gorph“ viešuoju „BaseWidget“ getWidget () {return myWidget; } ... // grąžinti „Gorph“ su tam tikru sąryšiu su šiuo „Gorph“ // tai visada bus to paties tipo, kaip jis yra iškviestas, bet mes galime // grąžinti tik mūsų bazinės klasės viešo abstrakto „BaseGorph otherGorph () {“ egzempliorių. ..}} // „Gorph“ poklasis, naudojant „Widget“ poklasio viešąją klasę „SubGorph“ pratęsia „BaseGorph“ {// grąžina „Gorph“ su tam tikru ryšiu su šia „Gorph“ viešąja „BaseGorph“ kitaGorph () {...} ... public void anyMethod () {.. . // nustatykite valdiklį, kuriame naudojame „SubWidget“ valdiklį = ... setWidget (valdiklis); ... // naudokite mūsų valdiklį ((SubWidget) getWidget ()). doSubWidgetSomething (); ... // naudokite mūsų otherGorph SubGorph other = (SubGorph) otherGorph (); ...}}