Programování

Šest rolí rozhraní

Nováčci v jazyce Java často pociťují zmatek. Jejich zmatek je do značné míry způsoben paletou exotických jazykových prvků Java, jako jsou generické a lambdas. I jednodušší funkce, jako jsou rozhraní, však mohou být záhadné.

Nedávno jsem narazil na otázku, proč Java podporuje rozhraní (přes rozhraní a nářadí klíčová slova). Když jsem se v 90. letech začal učit Javu, na tuto otázku jsem často odpovídal tvrzením, že rozhraní obcházejí nedostatek podpory Javy pro vícenásobná implementační dědičnost (podřízené třídy dědí z více nadřazených tříd). Rozhraní však slouží mnohem víc než kludge. V tomto příspěvku představuji šest rolí, které rozhraní hrají v jazyce Java.

O vícenásobném dědictví

Termín vícenásobné dědictví se běžně používá k označení podřízené třídy zděděné z více nadřazených tříd. V Javě termín vícenásobná implementační dědičnost znamená totéž. Java také podporuje dědičnost více rozhraní ve kterém může podřízené rozhraní dědit z více nadřazených rozhraní. Chcete-li se dozvědět více o vícenásobné dědičnosti (včetně slavného problému s diamanty), podívejte se na položku Vícenásobné dědictví Wikipedie.

Role 1: Deklarace typů anotací

The rozhraní klíčové slovo je přetíženo pro použití při deklaraci typů anotací. Například výpis 1 představuje jednoduchý Pahýl typ poznámky.

Výpis 1. Stub.java

