Programmering

Interfaceens seks roller

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 stubben
  • Afleveringsdato() identificerer den dato, hvor stubben skal udfyldes med kode
  • Udvikler() 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.StringBufferog 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, Snorog 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, StringBufferog 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) parallel java.util.stream.Stream objekt med denne samling som kilde.
  • standard stream stream (): Returner en sekventiel Strø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.