Java 8 voor Android-ontwikkeling standaard en statische methoden

Java 8 was een enorme stap voorwaarts voor de programmeertaal en nu, met de release van Android Studio 3.0, hebben Android-ontwikkelaars eindelijk toegang tot ingebouwde ondersteuning voor enkele van de belangrijkste functies van Java 8.

In deze driedelige serie hebben we de Java 8-functies onderzocht die u kunt gebruiken in uw Android-projecten vandaag. In Cleaner Code Met Lambda Expressions hebben we onze ontwikkeling opgezet om de Java 8-ondersteuning te gebruiken die wordt geboden door de standaard toolchain van Android, voordat we lambda-expressies grondig bekijken..

In dit bericht bekijken we twee verschillende manieren waarop u niet-abstracte methoden in uw interfaces kunt declareren (iets dat in eerdere versies van Java niet mogelijk was). We zullen ook de vraag beantwoorden, nu interfaces kunnen methoden implementeren, wat precies is het verschil tussen abstracte klassen en interfaces?

We behandelen ook een Java 8-functie die u de vrijheid biedt om dezelfde aantekening zo vaak te gebruiken als u wilt op dezelfde locatie, terwijl u achterwaarts compatibel blijft met eerdere versies van Android.

Maar laten we eerst eens kijken naar een Java 8-functie die is ontworpen om te worden gebruikt in combinatie met de lambda-expressies die we in de vorige post hebben gezien.

Schrijf schonere Lambda-expressies, met verwijzingen naar methoden

In het laatste bericht zag je hoe je lambda-expressies kunt gebruiken om veel boilerplate-code uit je Android-applicaties te verwijderen. Wanneer een lambda-expressie echter gewoon een enkele methode aanroept die al een naam heeft, kunt u nog meer code uit uw project knippen door een methode-verwijzing te gebruiken.

Deze lambda-expressie is bijvoorbeeld eigenlijk alleen maar werk omleiden naar een bestaande handleViewClick methode:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (weergave -> handleViewClick (weergave));  private ongeldige handleViewClick (bekijk weergave) 

In dit scenario kunnen we deze methode op naam raadplegen, met behulp van de :: methode referentie-operator. U maakt dit soort referentiemethode met behulp van het volgende formaat:

Object / Class / Type :: methodName

In ons voorbeeld van een zwevende actieknop kunnen we een referentiemethode gebruiken als de hoofdtekst van onze lambda-expressie:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (dit :: handleViewClick); 

Houd er rekening mee dat de methode waarnaar wordt verwezen dezelfde parameters moet hebben als de interface, in dit geval dus Uitzicht.

U kunt de methode reference operator gebruiken (::) om te verwijzen naar een van de volgende:

Een statische methode

Als je een lambda-expressie hebt die een statische methode aanroept:

(args) -> Class.staticMethod (args)

Dan kun je er een methode-referentie van maken:

Class :: staticMethodName

Bijvoorbeeld als u een statische methode had PrintMessage in een Mijn klas class, dan zou je methodeverwijzing er ongeveer zo uitzien:

public class myClass public static void PrintMessage () System.out.println ("Dit is een statische methode");  public static void main (String [] args) Thread thread = new Thread (myClass :: PrintMessage); thread.start ();  

Een instantie-methode van een specifiek object

Dit is een instantiemethode van een object dat van tevoren bekend is, waardoor u het volgende kunt vervangen:

(argumenten) -> containingObject.instanceMethodName (arguments)

Met:

containingObject :: instanceMethodName

Dus, als je de volgende lambda-expressie had:

MyClass.printNames (namen, x -> System.out.println (x));

Als u vervolgens een referentiemethode introduceert, krijgt u het volgende:

MyClass.printNames (namen, System.out :: println);

Een instantiemethode van een willekeurig object van een bepaald type

Dit is een instantiemethode van een willekeurig object dat later wordt geleverd en in het volgende formaat wordt geschreven:

ContainingType :: methodName

Constructor Referenties