importovat java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention (RetentionPolicy.RUNTIME) public @interface Stub {int id (); // Středník ukončuje deklaraci prvku. Řetězec dueDate (); Řetězcový vývojář () výchozí "nepřiřazeno"; }

Pahýl popisuje kategorii anotace (instance typu anotace), které označují nedokončené typy a metody. Jeho deklarace začíná hlavičkou skládající se z @ následovaný rozhraní klíčové slovo následované jeho názvem.

Tento typ poznámky deklaruje tři elementy, které si můžete představit jako hlavičky metod:

  • id () vrátí celočíselný identifikátor pro pahýl
  • datum splatnosti() označuje datum, do kterého musí být stub vyplněn kódem
  • vývojář() identifikuje vývojáře odpovědného za vyplnění útržku

Element vrací jakoukoli hodnotu, která mu je přiřazena anotací. Pokud prvek není zadán, jeho výchozí hodnota (po výchozí klíčové slovo v deklaraci).

Výpis 2 ukazuje Pahýl v kontextu nedokončeného Kontaktovat třída; třída a její osamělá metoda byly anotovány @Pahýl anotace.

Výpis 2. ContactMgr.java

@Stub (id = 1, dueDate = "31.12.2016") veřejná třída ContactMgr {@Stub (id = 2, dueDate = "06/31/2016", developer = "Marty") public void addContact (řetězec contactID ) {}}

Instance typu anotace začíná @, za kterým následuje název typu poznámky. Tady první @Pahýl anotace se identifikuje jako číslo 1 s datem splatnosti 31. prosince 2016. Vývojář odpovědný za vyplnění útržku ještě nebyl přidělen. Naproti tomu druhý @Pahýl anotace se identifikuje jako číslo 2 s datem splatnosti 31. června 2016. Vývojář odpovědný za vyplnění útržku je označen jako Marty.

Aby byly poznámky užitečné, musí být zpracovány. (Pahýl je anotován @Retention (RetentionPolicy.RUNTIME) aby jej bylo možné zpracovat.) Výpis 3 představuje a StubFinder aplikace, která hlásí třídu @Pahýl anotace.

Výpis 3. StubFinder.java

import java.lang.reflect.Method; public class StubFinder {public static void main (String [] args) throws Exception {if (args.length! = 1) {System.err.println ("usage: java StubFinder classfile"); vrátit se; } Class clazz = Class.forName (args [0]); if (clazz.isAnnotationPresent (Stub.class)) {Stub stub = clazz.getAnnotation (Stub.class); System.out.println ("ID stubu =" + stub.id ()); System.out.println ("Stub Date =" + stub.dueDate ()); System.out.println ("Stub Developer =" + stub.developer ()); System.out.println (); } Metoda [] metody = clazz.getMethods (); for (int i = 0; i <methods.length; i ++) if (methods [i] .isAnnotationPresent (Stub.class)) {Stub stub = methods [i] .getAnnotation (Stub.class); System.out.println ("ID pahýlu =" + stub.id ()); System.out.println ("Stub Date =" + stub.dueDate ()); System.out.println ("Stub Developer =" + stub.developer ()); System.out.println (); }}}

Výpis 3 hlavní() metoda používá Java Reflection API k načtení všech @Pahýl anotace, které předponou deklarace třídy i deklarací jejích metod.

Zkompilujte výpisy 1 až 3 takto:

javac * .java

Výslednou aplikaci spusťte následovně:

java StubFinder ContactMgr

Měli byste dodržovat následující výstup:

ID stubu = 1 Datum stubu = 31.12.2016 Stub Developer = nepřiřazeno ID stubu = 2 Datum stubu = 31.06.2016 Stub Developer = Marty

Můžete namítnout, že typy anotací a jejich anotace nemají nic společného s rozhraními. Koneckonců, deklarace tříd a nářadí klíčové slovo není k dispozici. S tímto závěrem bych však nesouhlasil.

@rozhraní je podobný třída v tom, že zavádí typ. Jeho prvky jsou metody, které jsou implementovány (v zákulisí) k vrácení hodnot. Prvky s výchozí hodnoty vrací hodnoty, i když nejsou obsaženy v anotacích, které jsou podobné objektům. Nestandardní prvky musí být vždy přítomny v anotaci a musí být deklarovány pro vrácení hodnoty. Proto je to, jako kdyby byla deklarována třída a že třída implementuje metody rozhraní.

Role 2: Popis možností nezávislých na implementaci

Různé třídy mohou nabízet společné možnosti. Například java.nio.CharBuffer, javax.swing.text.Segment, řetězec java.lang, java.lang.StringBuffer, a java.lang.StringBuilder třídy poskytují přístup k čitelným sekvencím char hodnoty.

Když třídy nabízejí společnou schopnost, lze rozhraní pro tuto schopnost extrahovat pro opětovné použití. Například rozhraní k "čitelné posloupnosti char Hodnoty "byly extrahovány do java.lang.CharSequence rozhraní. CharSequence poskytuje jednotný přístup pouze pro čtení k mnoha různým druhům char sekvence.

Předpokládejme, že jste byli požádáni, abyste napsali malou aplikaci, která spočítá počet výskytů každého druhu malých písmen CharBuffer, Tětiva, a StringBuffer předměty. Po chvíli přemýšlení můžete přijít s výpisem 4. (Obvykle bych se vyhýbal kulturně předpojatým výrazům, jako je ch - „a“, ale chci příklad ponechat jednoduchý.)

Výpis 4. Freq.java (verze 1)

import java.nio.CharBuffer; public class Freq {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("usage: java Freq text"); vrátit se; } analyzeS (args [0]); analyzeSB (new StringBuffer (args [0])); analyzeCB (CharBuffer.wrap (args [0])); } static void analyzeCB (CharBuffer cb) {int count [] = new int [26]; while (cb.hasRemaining ()) {char ch = cb.get (); if (ch> = 'a' && ch <= 'z') počítá [ch - 'a'] ++; } for (int i = 0; i <countts.length; i ++) System.out.printf ("Count of% c is% d% n", (i + 'a'), countts [i]); System.out.println (); } static void analyzeS (String s) {int count [] = new int [26]; for (int i = 0; i = 'a' && ch <= 'z') počítá [ch - 'a'] ++; } for (int i = 0; i <countts.length; i ++) System.out.printf ("Count of% c is% d% n", (i + 'a'), countts [i]); System.out.println (); } static void analyzeSB (StringBuffer sb) {int count [] = new int [26]; for (int i = 0; i = 'a' && ch <= 'z') počítá [ch - 'a'] ++; } for (int i = 0; i <countts.length; i ++) System.out.printf ("Count of% c is% d% n", (i + 'a'), countts [i]); System.out.println (); }}

Výpis 4 představuje tři různé analyzovat metody pro záznam počtu výskytů malých písmen a výstup této statistiky. Ačkoliv Tětiva a StringBuffer varianty jsou prakticky identické (a můžete být v pokušení vytvořit jednu metodu pro obě), CharBuffer varianta se výrazněji liší.

Výpis 4 odhaluje spoustu duplicitního kódu, což vede k většímu třídnímu souboru, než je nutné. Stejného statistického cíle byste mohli dosáhnout prací s CharSequence rozhraní. Výpis 5 představuje alternativní verzi frekvenční aplikace, na které je založen CharSequence.

