Nybegyndere på Java-sproget oplever ofte forvirring. Deres forvirring skyldes i høj grad Java's palet af eksotiske sproglige træk, såsom generiske og lambdas. Selv enklere funktioner som grænseflader kan dog være forvirrende.
For nylig konfronterede jeg et spørgsmål om, hvorfor Java understøtter grænseflader (via interface
og redskaber
nøgleord). Da jeg begyndte at lære Java i 1990'erne, blev dette spørgsmål ofte besvaret ved at sige, at grænseflader kommer omkring Java's manglende støtte til multipel implementeringsarv (barneklasser arver fra flere forældreklasser). Imidlertid tjener grænseflader så meget mere end et kludge. I dette indlæg præsenterer jeg de seks roller, som grænseflader spiller på Java-sproget.
Om flere arv
Begrebet flere arv bruges ofte til at henvise til en barneklasse, der arver fra flere forældreklasser. I Java er udtrykket multipel implementeringsarv betyder det samme. Java understøtter også flere interface arv hvor en underordnet grænseflade kan arve fra flere overordnede grænseflader. For at lære mere om multipel arv (inklusive det berømte diamantproblem), se Wikipedia's Multiple arvspost.
Rolle 1: Erklæring om annoteringstyper
Det interface
nøgleordet er overbelastet til brug ved erklæring af annoteringstyper. For eksempel præsenterer liste 1 en enkel Stub
annoteringstype.
Liste 1. Stub.java
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention (RetentionPolicy.RUNTIME) offentlig @interface Stub {int id (); // Et semikolon afslutter en elementerklæring. String dueDate (); Strengudvikler () er standard "ikke tildelt"; }
Stub
beskriver en kategori af kommentarer (forekomster af annotationstypen), der angiver ufærdige typer og metoder. Dens erklæring begynder med et overskrift bestående af @
efterfulgt af interface
nøgleord efterfulgt af dets navn.
Denne annoteringstype erklærer tre elementer, som du kan tænke på som metodeoverskrifter:
id ()
returnerer en heltal-baseret identifikator til stubbenAfleveringsdato()
identificerer den dato, hvor stubben skal udfyldes med kodeUdvikler()
identificerer den udvikler, der er ansvarlig for at udfylde stubben
Et element returnerer den værdi, der er tildelt det ved en kommentar. Hvis elementet ikke er specificeret, er dets standardværdi (efter Standard
nøgleord i erklæringen) returneres.
Liste 2 viser Stub
i sammenhæng med en ufærdig KontaktMgr
klasse; klassen og dens ensomme metode er blevet kommenteret med @Stub
kommentarer.
Liste 2. KontaktMgr.java
@Stub (id = 1, dueDate = "31/12/2016") offentlig klasse ContactMgr {@Stub (id = 2, dueDate = "06/31/2016", udvikler = "Marty") offentlig ugyldig addContact (String contactID ) {}}
En forekomst af en kommentartype begynder med @
, som efterfølges af navnet på annoteringstypen. Her, den første @Stub
annotation identificerer sig som nummer 1 med en forfaldsdato den 31. december 2016. Udvikleren, der er ansvarlig for at udfylde stubben, er endnu ikke tildelt. I modsætning hertil er det andet @Stub
annotation identificerer sig selv som nummer 2 med en forfaldsdato den 31. juni 2016. Udvikleren, der er ansvarlig for at udfylde stubben, identificeres som Marty.
Kommentarer skal behandles for at være til enhver brug. (Stub
er kommenteret @Retention (RetentionPolicy.RUNTIME)
så det kan behandles.) Liste 3 præsenterer a StubFinder
applikation, der rapporterer en klasses @Stub
kommentarer.
Liste 3. StubFinder.java
importere java.lang.reflect.Methode; offentlig klasse StubFinder {offentlig statisk ugyldig hoved (String [] args) kaster undtagelse {if (args.length! = 1) {System.err.println ("brug: java StubFinder classfile"); Vend tilbage; } Class clazz = Class.forName (args [0]); hvis (clazz.isAnnotationPresent (Stub.class)) {Stub stub = clazz.getAnnotation (Stub.class); System.out.println ("Stub ID =" + stub.id ()); System.out.println ("Stubdato =" + stub.dueDate ()); System.out.println ("Stub Developer =" + stub.developer ()); System.out.println (); } Metode [] metoder = clazz.getMethods (); for (int i = 0; i <methods.length; i ++) hvis (metoder [i] .isAnnotationPresent (Stub.class)) {Stub stub = metoder [i] .getAnnotation (Stub.class); System.out.println ("Stub ID =" + stub.id ()); System.out.println ("Stubdato =" + stub.dueDate ()); System.out.println ("Stub Developer =" + stub.developer ()); System.out.println (); }}}
Liste 3's hoved ()
metode bruger Java's Reflection API til at hente alt @Stub
bemærkninger, der ligger foran en klassedeklaration såvel som dens metodedeklarationer.
Kompilér lister 1 til 3 som følger:
javac * .java
Kør den resulterende applikation som følger:
java StubFinder KontaktMgr
Du skal overholde følgende output:
Stub ID = 1 Stubdato = 31/12/2016 Stubudvikler = ikke tildelt Stub ID = 2 Stubdato = 31-06-2016 Stubudvikler = Marty
Du kan måske argumentere for, at annoteringstyper og deres kommentarer ikke har noget at gøre med grænseflader. Når alt kommer til alt, klassedeklarationer og redskaber
nøgleord er ikke til stede. Jeg er imidlertid uenig i denne konklusion.
@interface
ligner klasse
ved at det introducerer en type. Dets elementer er metoder, der implementeres (bag kulisserne) for at returnere værdier. Elementer med Standard
værdier returnerer værdier, selvom de ikke findes i annoteringer, der ligner objekter. Ikke-standardelementer skal altid være til stede i en kommentar og skal erklæres for at returnere en værdi. Derfor er det som om en klasse er blevet erklæret, og at klassen implementerer en grænseflades metoder.
Rolle 2: Beskriver implementeringsuafhængige kapaciteter
Forskellige klasser kan tilbyde en fælles kapacitet. F.eks java.nio.CharBuffer
, javax.swing.text.Segment
, java.lang.Streng
, java.lang.StringBuffer
og java.lang.StringBuilder
klasser giver adgang til læsbare sekvenser af char
værdier.
Når klasser tilbyder en fælles kapacitet, kan der udvindes en grænseflade til denne kapacitet til genbrug. For eksempel en grænseflade til den "læsbare sekvens af char
værdier "er blevet ekstraheret til java.lang.CharSequence
interface. CharSequence
giver ensartet, skrivebeskyttet adgang til mange forskellige slags char
sekvenser.
Antag, at du blev bedt om at skrive en lille applikation, der tæller antallet af forekomster af hver type små bogstaver i CharBuffer
, Snor
og StringBuffer
genstande. Efter nogle overvejelser kan du komme med Listing 4. (Jeg ville typisk undgå kulturelt forudindtagede udtryk som f.eks ch - 'a'
, men jeg vil gerne holde eksemplet enkelt.)
Liste 4. Freq.java
(version 1)
importere java.nio.CharBuffer; public class Freq {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("use: java Freq text"); Vend tilbage; } analysS (args [0]); analyseSB (ny StringBuffer (args [0])); analyseCB (CharBuffer.wrap (args [0])); } statisk ugyldig analyseCB (CharBuffer cb) {int tæller [] = ny int [26]; mens (cb.hasRemaining ()) {char ch = cb.get (); hvis (ch> = 'a' && ch <= 'z') tæller [ch - 'a'] ++; } for (int i = 0; i <tæller.længde; i ++) System.out.printf ("Antallet af% c er% d% n", (i + 'a'), tæller [i]); System.out.println (); } statisk ugyldig analyseS (streng s) {int tæller [] = ny int [26]; for (int i = 0; i = 'a' && ch <= 'z') tæller [ch - 'a'] ++; } for (int i = 0; i <tæller.længde; i ++) System.out.printf ("Antallet af% c er% d% n", (i + 'a'), tæller [i]); System.out.println (); } statisk ugyldig analyseSB (StringBuffer sb) {int tæller [] = ny int [26]; for (int i = 0; i = 'a' && ch <= 'z') tæller [ch - 'a'] ++; } for (int i = 0; i <tæller.længde; i ++) System.out.printf ("Antallet af% c er% d% n", (i + 'a'), tæller [i]); System.out.println (); }}
Liste 4 viser tre forskellige analysere
metoder til registrering af antallet af små bogstaver og udsendelse af denne statistik. Selvom Snor
og StringBuffer
varianter er praktisk talt identiske (og du kan blive fristet til at oprette en enkelt metode til begge), CharBuffer
variant adskiller sig mere markant.
Fortegnelse 4 afslører en masse duplikatkode, hvilket fører til en større klassefil end nødvendigt. Du kan nå det samme statistiske mål ved at arbejde med CharSequence
interface. Liste 5 viser en alternativ version af frekvensapplikationen, der er baseret på CharSequence
.
Liste 5. Freq.java
(version 2)
importere java.nio.CharBuffer; public class Freq {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("use: java Freq text"); Vend tilbage; } analysere (args [0]); analysere (ny StringBuffer (args [0])); analysere (CharBuffer.wrap (args [0])); } statisk hulrumsanalyse (CharSequence cs) {int tæller [] = ny int [26]; for (int i = 0; i = 'a' && ch <= 'z') tæller [ch - 'a'] ++; } for (int i = 0; i <tæller.længde; i ++) System.out.printf ("Antallet af% c er% d% n", (i + 'a'), tæller [i]); System.out.println (); }}
Listing 5 afslører en meget enklere applikation, der skyldes kodifikation analysere()
at modtage en CharSequence
argument. Fordi hver af Snor
, StringBuffer
og CharBuffer
redskaber CharSequence
, det er lovligt at videregive forekomster af disse typer til analysere()
.
Et andet eksempel
Udtryk CharBuffer.wrap (args [0])
er et andet eksempel på at videregive en Snor
modsætter sig en parameter af typen CharSequence
.
For at opsummere er en grænseflades anden rolle at beskrive en implementeringsuafhængig kapacitet. Ved at kode til en grænseflade (f.eks CharSequence
) i stedet for til en klasse (som f.eks Snor
, StringBuffer
, eller CharBuffer
), undgår du duplikatkode og genererer mindre klassefiler. I dette tilfælde opnåede jeg en reduktion på mere end 50%.
Rolle 3: letter bibliotekets udvikling
Java 8 introducerede os til den ekstremt nyttige lambda-sprogfunktion og Streams API (med fokus på hvilken beregning der skal udføres snarere end på hvordan den skal udføres). Lambdas og Streams gør det meget lettere for udviklere at indføre parallelisme i deres applikationer. Desværre kunne Java Collections Framework ikke udnytte disse funktioner uden behov for en omfattende omskrivning.
For hurtigt at forbedre samlinger til brug som streamkilder og destinationer understøtter du standardmetoder (også kendt som udvidelsesmetoder), som er ikke-statiske metoder, hvis overskrifter er forud for Standard
nøgleord og hvilke forsyningskodelegemer, blev føjet til Java's interface-funktion. Standardmetoder hører til grænseflader; de er ikke implementeret (men kan tilsidesættes) af klasser, der implementerer grænseflader. De kan også påberåbes via objektreferencer.
Når standardmetoder blev en del af sproget, blev følgende metoder tilføjet til java.util.Collection
interface for at give en bro mellem samlinger og streams:
standard Stream parallelStream ()
: Returner en (muligvis) paralleljava.util.stream.Stream
objekt med denne samling som kilde.standard stream stream ()
: Returner en sekventielStrøm
objekt med denne samling som kilde.
Antag at du har erklæret følgende java.util.Liste
variabel og tildelingsudtryk:
Liste innerPlanets = Arrays.asList ("Mercury", "Venus", "Earth", "Mars");
Du vil traditionelt gentage denne samling som følger:
for (String innerPlanet: innerPlanets) System.out.println (innerPlanet);
Du kan erstatte denne eksterne iteration, som fokuserer på, hvordan man udfører en beregning, med Streams-baseret intern iteration, som fokuserer på, hvilken beregning der skal udføres, som følger:
innerPlanets.stream (). forEach (System.out :: println); innerPlanets.parallelStream (). forEach (System.out :: println);
Her, innerPlanets.stream ()
og innerPlanets.parallelStream ()
returnere sekventielle og parallelle streams til det tidligere oprettede Liste
kilde. Lænket til de returnerede Strøm
referencer er forEach (System.out :: println)
, som gentager sig over strømens genstande og påberåber sig System.out.println ()
(identificeret ved System.out :: println
metodehenvisning) for hvert objekt at udsende sin strengrepræsentation til standardoutputstrømmen.
Standardmetoder kan gøre koden mere læsbar. F.eks java.util.Collections
klasse erklærer en ugyldig sortering (liste liste, komparator c)
statisk metode til sortering af en lists indhold underlagt den angivne komparator. Java 8 tilføjede en standard ugyldig sortering (Comparator c)
metode til Liste
interface, så du kan skrive mere læselig myList.sort (komparator);
i stedet for Collections.sort (myList, komparator);
.
Standardmetoderollen, der tilbydes af grænseflader, har givet Java Collections Framework nyt liv. Du kan overveje denne rolle for dine egne gamle interface-baserede biblioteker.