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ýldatum splatnosti()
označuje datum, do kterého musí být stub vyplněn kódemvý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
, StringBuffer
nebo 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ě) paralelujava.util.stream.Stream
objekt s touto kolekcí jako zdrojem.výchozí Stream stream ()
: Vrátit posloupnostProud
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í.