Výpis 5. Freq.java (verze 2)

import java.nio.CharBuffer; public class Freq {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("usage: java Freq text"); vrátit se; } analyzovat (args [0]); analyzovat (nový StringBuffer (args [0])); analyzovat (CharBuffer.wrap (args [0])); } statická analýza prázdnoty (CharSequence cs) {int count [] = new int [26]; for (int i = 0; i = 'a' && ch <= 'z') počítá [ch - 'a'] ++; } for (int i = 0; i <countts.length; i ++) System.out.printf ("Count of% c is% d% n", (i + 'a'), countts [i]); System.out.println (); }}

Výpis 5 odhaluje mnohem jednodušší aplikaci, která je způsobena kodifikací analyzovat() získat a CharSequence argument. Protože každý z Tětiva, StringBuffer, a CharBuffer nářadí CharSequence, je legální předávat instance těchto typů analyzovat().

Další příklad

Výraz CharBuffer.wrap (args [0]) je dalším příkladem předání a Tětiva objekt na parametr typu CharSequence.

Stručně řečeno, druhou rolí rozhraní je popsat schopnost nezávislou na implementaci. Kódováním do rozhraní (například CharSequence) místo do třídy (např Tětiva, StringBuffernebo CharBuffer), vyhnete se duplicitnímu kódu a generujete menší soubory tříd. V tomto případě jsem dosáhl snížení o více než 50%.

Role 3: Usnadnění vývoje knihovny

Java 8 nám představil extrémně užitečnou funkci jazyka lambda a Streams API (se zaměřením na to, jaký výpočet by měl být proveden, spíše než na to, jak by měl být proveden). Díky lambdám a streamům je pro vývojáře mnohem snazší zavést do svých aplikací paralelismus. Bohužel rámec Java Collections Framework nemohl tyto schopnosti využít bez nutnosti rozsáhlého přepsání.

Chcete-li rychle vylepšit sbírky pro použití jako zdroje a cíle streamu, podpora pro výchozí metody (také známý jako metody rozšíření), což jsou nestatické metody, jejichž hlavičky mají předponu výchozí klíčové slovo a který dodává těla kódového kódu, byl přidán do funkce rozhraní Java. Výchozí metody patří k rozhraním; nejsou implementovány (ale lze je přepsat) třídami, které implementují rozhraní. Lze je také vyvolat prostřednictvím odkazů na objekty.

Jakmile se výchozí metody staly součástí jazyka, do metody byly přidány následující metody java.util.Collection rozhraní, které poskytuje most mezi sbírkami a streamy:

  • výchozí Stream parallelStream (): Vrátit (případně) paralelu java.util.stream.Stream objekt s touto kolekcí jako zdrojem.
  • výchozí Stream stream (): Vrátit posloupnost Proud objekt s touto kolekcí jako zdrojem.

Předpokládejme, že jste deklarovali následující java.util.List proměnná a přiřazovací výraz:

Seznam innerPlanets = Arrays.asList ("Merkur", "Venuše", "Země", "Mars");

Tuto kolekci byste tradičně iterovali takto:

pro (String innerPlanet: innerPlanets) System.out.println (innerPlanet);

Tuto externí iteraci, která se zaměřuje na to, jak provádět výpočet, můžete nahradit interní iterací založenou na streamech, která se zaměřuje na to, jaký výpočet provést, a to následovně:

innerPlanets.stream (). forEach (System.out :: println); innerPlanets.parallelStream (). forEach (System.out :: println);

Tady, innerPlanets.stream () a innerPlanets.parallelStream () vrátit sekvenční a paralelní proudy do dříve vytvořeného Seznam zdroj. Připoután k vrácenému Proud reference je forEach (System.out :: println), který iteruje přes objekty proudu a vyvolává System.out.println () (označeno System.out :: println reference metody) pro každý objekt k výstupu jeho řetězcové reprezentace do standardního výstupního proudu.

Díky výchozím metodám je kód čitelnější. Například kolekce java.util třída deklaruje a void sort (seznam, komparátor c) statická metoda pro třídění obsahu seznamu podle zadaného komparátoru. Java 8 přidal a výchozí řazení void (komparátor c) metoda k Seznam rozhraní, takže můžete psát čitelnější myList.sort (komparátor); namísto Collections.sort (myList, komparátor);.

Role výchozí metody nabízená rozhraními dala nový život rozhraní Java Collections Framework. Tuto roli můžete zvážit pro své vlastní starší knihovny založené na rozhraní.

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