Referenties van constructors zijn vergelijkbaar met verwijzingen naar methoden, behalve dat u het trefwoord gebruikt nieuwe om de constructor aan te roepen. Bijvoorbeeld, Button :: nieuwe is een constructorreferentie voor de klas Knop, hoewel de exacte constructor die wordt aangeroepen, afhankelijk is van de context.

Met constructorreferenties kunt u:

(argumenten) -> nieuwe ClassName (argumenten)

In:

ClassName :: nieuwe

Bijvoorbeeld, als u het volgende had MyInterface interface:

 public Interface myInterface public abstract Student getStudent (String name, Integer age); 

Dan zou je constructor-verwijzingen kunnen gebruiken om nieuw te creëren Student voorbeelden:

myInterface stu1 = Student :: nieuw; Student stu = stu1.getStudent ("John Doe", 27);

Het is ook mogelijk om constructorreferenties voor arraytypen te maken. Bijvoorbeeld een constructorreferentie voor een array van intzus int [] :: nieuwe.

Voeg standaardmethoden toe aan uw interfaces

Vóór Java 8 kon je alleen abstracte methoden opnemen in je interfaces (dat wil zeggen methoden zonder een hoofdtekst), waardoor het moeilijk was om interfaces te ontwikkelen, post-publicatie.

Telkens wanneer u een methode aan een interfacedefinitie toevoegde, ontbraken in elke klasse die deze interface implementeerde plotseling een implementatie. Als u bijvoorbeeld een interface had (MyInterface) die werd gebruikt door Mijn klas, vervolgens een methode toevoegen aan MyInterface zou verenigbaar zijn met Mijn klas.

In het beste geval waarin u verantwoordelijk was voor het kleine aantal klassen dat werd gebruikt MyInterface, dit gedrag zou vervelend, maar beheersbaar zijn - je zou alleen wat tijd vrij moeten maken om je klassen bij te werken met de nieuwe implementatie. Er kunnen echter dingen worden veel gecompliceerder als een groot aantal klassen wordt geïmplementeerd MyInterface, of als de interface werd gebruikt in klassen waarvoor u niet verantwoordelijk was.

Hoewel er een aantal oplossingen voor dit probleem waren, was geen van hen ideaal. U zou bijvoorbeeld nieuwe methoden kunnen opnemen in een abstracte klasse, maar dit zou nog steeds vereisen dat iedereen hun code bijwerkt om deze abstracte klasse uit te breiden; en, terwijl jij kon uitbreiding van de oorspronkelijke interface met een nieuwe interface, iedereen die deze nieuwe methoden wilde gebruiken, moest deze vervolgens herschrijven allemaal hun bestaande interface-referenties.

Met de introductie van standaardmethoden in Java 8, is het nu mogelijk om niet-abstracte methoden (dat wil zeggen methoden met een hoofdtekst) binnen uw interfaces te declareren, zodat u eindelijk standaardimplementaties voor uw methoden kunt maken.

Wanneer u een methode als standaardmethode aan uw interface toevoegt, hoeft elke klasse die deze interface implementeert niet noodzakelijk zijn eigen implementatie te bieden, waardoor u een manier krijgt om uw interfaces bij te werken zonder de compatibiliteit te breken. Als u een nieuwe methode toevoegt aan een interface als een standaard methode, dan ervaart elke klasse die deze interface gebruikt maar geen eigen implementatie biedt gewoon de standaardimplementatie van de methode. Aangezien de klasse een implementatie niet mist, blijft deze werken zoals normaal.

In feite was de introductie van standaardmethoden de reden dat Oracle zo'n groot aantal toevoegingen aan de collecties-API kon maken in Java 8.

Verzameling is een generieke interface die in veel verschillende klassen wordt gebruikt, dus het toevoegen van nieuwe methoden aan deze interface heeft het potentieel om ontelbare coderegels te doorbreken. In plaats van nieuwe methoden toe te voegen aan de Verzameling interface en het breken van elke klasse die is afgeleid van deze interface, Oracle heeft de standaardmethode gemaakt en deze nieuwe methoden vervolgens toegevoegd als standaardmethoden. Als u de nieuwe methode Collection.Stream () bekijkt (die we in deel drie in detail zullen onderzoeken), ziet u dat deze als standaardmethode is toegevoegd:

 standaard Stream stream () return StreamSupport.stream (spliterator (), false); 

