Java 8 var ett enormt steg framåt för programmeringsspråket och nu, med utgåvan av Android Studio 3.0, har Android-utvecklare äntligen tillgång till inbyggt stöd för några av Java 8: s viktigaste funktioner.
I den här tredelade serien har vi undersökt de Java 8-funktioner som du kan börja använda i dina Android-projekt i dag. I Cleaner Code With Lambda Expressions, startade vi vår utveckling för att använda Java 8-stöd som tillhandahålls av Androids standardverktygskedja innan vi tar en djupgående titt på lambda-uttryck.
I det här inlägget ser vi på två olika sätt att du kan deklarera icke abstrakta metoder i dina gränssnitt (något som inte var möjligt i tidigare versioner av Java). Vi kommer också att svara på frågan om, nu att gränssnitt kan implementera metoder, vad exakt är skillnaden mellan abstrakta klasser och gränssnitt?
Vi täcker också en Java 8-funktion som ger dig friheten att använda samma annotering så många gånger du vill ha samma plats, medan du återstår bakåtkompatibel med tidigare versioner av Android.
Men först, ta en titt på en Java 8-funktion som är konstruerad att användas i kombination med de lambda-uttryck vi såg i föregående inlägg.
I det sista inlägget såg du hur du kan använda lambda-uttryck för att ta bort massor av boilerplate-kod från dina Android-applikationer. Men när ett lambda-uttryck helt enkelt kallar en enda metod som redan har ett namn kan du skära ännu mer kod från ditt projekt med hjälp av en metodreferens.
Till exempel, detta lambda uttryck uttrycker egentligen bara arbetet med en befintlig handleViewClick
metod:
FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (visa -> hanteraViewClick (visa)); privat tomt handtagViewClick (Visa vy)
I detta scenario kan vi hänvisa till den här metoden med namnet, med hjälp av ::
metod referensoperatör. Du skapar denna typ av metodreferens med följande format:
Objekt / klass / typ :: method
I vårt Floating Action Button-exempel kan vi använda en metodreferens som kroppen av vårt lambda-uttryck:
FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (detta :: handleViewClick);
Observera att den refererade metoden måste ha samma parametrar som gränssnittet - i det här fallet, det vill säga Se
.
Du kan använda metodreferensoperatören (::
) för att hänvisa till något av följande:
Om du har ett lambda-uttryck som kallar en statisk metod:
(args) -> Class.staticMethod (args)
Då kan du göra det till en metodreferens:
Klass :: staticMethodName
Till exempel, om du hade en statisk metod PrintMessage
i en Min klass
klass, så skulle din metodreferens se ut så här:
offentlig klass myClass public static void PrintMessage () System.out.println ("Detta är en statisk metod"); Statisk statisk tomt huvud (String [] args) Tråd tråd = Ny tråd (myClass :: PrintMessage); thread.start ();
Detta är en förekomstmetod för ett objekt som är känt före tiden, vilket gör att du kan ersätta:
(argument) -> containingObject.instanceMethodName (argumenter)
Med:
containingObject :: instanceMethodName
Så om du hade följande lambda uttryck:
MyClass.printNames (namn, x -> System.out.println (x));
Då skulle en metodreferens införa följande:
MyClass.printNames (namn, System.out :: println);
Detta är en instansmetod för ett godtyckligt objekt som kommer att levereras senare och skrivet i följande format:
ContainingType :: method
Konstruktorreferenser liknar metodreferenser, förutom att du använder sökordet ny
att åberopa konstruktören. Till exempel, Button :: ny
är en konstruktorreferens för klassen Knapp
, även om den exakta konstruktören som åberopas beror på sammanhanget.
Med hjälp av konstruktorreferenser kan du vända:
(argument) -> nytt klassnamn (argument)
In i:
Classname :: ny
Till exempel om du hade följande MyInterface
gränssnitt:
Public Interface myInterface offentlig abstrakt Student getStudent (String namn, Integer ålder);
Då kan du använda konstruktorreferenser för att skapa nya Studerande
instanser:
myInterface stu1 = Student :: new; Student stu = stu1.getStudent ("John Doe", 27);
Det är också möjligt att skapa konstruktorreferenser för array typer. Till exempel en konstruktorreferens för en grupp av int
s är int [] :: ny
.
Före Java 8 kan du bara inkludera abstrakta metoder i dina gränssnitt (dvs metoder utan en kropp), vilket gjorde det svårt att utveckla gränssnitt, efter publicering.
Varje gång du lägger till en metod för en gränssnittsdefinition, skulle alla klasser som genomförde detta gränssnitt plötsligt sakna en implementering. Om du till exempel hade ett gränssnitt (MyInterface
) som användes av Min klass
, sedan lägger till en metod till MyInterface
skulle bryta kompatibilitet med Min klass
.
I det bästa fallet där du var ansvarig för det lilla antalet klasser som användes MyInterface
, Detta beteende skulle vara irriterande men hanterbart - du måste bara avsätta lite tid för att uppdatera dina klasser med den nya implementeringen. Men saker kan bli mycket mer komplicerat om ett stort antal klasser implementeras MyInterface
, eller om gränssnittet användes i klasser som du inte var ansvarig för.
Medan det fanns ett antal lösningar för detta problem, var ingen av dem idealiska. Till exempel kan du inkludera nya metoder i en abstrakt klass, men detta kräver fortfarande att alla uppdaterar sin kod för att förlänga denna abstrakta klass; och medan du skulle kunna förlänga det ursprungliga gränssnittet med ett nytt gränssnitt, alla som ville använda dessa nya metoder skulle då behöva skriva om Allt deras existerande gränssnittsreferenser.
Med introduktionen av standardmetoder i Java 8 är det nu möjligt att deklarera icke-abstrakta metoder (dvs metoder med en kropp) inuti dina gränssnitt, så att du äntligen kan skapa standard implementeringar för dina metoder.
När du lägger till en metod i ditt gränssnitt som standardmetod, behöver varje klass som implementerar detta gränssnitt inte nödvändigtvis tillhandahålla en egen implementering, vilket ger dig möjlighet att uppdatera gränssnitt utan att bryta kompatibilitet. Om du lägger till en ny metod i ett gränssnitt som en standardmetod, då är varje klass som använder det här gränssnittet, men inte tillhandahåller en egen implementering, helt enkelt en arv av metodens standardimplementering. Eftersom klassen saknar ett genomförande fortsätter det att fungera som vanligt.
Faktum är att införandet av standardmetoder var orsaken till att Oracle kunde göra så många tillägg till API-samlingar i Java 8.
Samling
är ett generiskt gränssnitt som används i många olika klasser, så att lägga till nya metoder för detta gränssnitt hade potential att bryta otaliga kodkod. Snarare än att lägga till nya metoder för Samling
gränssnitt och bryter varje klass som härrörde från det här gränssnittet, skapade Oracle standardmetodfunktionen och lade sedan till dessa nya metoder som standardmetoder. Om du tittar på den nya metoden Collection.Stream () som vi kommer att undersöka i detalj i del tre) ser du att den har lagts till som standardmetod:
standard strömström () returnera StreamSupport.stream (splitterator (), false);
Skapa en standardmetod är enkel-lägg bara till standard
modifierare till din metod underskrift:
offentligt gränssnitt MyInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Detta är en standard metod");
Nu om Min klass
användningar MyInterface
men ger inte sitt eget genomförande av defaultMethod
, det kommer bara att ärva den standardimplementering som tillhandahålls av MyInterface
. Till exempel kommer följande klass fortfarande att kompilera:
offentlig klass MyClass utökar AppCompatActivity implementerar MyInterface
En implementeringsklass kan åsidosätta standardinställningen som tillhandahålls av gränssnittet, så klasserna är fortfarande i full kontroll över deras implementeringar.
Medan standardmetoder är ett välkommet tillägg för API-designers kan de ibland orsaka problem för utvecklare som försöker använda flera gränssnitt i samma klass.
Föreställ dig det förutom MyInterface
, du har följande:
offentligt gränssnitt SecondInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Detta är också en standard metod");
Både MyInterface
och SecondInterface
innehåller en standardmetod med exakt samma signatur (defaultMethod
). Tänk nu att du försöker använda båda dessa gränssnitt i samma klass:
offentlig klass MyClass utökar AppCompatActivity implementerar MyInterface, SecondInterface
Vid denna tidpunkt har du två motstridiga implementeringar av defaultMethod
, och kompilatorn har ingen aning om vilken metod den ska använda, så att du kommer att stöta på ett kompilatorfel.
Ett sätt att lösa detta problem är att åsidosätta den motstridiga metoden med ditt eget genomförande:
offentlig klass MyClass utökar AppCompatActivity implementerar MyInterface, SecondInterface public void defaultMethod ()
Den andra lösningen är att ange vilken version av defaultMethod
du vill implementera, med följande format:
.super. ();
Så om du ville ringa MyInterface # defaultMethod ()
implementering, då skulle du använda följande:
offentlig klass MyClass utökar AppCompatActivity implementerar MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod ();
På samma sätt som standardmetoder ger statiska gränssnittsmetoder dig ett sätt att definiera metoder i ett gränssnitt. I motsats till standardmetoder kan en implementeringsklass dock inte åsidosätta gränssnittets statisk metoder.
Om du har statiska metoder som är specifika för ett gränssnitt, ger Java 8s statiska gränssnittsmetoder dig möjlighet att placera dessa metoder i motsvarande gränssnitt, istället för att lagra dem i en separat klass.
Du skapar en statisk metod genom att placera sökordet statisk
vid början av metodens signatur, till exempel:
offentligt gränssnitt MyInterface static void staticMethod () System.out.println ("Detta är en statisk metod");
När du implementerar ett gränssnitt som innehåller en statisk gränssnittsmetod, är den statiska metoden fortfarande en del av gränssnittet och är inte ärvt av klassen som implementerar det, så du måste prefixa metoden med gränssnittsnamnet, till exempel:
public class MyClass utökar AppCompatActivity implementerar MyInterface public static void main (String [] args) MyInterface.staticMethod (); ...
Detta innebär också att en klass och ett gränssnitt kan ha en statisk metod med samma signatur. Till exempel, med användning av MyClass.staticMethod
och MyInterface.staticMethod
i samma klass orsakar inte ett kompileringsfel.
Tillägget av statiska gränssnittsmetoder och standardmetoder har lett till att vissa utvecklare frågar om Java-gränssnittet blir mer som abstrakta klasser. Men även med tillägg av standard- och statiska gränssnittsmetoder finns det fortfarande några anmärkningsvärda skillnader mellan gränssnitt och abstrakta klasser:
Traditionellt har en av begränsningarna i Java-annoteringar varit att du inte kan tillämpa samma annotering mer än en gång på samma plats. Försök att använda samma annotering flera gånger, och du kommer att stöta på ett kompileringstidsfel.
Men med introduktionen av Java 8s upprepande kommentarer, kan du nu använda samma annotering så många gånger som du vill ha samma plats.
För att säkerställa att din kod fortfarande är kompatibel med tidigare versioner av Java, måste du lagra dina upprepande noteringar i en behållaranmärkning.
Du kan berätta för kompilatorn att generera den här behållaren genom att göra följande steg:
@Repeatable
meta-annotering (en anteckning som används för att kommentera en anteckning). Till exempel, om du ville göra @Att göra
annotering repeterbar, du skulle använda: @Repeatable (ToDos.class)
. Värdet inom parentes är typen av containeranmärkning som kompilatorn så småningom kommer att generera.public @interface ToDos ToDo [] värde ();
Att försöka använda samma annotering flera gånger utan att först förklara att det är repeterbart kommer att resultera i ett fel vid sammanställningen. När du väl har angett att det här är en repeterbar anteckning, kan du använda den här annoteringen flera gånger på en plats där du skulle använda en standardanmärkning.
I den här andra delen av vår serie på Java 8 såg vi hur du kan skära ännu mer pannkodskod från dina Android-projekt genom att kombinera lambda-uttryck med metodreferenser och hur du förbättrar gränssnittet med standard- och statiska metoder.
I den tredje och sista delen kommer vi att titta på ett nytt Java 8 API som låter dig bearbeta enorma mängder data på ett mer effektivt, deklarativt sätt, utan att behöva oroa sig för samtidighet och trådhantering. Vi kommer också att koppla ihop några av de olika funktionerna som vi har diskuterat i hela serien genom att utforska den roll som funktionella gränssnitt måste spela i lambda-uttryck, statiska gränssnittsmetoder, standardmetoder och mer.
Och slutligen, även om vi fortfarande väntar på Java 8: s nya Date and Time API för att officiellt anlända till Android, visar jag hur du kan börja använda det här nya API-programmet i dina Android-projekt idag, med hjälp av någon tredje part bibliotek.
Under tiden, kolla in några av våra andra inlägg på Java och Android app utveckling!