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.
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:
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 ();
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);
Dit is een instantiemethode van een willekeurig object dat later wordt geleverd en in het volgende formaat wordt geschreven:
ContainingType :: methodName
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 int
zus int [] :: nieuwe
.
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 Streamstream () 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 ();
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.
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:
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:
@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.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.
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!