Het maken van een standaardmethode is eenvoudig - voeg gewoon de standaard modifier voor de handtekening van uw methode:

openbare interface MyInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Dit is een standaardmethode");

Nu als Mijn klas toepassingen MyInterface maar biedt geen eigen implementatie van defaultMethod, het ervaart alleen de standaardimplementatie geleverd door MyInterface. De volgende klasse compileert bijvoorbeeld nog steeds:

openbare klasse MyClass breidt AppCompatActivity implementeert MyInterface 

Een implementatieklasse kan de standaardimplementatie overschrijven die door de interface wordt geboden, zodat de klassen nog steeds de volledige controle over hun implementaties hebben.

Hoewel standaardmethoden een welkome toevoeging zijn voor API-ontwerpers, kunnen ze soms een probleem veroorzaken voor ontwikkelaars die meerdere interfaces in dezelfde klasse proberen te gebruiken. 

Stel je voor dat in aanvulling op MyInterface, je hebt het volgende:

openbare interface SecondInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Dit is ook een standaardmethode");

Beide MyInterface en SecondInterface een standaardmethode bevatten met exact dezelfde handtekening (defaultMethod). Stel je nu voor dat je beide interfaces in dezelfde klas probeert te gebruiken:

openbare klasse MyClass breidt AppCompatActivity implementeert MyInterface, SecondInterface 

Op dit punt heb je twee tegenstrijdige implementaties van defaultMethod, en de compiler heeft geen idee welke methode hij moet gebruiken, dus je zult een compileerfout tegenkomen.

Een manier om dit probleem op te lossen is om de conflicterende methode te vervangen door uw eigen implementatie:

openbare klasse MyClass breidt AppCompatActivity implementeert MyInterface, SecondInterface public void defaultMethod () 

De andere oplossing is om te specificeren welke versie van defaultMethod u wilt implementeren, met behulp van het volgende formaat:

.super.();

Dus als je de MyInterface # defaultMethod () implementatie, dan zou je het volgende gebruiken:

public class MyClass breidt AppCompatActivity implementeert MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod (); 

Statische methoden gebruiken in uw Java 8-interfaces

Net als bij standaardmethoden, bieden statische-interfacewerkwijzen u een manier om methoden in een interface te definiëren. In tegenstelling tot standaardmethoden kan een implementatieklasse geen interface's overschrijven statisch methoden.

Als u statische methoden hebt die specifiek zijn voor een interface, bieden de statische interfacemethoden van Java 8 u een manier om deze methoden in de bijbehorende interface te plaatsen, in plaats van ze in een afzonderlijke klasse op te slaan.

U maakt een statische methode door het trefwoord te plaatsen statisch aan het begin van de methodehandtekening, bijvoorbeeld:

openbare interface MyInterface static void staticMethod () System.out.println ("Dit is een statische methode"); 

Wanneer u een interface implementeert die een statische-interfacemethode bevat, maakt die statische methode nog steeds deel uit van de interface en wordt deze niet overgenomen door de klasse die deze implementeert, dus u moet de methode vooraf de naam van de interface geven, bijvoorbeeld:

openbare klasse MyClass breidt AppCompatActivity implementeert MyInterface public static void main (String [] args) MyInterface.staticMethod (); ... 

Dit betekent ook dat een klasse en een interface een statische methode kunnen hebben met dezelfde handtekening. Gebruik bijvoorbeeld MyClass.staticMethod en MyInterface.staticMethod in dezelfde klasse veroorzaakt geen compileerfout.

Dus, zijn interfaces in essentie alleen abstracte klassen?

