Ohjelmointi

Käyttöliittymän kuusi roolia

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 tunnisteen
  • eräpäivä() tunnistaa päivämäärän, johon mennessä tynkä on täytettävä koodilla
  • kehittä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.StringBufferja 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, Merkkijonoja 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, StringBufferja 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, StringBuffertai 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) yhdensuuntainen java.util.stream.Stream esine, jonka lähde on tämä kokoelma.
  • oletussuoratoisto (): Palaa peräkkäin Suoratoisto 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.