Java 8 för Android Development Standard och Static Methods

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.

Skriv Cleaner Lambda-uttryck, med metodreferenser

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:

En statisk metod

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 ();  

En instansmetod för ett visst objekt

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);

En instansmetod för ett godtyckligt föremål av en viss typ

Detta är en instansmetod för ett godtyckligt objekt som kommer att levereras senare och skrivet i följande format:

ContainingType :: method

Constructor Referenser

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 ints är int [] :: ny.

Lägg till standardmetoder till dina gränssnitt

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öm strö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 (); 

Använda statiska metoder i dina gränssnitt för Java 8

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.

Så är gränssnitt i huvudsak bara abstrakta klasser?

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:

  • Abstrakta klasser kan ha slutliga, icke-slutliga, statiska och icke-statiska variabler, medan ett gränssnitt endast kan ha statiska och slutliga variabler.
  • Abstrakta klasser tillåter dig att deklarera fält som inte är statiska och slutliga, medan gränssnittets fält är iboende statiska och slutliga.
  • I gränssnitt är alla metoder som du förklarar eller definierar som standardmetoder offentliga, medan i abstrakta klasser du kan definiera offentliga, skyddade och privata betongmetoder.
  • Abstrakta klasser är klasser, och kan därför ha tillstånd gränssnitt kan inte ha statligt samband med dem.
  • Du kan definiera konstruktörer inom en abstrakt klass, något som inte är möjligt inom Java-gränssnitt.
  • Java tillåter dig bara att förlänga en klass (oavsett om det är abstrakt), men du är fri att implementera så många gränssnitt som du behöver. Det betyder att gränssnittet typiskt har kanten när du behöver flera arv, även om du behöver ta hand om den dödliga diamanten av döden!

Applicera samma anteckning som många gånger som du vill ha

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:

  • Markera anteckningen i fråga med @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.
  • Förklara den inneboende annoteringstypen. Detta måste ha ett attribut som är en uppsättning av den upprepande annoteringstypen, till exempel:
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.

Slutsats

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!