De toevoeging van statische interfacemethoden en standaardmethoden heeft ertoe geleid dat sommige ontwikkelaars zich afvroegen of Java-interfaces meer op abstracte klassen lijken. Echter, zelfs met de toevoeging van standaard en statische interfacemethoden, zijn er nog enkele opvallende verschillen tussen interfaces en abstracte klassen:

  • Abstracte klassen kunnen eind-, niet-definitieve, statische en niet-statische variabelen hebben, terwijl een interface alleen statische en eindvariabelen kan hebben.
  • Met abstracte klassen kunt u velden declareren die niet statisch en definitief zijn, terwijl de velden van een interface inherent statisch en definitief zijn.
  • In interfaces zijn alle methoden die u declareert of definieert als standaardmethoden inherent openbaar, terwijl u in abstracte klassen openbare, beschermde en private concrete methoden kunt definiëren.
  • Abstracte klassen zijn klassen, en kan daarom staat hebben; interfaces kunnen geen bijbehorende status hebben.
  • Je kunt constructors binnen een abstracte klasse definiëren, iets dat niet mogelijk is binnen Java-interfaces.
  • Met Java kun je slechts één klasse uitbreiden (ongeacht of deze abstract is), maar je bent vrij om zoveel interfaces te implementeren als je nodig hebt. Dit betekent dat interfaces meestal de rand hebben wanneer u meerdere overerving nodig hebt, hoewel u wel op de dodelijke diamant van de dood moet letten!

Dezelfde annotatie toepassen zo vaak als u wilt

Traditioneel was een van de beperkingen van Java-annotaties dat je dezelfde annotatie niet meer dan eens op dezelfde locatie kunt toepassen. Probeer dezelfde annotatie meerdere keren te gebruiken en je zult een compileerfoutmelding tegenkomen.

Met de introductie van de herhalende annotaties van Java 8 bent u nu vrij om dezelfde aantekening zo vaak te gebruiken als u wilt op dezelfde locatie.

Om ervoor te zorgen dat uw code compatibel blijft met eerdere versies van Java, moet u uw herhalende annotaties opslaan in een containerannotatie.

U kunt de compiler vertellen om deze container te genereren door de volgende stappen uit te voeren:

  • Markeer de annotatie in kwestie met de @Repeatable meta-annotatie (een annotatie die wordt gebruikt om een ​​annotatie te annoteren). Als u bijvoorbeeld de. Wilt maken @Te doen annotatie herhaalbaar, zou je gebruiken: @Repeatable (ToDos.class). De waarde tussen haakjes is het type containerannotatie dat de compiler uiteindelijk zal genereren.
  • Verklaar het bevattende annotatietype. Dit moet een attribuut hebben dat een array van het herhaalde annotatietype is, bijvoorbeeld:
public @interface ToDos ToDo [] waarde (); 

Als u dezelfde annotatie meerdere keren probeert toe te passen zonder eerst te verklaren dat de annotatie herhaalbaar is, resulteert dit in een fout tijdens het compileren. Nadat u echter hebt opgegeven dat dit een herhaalbare annotatie is, kunt u deze annotatie meerdere keren gebruiken op elke locatie waar u een standaardannotatie zou gebruiken.

Conclusie

In dit tweede deel van onze serie over Java 8 hebben we gezien hoe je nog meer boilerplate code uit je Android-projecten kunt knippen door lambda-expressies te combineren met methodeverwijzingen en hoe je je interfaces kunt verbeteren met standaard en statische methoden.

In het derde en laatste deel bekijken we een nieuwe Java 8 API waarmee u enorme hoeveelheden gegevens op een efficiëntere, verklarende manier kunt verwerken, zonder zorgen te maken over concurrency en thread management. We zullen ook enkele van de verschillende functies die we in deze serie hebben besproken aan elkaar koppelen door de rol te verkennen die functionele interfaces moeten spelen in lambda-expressies, statische interfacemethoden, standaardmethoden en meer.

En tot slot, ook al wachten we nog steeds op de nieuwe Date and Time API van Java 8 om officieel op Android te komen, ik zal laten zien hoe je deze nieuwe API vandaag kunt gebruiken in je Android-projecten, met de hulp van een derde partij bibliotheken.

Bekijk in de tussentijd enkele van onze andere berichten over de ontwikkeling van Java- en Android-apps!