Java-kielen uudet käyttäjät kokevat usein sekaannusta. Heidän hämmennys johtuu suurelta osin Javan eksoottisten kieliominaisuuksien, kuten geneeristen ja lambdas-palettien, paletista. Jopa yksinkertaisemmat ominaisuudet, kuten käyttöliittymät, voivat olla hämmentäviä.
Viime aikoina kohtasin kysymyksen siitä, miksi Java tukee rajapintoja (via käyttöliittymä
ja työvälineet
avainsanat). Kun aloitin Java-oppimisen 1990-luvulla, tähän kysymykseen vastattiin usein sanomalla, että käyttöliittymät kiertävät Javan puutteen tuesta usean toteutuksen perintö (lapsiluokit periytyvät useista vanhempaluokista). Liitännät palvelevat kuitenkin paljon enemmän kuin kludge. Tässä viestissä esittelen kuusi roolia, joita rajapinnoilla on Java-kielellä.
Tietoja moninkertaisesta perinnöstä
Termi moniperintö käytetään yleisesti viittaamaan lapsiluokkaan, joka perii useita vanhempaluokkia. Java, termi usean toteutuksen perintö tarkoittaa samaa. Java tukee myös usean käyttöliittymän perintö jossa lapsiliittymä voi periä useista vanhempien rajapinnoista. Saat lisätietoja moniperinnöstä (mukaan lukien kuuluisa timanttiongelma) tutustumalla Wikipedian moniperintökohteeseen.
Rooli 1: Merkintätyyppien ilmoittaminen
käyttöliittymä
avainsana on ylikuormitettu käytettäväksi merkintätyyppien ilmoittamisessa. Esimerkiksi Listaus 1 esittää yksinkertaisen Tynkä
merkinnän tyyppi.
Listaus 1. Stub.java
tuo java.lang.annotation.Retention; tuo java.lang.annotation.RetentionPolicy; @Retention (RetentionPolicy.RUNTIME) public @interface Stub {int id (); // Puolipiste lopettaa elementti-ilmoituksen. Merkkijono eräpäivä (); Merkkijonokehittäjä () oletuksena "määrittelemätön"; }
Tynkä
kuvaa luokan merkinnät (merkintätyyppiset esiintymät), jotka merkitsevät keskeneräisiä tyyppejä ja menetelmiä. Sen julistus alkaa otsikosta, joka koostuu @
jota seuraa käyttöliittymä
avainsana ja sen nimi.
Tämä merkintätyyppi ilmoittaa kolme elementtejä, jonka voit ajatella menetelmän otsikoina:
tunnus ()
palauttaa tynkille kokonaislukupohjaisen tunnisteeneräpäivä()
tunnistaa päivämäärän, johon mennessä tynkä on täytettävä koodillakehittäjä()
yksilöi tynkän täyttämisestä vastaavan kehittäjän
Elementti palauttaa minkä tahansa sille merkinnällä osoitetun arvon. Jos elementtiä ei ole määritetty, sen oletusarvo (seuraa oletuksena
avainsana ilmoituksessa) palautetaan.
Listaus 2 osoittaa Tynkä
keskeneräisen yhteydessä Ota yhteyttä
luokka; luokka ja sen yksinäinen menetelmä on merkitty @Tynkä
merkinnät.
Listaus 2. Ota yhteyttäMgr.java
@Stub (id = 1, dueDate = "31.12.2016") public class ContactMgr {@Stub (id = 2, dueDate = "31.06.2016, developer =" Marty ") public void addContact (String contactID) ) {}}
Merkintätyyppinen ilmentymä alkaa @
, jota seuraa merkintätyypin nimi. Täällä, ensimmäinen @Tynkä
merkintä tunnistaa itsensä numeroksi 1, jonka eräpäivä on 31. joulukuuta 2016. Tynnyrin täyttämisestä vastaavaa kehittäjää ei ole vielä määritetty. Sen sijaan toinen @Tynkä
merkintä tunnistaa itsensä numeroksi 2, jonka eräpäivä on 31. kesäkuuta 2016. Tynnyrin täyttämisestä vastaava kehittäjä on nimeltään Marty.
Merkinnät on käsiteltävä millään tavalla. (Tynkä
on merkitty @Retention (RetentionPolicy.RUNTIME)
jotta se voidaan käsitellä.) Luettelossa 3 esitetään a StubFinder
luokan raportoiva sovellus @Tynkä
merkinnät.
Listaus 3. StubFinder.java
tuo java.lang.reflect.Method; public class StubFinder {public static void main (String [] args) heittää poikkeuksen {if (args.pituus! = 1) {System.err.println ("käyttö: java StubFinder-luokkatiedosto"); palata; } Class clazz = Class.forName (argumentit [0]); if (clazz.isAnnotationPresent (tynkä.luokka)) {Stub stub = clazz.getAnnotation (tynkä.luokka); System.out.println ("Stub ID =" + stub.id ()); System.out.println ("Stub Date =" + stub.dueDate ()); System.out.println ("Stub Developer =" + stub.developer ()); System.out.println (); } Method [] method = clazz.getMethods (); for (int i = 0; i <method.length; i ++) if (method [i] .isAnnotationPresent (Stub.luokka)) {Stub stub = metodit [i] .getAnnotation (Stub.class); System.out.println ("Stub ID =" + stub.id ()); System.out.println ("Stub Date =" + stub.dueDate ()); System.out.println ("Stub Developer =" + stub.developer ()); System.out.println (); }}}
Luettelo 3: sta main ()
-menetelmä käyttää Javan Reflection-sovellusliittymää kaikkien hakemiseen @Tynkä
merkinnät, jotka etuliittävät luokan ilmoituksen sekä sen metodideklarukset.
Koosta ilmoitukset 1–3 seuraavasti:
javac * .java
Suorita tuloksena oleva sovellus seuraavasti:
java StubFinder Ota yhteyttä
Ota huomioon seuraava tulos:
Oman tunnus = 1 Oman päivämäärä = 31.12.2016 Oman kehittäjä = määrittelemätön Oman tunnus = 2 Oman päivämäärä = 31.06.2016 Oman kehittäjän = Marty
Saatat väittää, että merkintätyypeillä ja niiden merkinnöillä ei ole mitään tekemistä liitäntöjen kanssa. Loppujen lopuksi luokan ilmoitukset ja työvälineet
avainsanaa ei ole läsnä. En kuitenkaan hyväksy tätä johtopäätöstä.
@käyttöliittymä
on samanlainen kuin luokassa
siinä, että se esittelee tyypin. Sen elementit ovat menetelmiä, jotka toteutetaan (kulissien takana) arvojen palauttamiseksi. Elementit oletuksena
arvot palauttavat arvot, vaikka niitä ei olisikaan huomautuksissa, jotka ovat samanlaisia kuin objektit. Ei-oletuselementtien on aina oltava merkinnässä, ja ne on ilmoitettava palauttamaan arvo. Siksi on kuin luokka olisi ilmoitettu ja että luokka toteuttaa käyttöliittymän menetelmät.
Rooli 2: Kuvaus toteutuksesta riippumattomista ominaisuuksista
Eri luokat voivat tarjota yhteisen kyvyn. Esimerkiksi java.nio.CharBuffer
, javax.swing.text.Segment
, java.lang.String
, java.lang.StringBuffer
ja java.lang.StringBuilder
luokat tarjoavat pääsyn luettaviin hiiltyä
arvot.
Kun luokat tarjoavat yhteisen ominaisuuden, tämän ominaisuuden käyttöliittymä voidaan purkaa uudelleenkäyttöä varten. Esimerkiksi käyttöliittymä "luettavaan hiiltyä
arvot "- ominaisuus on purettu java.lang.CharSequence
käyttöliittymä. CharSequence
tarjoaa yhtenäisen, vain luku -oikeuden moniin erilaisiin hiiltyä
sekvenssit.
Oletetaan, että sinua pyydettiin kirjoittamaan pieni sovellus, joka laskee kunkin pienen kirjaimen esiintymien määrän CharBuffer
, Merkkijono
ja StringBuffer
esineitä. Hieman miettinyt, saatat keksiä Listaus 4. (Vältäisin tyypillisesti kulttuurisesti puolueellisia ilmaisuja, kuten ch - 'a'
, mutta haluan pitää esimerkin yksinkertaisena.)
Listaus 4. Freq.java
(versio 1)
tuo java.nio.CharBuffer; public class Freq {public static void main (String [] args) {if (args.pituus! = 1) {System.err.println ("käyttö: java Freq -teksti"); palata; } analysoiS (argumentit [0]); analySB (uusi StringBuffer (argumentit [0])); analysoi CB (CharBuffer.wrap (argumentit [0])); } staattinen void analyysiCB (CharBuffer cb) {int laskee [] = uusi int [26]; kun (cb.hasRemaining ()) {char ch = cb.get (); jos (ch> = 'a' && ch <= 'z') laskee [ch - 'a'] ++; } (int i = 0; i <laskee.pituus; i ++) System.out.printf ("% c: n määrä on% d% n", (i + 'a'), laskee [i]); System.out.println (); } staattinen void analysoi (String s) {int laskee [] = uusi int [26]; sillä (int i = 0; i = 'a' && ch <= 'z') laskee [ch - 'a'] ++; } (int i = 0; i <laskee.pituus; i ++) System.out.printf ("% c: n määrä on% d% n", (i + 'a'), laskee [i]); System.out.println (); } staattinen void analysisSB (StringBuffer sb) {int laskee [] = uusi int [26]; sillä (int i = 0; i = 'a' && ch <= 'z') laskee [ch - 'a'] ++; } (int i = 0; i <laskee.pituus; i ++) System.out.printf ("% c: n määrä on% d% n", (i + 'a'), laskee [i]); System.out.println (); }}
Luettelossa 4 on kolme erilaista analysoida
menetelmät pienten kirjainten lukumäärän tallentamiseksi ja tämän tilastotiedon tuottamiseksi. vaikkakin Merkkijono
ja StringBuffer
variantit ovat käytännössä identtisiä (ja saatat olla kiusaus luoda yksi menetelmä molemmille), CharBuffer
variantti eroaa merkittävämmin.
Listaus 4 paljastaa paljon päällekkäisiä koodeja, mikä johtaa suurempaan luokkatiedostoon kuin on tarpeen. Voit saavuttaa saman tilastollisen tavoitteen työskentelemällä CharSequence
käyttöliittymä. Listaus 5 esittää vaihtoehtoisen version taajuussovelluksesta, joka perustuu CharSequence
.
Listaus 5. Freq.java
(versio 2)
tuo java.nio.CharBuffer; public class Freq {public static void main (String [] args) {if (args.pituus! = 1) {System.err.println ("käyttö: java Freq -teksti"); palata; } analysoi (argumentit [0]); analysoi (uusi StringBuffer (argumentit [0])); analysoi (CharBuffer.wrap (args [0])); } staattinen tyhjiöanalyysi (CharSequence cs) {int laskee [] = uusi int [26]; sillä (int i = 0; i = 'a' && ch <= 'z') laskee [ch - 'a'] ++; } (int i = 0; i <laskee.pituus; i ++) System.out.printf ("% c: n määrä on% d% n", (i + 'a'), laskee [i]); System.out.println (); }}
Listaus 5 paljastaa paljon yksinkertaisemman sovelluksen, joka johtuu koodaamisesta analysoida()
saada a CharSequence
Perustelu. Koska jokainen Merkkijono
, StringBuffer
ja CharBuffer
työvälineet CharSequence
, on laillista siirtää tämäntyyppisiä instansseja analysoida()
.
Toinen esimerkki
Ilmaisu CharBuffer.wrap (argumentit [0])
on toinen esimerkki a Merkkijono
vastustaa tyypin parametria CharSequence
.
Yhteenvetona voidaan todeta, että käyttöliittymän toinen rooli on kuvata toteutuksesta riippumaton kyky. Koodaamalla käyttöliittymälle (kuten CharSequence
) luokan sijaan (kuten Merkkijono
, StringBuffer
tai CharBuffer
), vältät päällekkäistä koodia ja tuotat pienempiä luokkatiedostoja. Tässä tapauksessa saavutin yli 50%: n vähennyksen.
Rooli 3: Kirjaston evoluution helpottaminen
Java 8 esitteli meille erittäin hyödyllisen lambda-kieliominaisuuden ja Streams-sovellusliittymän (keskittyen siihen, mikä laskenta tulisi suorittaa sen sijaan, miten se tulisi suorittaa). Lambdas and Streams helpottaa kehittäjien rinnakkaisuuden lisäämistä sovelluksiinsa. Valitettavasti Java Collections Framework ei voinut hyödyntää näitä ominaisuuksia tarvitsematta laajaa uudelleenkirjoitusta.
Jos haluat parantaa kokoelmia nopeasti käytettäväksi stream-lähteinä ja kohteina, tuki: oletusmenetelmät (tunnetaan myös laajennusmenetelmät), jotka ovat ei-staattisia menetelmiä, joiden otsikoihin on lisätty oletuksena
avainsana ja toimituskoodirungot lisättiin Java-käyttöliittymäominaisuuteen. Oletusmenetelmät kuuluvat rajapintoihin; rajapintoja toteuttavat luokat eivät ota niitä käyttöön (mutta ne voidaan ohittaa). Niihin voidaan myös vedota objektiviittausten kautta.
Kun oletusmenetelmistä tuli osa kieltä, seuraavat menetelmät lisättiin java.util.Kokoelma
käyttöliittymä, joka tarjoaa sillan kokoelmien ja suoratoistojen välille:
oletusvirta parallelStream ()
: Palauta (mahdollisesti) yhdensuuntainenjava.util.stream.Stream
esine, jonka lähde on tämä kokoelma.oletussuoratoisto ()
: Palaa peräkkäinSuoratoisto
esine, jonka lähde on tämä kokoelma.
Oletetaan, että olet ilmoittanut seuraavan java.util.List
muuttuja ja tehtävälauseke:
Luettele internalPlanets = Arrays.asList ("Elohopea", "Venus", "Maa", "Mars");
Toistat perinteisesti tämän kokoelman seuraavasti:
kohteelle (String internalPlanet: innerPlanets) System.out.println (internalPlanet);
Voit korvata tämän ulkoisen iteraation, joka keskittyy laskennan suorittamiseen, Stream-pohjaiseen sisäiseen iteraatioon, joka keskittyy suoritettavaan laskentaan seuraavasti:
internalPlanets.stream (). forEach (System.out :: println); internalPlanets.parallelStream (). forEach (System.out :: println);
Tässä, innerPlanets.stream ()
ja innerPlanets.parallelStream ()
palauttaa peräkkäiset ja rinnakkaiset virrat aiemmin luotuun Lista
lähde. Ketjutettu palautettuun Suoratoisto
viitteet ovat forEach (System.out :: println)
, joka toistaa virtauksen kohteet ja kutsuu System.out.println ()
(tunnistettu System.out :: println
method reference) kullekin objektille merkkijonon esityksen tulostamiseksi vakiolähtövirtaan.
Oletusmenetelmät voivat tehdä koodista luettavamman. Esimerkiksi java.util.Kokoelmat
luokka ilmoittaa a void sort (Luetteloluettelo, vertailija c)
staattinen menetelmä luettelon sisällön lajittelemiseksi määritetyn vertailijan mukaan. Java 8 lisäsi a oletusarvoinen tyhjä lajittelu (vertailija c)
menetelmä Lista
käyttöliittymä, jotta voit kirjoittaa luettavamman myList.sort (vertailija);
sijasta Collections.sort (myList, vertailija);
.
Rajapintojen tarjoama oletusmenetelmärooli on antanut uuden elämän Java Collections Frameworkille. Voisit harkita tätä roolia omissa vanhoissa käyttöliittymäkohtaisissa kirjastoissa.