Java 8 var et stort skritt fremover for programmeringsspråket, og nå, med utgivelsen av Android Studio 3.0, har Android-utviklere endelig tilgang til innebygd støtte for noen av Java 8s viktigste funksjoner.
I denne tredelte serien har vi undersøkt Java 8-funksjonene du kan begynne å bruke i Android-prosjekter i dag. I Cleaner Code With Lambda Expressions, satte vi opp utviklingen vår for å bruke Java 8-støtten som tilbys av Android standard verktøykjerne, før du tar en grundig titt på lambda-uttrykkene.
I dette innlegget ser vi på to forskjellige måter at du kan erklære ikke-abstrakte metoder i grensesnittene dine (noe som ikke var mulig i tidligere versjoner av Java). Vi vil også svare på spørsmålet om, nå at grensesnitt kan implementere metoder, hva nøyaktig er forskjellen mellom abstrakte klasser og grensesnitt?
Vi dekker også en Java 8-funksjon som gir deg friheten til å bruke samme annotasjon så mange ganger som du vil ha på samme sted, mens du fortsatt er bakoverkompatibel med tidligere versjoner av Android.
Men først, ta en titt på en Java 8-funksjon som er designet for å bli brukt i kombinasjon med lambda-uttrykkene vi så i forrige innlegg.
I det siste innlegget så du hvordan du kan bruke lambda-uttrykk for å fjerne mye boilerplate-kode fra Android-applikasjonene dine. Men når et lambda-uttrykk bare er å kalle en enkelt metode som allerede har et navn, kan du kutte enda mer kode fra prosjektet ditt ved å bruke en metode referanse.
For eksempel, dette lambda uttrykket egentlig bare omdirigere arbeid til en eksisterende handleViewClick
metode:
FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (se -> handleViewClick (se)); Privat tomt håndtakViewClick (Vis visning)
I dette scenariet kan vi referere til denne metoden ved navn, ved hjelp av ::
metode referanse operatør. Du oppretter denne typen metreferanse, ved hjelp av følgende format:
Objekt / klasse / type :: metode
I vårt flytende handlingsknappeksempel kan vi bruke en metodereferanse som kroppen til vårt lambda-uttrykk:
FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (dette :: handleViewClick);
Merk at den refererte metoden må ha samme parametere som grensesnittet - i dette tilfellet, det er Utsikt
.
Du kan bruke metodenes referanseoperatør (::
) for å referere til noen av følgende:
Hvis du har et lambda-uttrykk som kaller en statisk metode:
(args) -> Class.staticMethod (args)
Deretter kan du gjøre det til en metode referanse:
Klasse :: staticMethodName
For eksempel, hvis du hadde en statisk metode PrintMessage
i en Klassen min
klassen, så vil din referansemetode se slik ut:
offentlig klasse myClass public static void PrintMessage () System.out.println ("Dette er en statisk metode"); offentlig statisk tomrom hoved (String [] args) Tråd Tråd = Ny tråd (myClass :: PrintMessage); thread.start ();
Dette er en forekomstmetode for et objekt som er kjent på forhånd, slik at du kan erstatte:
(argumenter) -> containingObject.instanceMethodName (argumenter)
Med:
containingObject :: instanceMethodName
Så, hvis du hadde følgende lambda-uttrykk:
MyClass.printNames (navn, x -> System.out.println (x));
Deretter vil innføring av en metode referanse gi deg følgende:
MyClass.printNames (navn, System.out :: println);
Dette er en forekomstmetode for et vilkårlig objekt som vil bli levert senere og skrevet i følgende format:
ContainingType :: metode
Konstruksreferanser ligner på metodehenvisninger, bortsett fra at du bruker søkeordet ny
å påkalle konstruktøren. For eksempel, Button :: nytt
er en konstruktørreferanse for klassen Knapp
, selv om den eksakte konstruktøren som påberopes, avhenger av konteksten.
Ved hjelp av konstruktørreferanser kan du slå:
(argumenter) -> nytt klassenavn (argumenter)
Inn i:
Classname :: nytt
For eksempel, hvis du hadde følgende MyInterface
grensesnitt:
offentlig grensesnitt myInterface offentlig abstrakt Student getStudent (Strenge navn, integer alder);
Deretter kan du bruke konstruktørreferanser til å lage nye Student
instanser:
myInterface stu1 = Student :: ny; Student stu = stu1.getStudent ("John Doe", 27);
Det er også mulig å opprette konstruktorreferanser for array typer. For eksempel, en konstruktor referanse for en rekke av int
s er int [] :: nytt
.
Før Java 8, kan du bare inkludere abstrakte metoder i grensesnittene dine (dvs. metoder uten en kropp), noe som gjorde det vanskelig å utvikle grensesnitt, etter publisering.
Hver gang du la til en metode til en grensesnittdefinisjon, ville noen klasser som implementerte dette grensesnittet plutselig mangle en implementering. Hvis du for eksempel hadde et grensesnitt (MyInterface
) som ble brukt av Klassen min
, og deretter legge til en metode til MyInterface
ville bryte kompatibilitet med Klassen min
.
I beste tilfelle hvor du var ansvarlig for det lille antallet klasser som ble brukt MyInterface
, Denne oppførselen vil være irriterende, men håndterbar - du må bare legge til side litt tid for å oppdatere klassene dine med den nye implementeringen. Men ting kan bli mye mer komplisert hvis et stort antall klasser implementeres MyInterface
, eller hvis grensesnittet ble brukt i klasser som du ikke var ansvarlig for.
Mens det var flere løsninger på dette problemet, var ingen av dem ideelle. For eksempel kan du inkludere nye metoder i en abstrakt klasse, men dette vil fortsatt kreve at alle oppdatere koden for å utvide denne abstrakte klassen; og mens du kunne utvide det opprinnelige grensesnittet med et nytt grensesnitt, hvem som helst som ønsket å bruke disse nye metodene, ville da måtte omskrive alle deres eksisterende grensesnitt referanser.
Med introduksjonen av standardmetoder i Java 8 er det nå mulig å erklære ikke-abstrakte metoder (dvs. metoder med en kropp) i grensesnittene dine, slik at du endelig kan lage standard implementeringer for metodene dine.
Når du legger til en metode for grensesnittet som standardmetode, må noen klasser som implementerer dette grensesnittet ikke nødvendigvis gi sin egen implementering, noe som gir deg mulighet til å oppdatere grensesnittene uten å bryte kompatibiliteten. Hvis du legger til en ny metode til et grensesnitt som en standardmetode, så hver klasse som bruker dette grensesnittet, men ikke gir sin egen implementering, vil bare arve metodens standardimplementering. Siden klassen ikke mangler en implementering, fortsetter den å fungere som normalt.
Faktisk var innføringen av standardmetoder årsaken til at Oracle kunne lage så mange tillegg til API-samlingen i Java 8.
Samling
er et generisk grensesnitt som brukes i mange forskjellige klasser, slik at det å legge ut nye metoder til dette grensesnittet, har potensial til å bryte utallige kodelinjer. Snarere enn å legge til nye metoder til Samling
grensesnitt og bryter hver klasse som ble avledet fra dette grensesnittet, opprettet Oracle standardmetoden, og deretter legges disse nye metodene som standardmetoder. Hvis du ser på den nye metoden Collection.Stream () som vi vil undersøke i detalj i del tre), ser du at den ble lagt til som standardmetode:
standard strømstream () returner StreamSupport.stream (splitterator (), false);
Å lage en standardmetode er enkel, bare legg til misligholde
modifikator til metoden signaturen din:
offentlig grensesnitt MyInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Dette er en standard metode");
Nå, hvis Klassen min
bruker MyInterface
men gir ikke sin egen implementering av defaultMethod
, det vil bare arve standard implementering levert av MyInterface
. For eksempel vil følgende klasse fortsatt samle:
offentlig klasse MyClass utvider AppCompatActivity implementerer MyInterface
En implementeringsklasse kan overstyre standardimplementeringen som gis av grensesnittet, slik at klassene fortsatt er i full kontroll over deres implementeringer.
Selv om standardmetoder er et velkomment tillegg for API-designere, kan de noen ganger føre til et problem for utviklere som prøver å bruke flere grensesnitt i samme klasse.
Tenk deg det i tillegg til MyInterface
, du har følgende:
offentlig grensesnitt SecondInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Dette er også en standard metode");
Både MyInterface
og SecondInterface
inneholde en standardmetode med nøyaktig samme signatur (defaultMethod
). Forestill deg nå at du prøver å bruke begge disse grensesnittene i samme klasse:
offentlig klasse MyClass utvider AppCompatActivity implementerer MyInterface, SecondInterface
På dette punktet har du to motstridende implementeringer av defaultMethod
, og kompilatoren har ingen anelse om hvilken metode den skal bruke, så du kommer til å møte en kompilatorfeil.
En måte å løse dette problemet på er å overstyre den motstridende metoden med din egen implementering:
offentlig klasse MyClass utvider AppCompatActivity implementerer MyInterface, SecondInterface public void defaultMethod ()
Den andre løsningen er å spesifisere hvilken versjon av defaultMethod
du vil implementere, ved hjelp av følgende format:
.super. ();
Så hvis du ønsket å ringe MyInterface # defaultMethod ()
implementering, så bruker du følgende:
offentlig klasse MyClass utvider AppCompatActivity implementerer MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod ();
I likhet med standardmetoder gir statiske grensesnittmetoder deg en måte å definere metoder i et grensesnitt. I motsetning til standardmetoder kan en implementeringsklasse imidlertid ikke overstyre et grensesnitt statisk fremgangsmåter.
Hvis du har statiske metoder som er spesifikke for et grensesnitt, gir Java 8s statiske grensesnitt metoder deg en måte å plassere disse metodene i det tilsvarende grensesnittet, i stedet for å lagre dem i en egen klasse.
Du lager en statisk metode ved å plassere søkeordet statisk
ved begynnelsen av metoden signatur, for eksempel:
offentlig grensesnitt MyInterface static void staticMethod () System.out.println ("Dette er en statisk metode");
Når du implementerer et grensesnitt som inneholder en statisk grensesnittmetode, er den statiske metoden fortsatt en del av grensesnittet og er ikke arvet av klassen som implementerer den, så du må prefikse metoden med grensesnittnavnet, for eksempel:
offentlig klasse MyClass utvider AppCompatActivity implementerer MyInterface public static void main (String [] args) MyInterface.staticMethod (); ...
Dette betyr også at en klasse og et grensesnitt kan ha en statisk metode med samme signatur. For eksempel bruker MyClass.staticMethod
og MyInterface.staticMethod
i samme klasse vil ikke forårsake en kompileringstid feil.
Tillegg av statiske grensesnittmetoder og standardmetoder har ført til at noen utviklere spørsmålet om Java-grensesnittene blir mer som abstrakte klasser. Men selv med tillegg av standard og statisk grensesnitt, er det fortsatt noen bemerkelsesverdige forskjeller mellom grensesnitt og abstrakte klasser:
Tradisjonelt har en av begrensningene i Java-notater vært at du ikke kan bruke samme annotasjon mer enn én gang på samme sted. Prøv å bruke den samme annotasjonen flere ganger, og du vil møte en kompileringstid-feil.
Men med introduksjonen av Java 8s repeterende kommentarer, er du nå fri til å bruke samme annotasjon så mange ganger som du vil ha på samme sted.
For å sikre at koden din fortsatt er kompatibel med tidligere versjoner av Java, må du lagre de gjentatte notatene i en containeranmerking.
Du kan fortelle kompilatoren å generere denne beholderen ved å gjøre følgende:
@Repeatable
meta-merknad (en merknad som brukes til å annotere en merknad). For eksempel, hvis du ønsket å lage @Å gjøre
annotering repeterbar, du vil bruke: @Repeatable (ToDos.class)
. Verdien i parentes er typen av merketekst som kompilatoren til slutt vil generere.offentlige @interface ToDos ToDo [] value ();
Forsøk på å bruke samme annotasjon flere ganger uten først å erklære at den er repeterbar, vil føre til en feil ved kompileringstid. Når du imidlertid har angitt at dette er en repeterbar annotasjon, kan du bruke denne merknaden flere ganger på et sted hvor du vil bruke en standard notat.
I denne andre delen av serien vår på Java 8 så vi hvordan du kan kutte enda mer boilerplate-kode fra Android-prosjektene dine ved å kombinere lambda-uttrykk med metodehenvisninger, og hvordan du kan forbedre grensesnittene dine med standard og statiske metoder.
I tredje og siste avdrag vil vi se på en ny Java 8 API som lar deg behandle store mengder data på en mer effektiv, declarative måte, uten å måtte bekymre seg om samtidighet og trådstyring. Vi knytter også sammen noen av de forskjellige funksjonene vi har diskutert gjennom denne serien, ved å utforske rollen som funksjonelle grensesnitt må spille i lambda-uttrykk, statiske grensesnittmetoder, standardmetoder og mer.
Og til slutt, selv om vi fremdeles venter på Java 8s nye dato og tid-API for å ankomme offisielt på Android, viser jeg hvordan du kan begynne å bruke denne nye API-en i Android-prosjektene dine i dag, ved hjelp av noen tredjepart biblioteker.
I mellomtiden kan du sjekke ut noen av våre andre innlegg på Java og Android app utvikling!