Java 8 a reprezentat un uriaș pas înainte pentru limba de programare și acum, odată cu lansarea versiunii Android Studio 3.0, dezvoltatorii Android au în sfârșit acces la suportul încorporat pentru unele dintre cele mai importante caracteristici ale Java 8.
În această serie de trei părți, am explorat caracteristicile Java 8 pe care le puteți utiliza în proiectele Android astăzi. În codul mai curat cu expresii Lambda, am stabilit dezvoltarea noastră pentru a utiliza suportul Java 8 furnizat de uneltele de instrumente implicite pentru Android, înainte de a lua o privire aprofundată asupra expresiilor lambda.
În acest post, vom examina două moduri diferite în care puteți declara metode non-abstracte în interfețele dvs. (ceva care nu a fost posibil în versiunile anterioare de Java). Vom răspunde și la întrebarea, acum că interfețele pot implementa metode, ce exact este diferența dintre clasele abstracte și interfețele?
De asemenea, vom acoperi o caracteristică Java 8 care vă oferă libertatea de a folosi aceeași adnotare de câte ori doriți în aceeași locație, rămânând în același timp compatibile cu versiunile anterioare de Android.
Dar, mai întâi, să aruncăm o privire la o caracteristică Java 8 care este proiectată să fie utilizată în combinație cu expresiile lambda pe care le-am văzut în postul anterior.
În ultimul post, ați văzut cum puteți folosi expresiile lambda pentru a elimina o mulțime de coduri de bare din aplicațiile Android. Cu toate acestea, atunci când o expresie lambda numeste pur și simplu o singură metodă care are deja un nume, puteți reduce chiar mai mult cod din proiectul dvs. utilizând o referință de metodă.
De exemplu, această expresie lambda este într-adevăr doar redirecționarea muncii la un existent handleViewClick
metodă:
FloatingActionButton fab = (floatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (vizualizați -> handleViewClick (vizualizare)); privid void handleViewClick (Vizualizare vedere)
În acest scenariu, putem face referire la această metodă după nume, utilizând ::
operator de referință pentru metodă. Creați acest tip de referință de metodă, utilizând formatul următor:
Obiect / Clasa / Tip :: METHODNAME
În exemplul Floating Action Button, putem folosi o referință de metodă ca corp al expresiei noastre lambda:
FloatingActionButton fab = (floatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (aceasta :: handleViewClick);
Rețineți că metoda menționată trebuie să aibă aceiași parametri ca interfața - în acest caz, asta e Vedere
.
Puteți utiliza operatorul de referință al metodei (::
) pentru a face referire la oricare dintre următoarele:
Dacă aveți o expresie lambda care solicită o metodă statică:
(args) -> Class.staticMethod (args)
Apoi îl puteți transforma într-o referință de metodă:
Clasa :: staticMethodName
De exemplu, dacă ați avut o metodă statică PrintMessage
în a Clasa mea
clasă, atunci referința dvs. de metodă ar arăta astfel:
public class myClass public static void PrintMessage () System.out.println ("Aceasta este o metodă statică"); static public void principal (String [] args) Thread thread = Subiect nou (myClass :: PrintMessage); thread.start ();
Aceasta este o metodă a unui obiect care este cunoscut înainte de timp, permițându-vă să înlocuiți:
(argumente) -> care conținObject.instanceMethodName (argumente)
Cu:
containingObject :: instanceMethodName
Deci, dacă ați avea următoarea expresie lambda:
MyClass.printNames (nume, x -> System.out.println (x));
Introducerea unei metode de referință vă va oferi următoarele:
MyClass.printNames (nume, System.out :: println);
Aceasta este o metodă de instanță a unui obiect arbitrar care va fi furnizat ulterior și scris în formatul următor:
ContainingType :: METHODNAME
Referințele constructorului sunt similare cu referințele metodei, cu excepția faptului că utilizați cuvântul cheie nou
pentru a invoca constructorul. De exemplu, Butonul :: nouă
este o referință constructor pentru clasă Buton
, deși constructorul exact invocat depinde de context.
Folosind referințele constructorului, puteți transforma:
(argumente) -> new ClassName (argumente)
În:
ClassName :: noi
De exemplu, dacă ați avut următoarele lucruri MyInterface
interfaţă:
interfață publică interfața mea public abstract Student getStudent (Nume șir, vârstă integeră);
Apoi puteți folosi referințele constructorului pentru a crea noi Student
instanțe:
myInterface stu1 = Student :: nou; Student stu = stud1.getStudent ("John Doe", 27);
De asemenea, este posibil să creați referințe constructori pentru tipurile de matrice. De exemplu, o referință constructor pentru o serie de int
este int [] :: noi
.
Înainte de Java 8, ați putea include numai metode abstracte în interfețele dvs. (adică metode fără un corp), ceea ce a făcut dificilă evoluția interfețelor, post-publicare.
De fiecare dată când ați adăugat o metodă la o definiție de interfață, orice clase care au implementat această interfață ar lipsi brusc o implementare. De exemplu, dacă ați avut o interfață (MyInterface
) care a fost folosit de Clasa mea
, adăugând apoi o metodă MyInterface
ar rupe compatibilitatea cu Clasa mea
.
În cel mai bun scenariu în care ați fost responsabil pentru numărul mic de clase care au folosit MyInterface
, acest comportament ar fi enervant, dar ușor de gestionat - ar trebui să renunți puțin timp la actualizarea cursurilor cu noua implementare. Cu toate acestea, lucrurile ar putea deveni mult mai complicat dacă un număr mare de clase sunt implementate MyInterface
, sau dacă interfața a fost utilizată în clase pentru care nu sunteți responsabil (ă).
Deși au existat o serie de soluții pentru această problemă, nici unul dintre ele nu era ideal. De exemplu, ați putea include metode noi într-o clasă abstractă, dar acest lucru ar necesita totuși ca toată lumea să își actualizeze codul pentru a extinde această clasă abstractă; și, în timp ce tu ar putea extinde interfața originală cu o interfață nouă, oricine dorește să utilizeze aceste metode noi ar trebui apoi să rescrie toate referințele de interfață existente.
Odată cu introducerea metodelor implicite în Java 8, acum este posibil să se declare în interiorul interfețelor metode non-abstracte (adică metode cu un corp), astfel încât să puteți crea implementări implicite pentru metodele dvs..
Când adăugați o metodă la interfața dvs. ca metodă prestabilită, orice clasă care implementează această interfață nu trebuie neapărat să furnizeze propria implementare, ceea ce vă oferă o modalitate de a vă actualiza interfețele fără a rupe compatibilitatea. Dacă adăugați o nouă metodă la o interfață ca a implicită, atunci fiecare clasă care utilizează această interfață, dar care nu furnizează propria implementare, va moșteni pur și simplu implementarea implicită a metodei. Deoarece clasa nu lipsește o implementare, va continua să funcționeze în mod normal.
De fapt, introducerea metodelor implicite a fost motivul pentru care Oracle a reușit să facă un număr atât de mare de adăugiri la API-ul Colecții din Java 8.
Colectie
este o interfață generică folosită în multe clase diferite, adăugând astfel noi metode la această interfață, având potențialul de a rupe nenumărate linii de cod. Mai degrabă decât adăugarea de noi metode la Colectie
interfața și ruperea fiecărei clase derivate din această interfață, Oracle a creat caracteristica metodei implicite și apoi a adăugat aceste metode noi ca metode implicite. Dacă analizați noua metodă Collection.Stream () (pe care o vom explora în detaliu în partea a treia), veți vedea că a fost adăugată ca metodă prestabilită:
implicit Streamflux () retur StreamSupport.stream (spliterator (), false);
Crearea unei metode implicite este simplă - trebuie doar să adăugați Mod implicit
Modificator la semnarea metodei:
interfața publică MyInterface void interfaceMethod (); implicit default defaultMethod () Log.i (TAG, "Aceasta este o metodă implicită");
Acum dacă Clasa mea
utilizări MyInterface
dar nu oferă propria punere în aplicare a defaultMethod
, va moșteni doar implementarea implicită furnizată de MyInterface
. De exemplu, următoarea clasă va mai fi compilată:
clasa publica MyClass extinde AppCompatActivity implementa MyInterface
O clasă de implementare poate suprascrie implementarea implicită furnizată de interfață, astfel încât clasele sunt încă în controlul complet al implementărilor lor.
În timp ce metodele implicite sunt o adăugare binevenită pentru designerii API, pot provoca ocazional o problemă dezvoltatorilor care încearcă să utilizeze mai multe interfețe din aceeași clasă.
Imaginați-vă că în plus față de MyInterface
, aveți următoarele:
interfață publică SecondInterface void interfaceMethod (); implicit void defaultMethod () Log.i (TAG, "Aceasta este, de asemenea, o metodă implicită");
Ambii MyInterface
și SecondInterface
conține o metodă implicită cu exact aceeași semnătură (defaultMethod
). Acum imaginați-vă că încercați să utilizați ambele interfețe în aceeași clasă:
clasa publica MyClass extinde AppCompatActivity implementa MyInterface, SecondInterface
În acest moment aveți două implementări conflictuale defaultMethod
, iar compilatorul nu are nici o idee asupra metodei pe care ar trebui să o utilizeze, deci veți întâlni o eroare de compilator.
O modalitate de a rezolva această problemă este de a suprascrie metoda contradictorie cu propria implementare:
clasa publica MyClass extinde AppCompatActivity implementa MyInterface, SecondInterface public void defaultMethod ()
Cealaltă soluție este de a specifica ce versiune de defaultMethod
pe care doriți să o implementați, utilizând formatul următor:
.super. ();
Deci, dacă ați vrut să sunați MyInterface # defaultMethod ()
implementare, atunci veți folosi următoarele:
clasa publica MyClass extinde AppCompatActivity implementează MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod ();
Similar metodelor implicite, metodele de interfață statică vă oferă o modalitate de definire a metodelor în interiorul unei interfețe. Cu toate acestea, spre deosebire de metodele implicite, o clasă de implementare nu poate înlocui o interfață static metode.
Dacă aveți metode statice specifice unei interfețe, atunci metodele statice de interfață Java 8 vă oferă o modalitate de a plasa aceste metode în interfața corespunzătoare, mai degrabă decât de a le depozita într-o clasă separată.
Creați o metodă statică plasând cuvântul cheie static
la începutul semnării metodei, de exemplu:
interfața publică MyInterface static void staticMethod () System.out.println ("Aceasta este o metodă statică");
Când implementați o interfață care conține o metodă de interfață statică, acea metodă statică este încă parte a interfeței și nu este moștenită de clasa care o implementează, deci va trebui să prefixați metoda cu numele interfeței, de exemplu:
public class MyClass extinde AppCompatActivity implementează MyInterface public static void principal (String [] args) MyInterface.staticMethod (); ...
Acest lucru înseamnă de asemenea că o clasă și o interfață pot avea o metodă statică cu aceeași semnătură. De exemplu, folosind MyClass.staticMethod
și MyInterface.staticMethod
în aceeași clasă nu va provoca o eroare de compilare.
Adăugarea metodelor de interfață statică și a metodelor implicite a determinat unii dezvoltatori să pună întrebări dacă interfețele Java devin din ce în ce mai mult clase abstracte. Cu toate acestea, chiar și cu adăugarea metodelor de interfață implicită și statică, există încă unele diferențe notabile între interfețe și clase abstracte:
În mod tradițional, una dintre limitările adnotărilor Java a fost că nu puteți aplica aceeași adnotare mai mult decât o dată în aceeași locație. Încercați să utilizați aceeași adnotare de mai multe ori și veți întâlni o eroare de compilare.
Cu toate acestea, odată cu introducerea adnotărilor repetate ale Java 8, sunteți acum liber să utilizați aceeași adnotare de câte ori doriți în aceeași locație.
Pentru a vă asigura că codul dvs. rămâne compatibil cu versiunile anterioare de Java, va trebui să stocați adnotările dvs. repetate într-o adnotare a containerului.
Puteți spune compilatorului să genereze acest container, parcurgând următorii pași:
@Repeatable
meta-adnotare (o adnotare care este folosită pentru a adnota o adnotare). De exemplu, dacă doriți să faceți @A face
adnotare repetabilă, ați folosi: @Repeatable (ToDos.class)
. Valoarea în paranteze este tipul de adnotare a containerului pe care compilatorul o va genera în cele din urmă.public @interface ToDos ToDo [] valoare ();
Încercarea de a aplica aceeași adnotare de mai multe ori fără a declara mai întâi că este repetabilă va duce la o eroare la momentul compilării. Cu toate acestea, odată ce ați specificat că aceasta este o adnotare repetabilă, puteți folosi această adnotare de mai multe ori în orice locație unde ați folosi o adnotare standard.
În această a doua parte a seriei noastre despre Java 8, am văzut cum puteți reduce și mai mult codul de boilerplate din proiectele Android prin combinarea expresiilor lambda cu referințe de metode și despre îmbunătățirea interfețelor cu metode implicite și statice.
În cea de-a treia și ultima tranșă, vom căuta un nou API Java 8 care vă permite să procesați cantități uriașe de date într-un mod mai eficient și declarativ, fără trebuie să vă faceți griji cu privire la concurența și gestionarea firului. Vom lega, de asemenea, câteva dintre caracteristicile pe care le-am discutat în această serie, explorând rolul pe care interfețele funcționale trebuie să îl joace în expresiile lambda, metode de interfață statică, metode implicite și multe altele.
Și în sfârșit, chiar dacă încă așteptăm ca API-ul Data and Time API-ul Java 8 să ajungă oficial pe Android, vă voi arăta cum puteți începe să folosiți acest nou API în proiectele dvs. Android astăzi, cu ajutorul unor terțe părți biblioteci.
Între timp, verificați câteva dintre celelalte postări despre dezvoltarea aplicațiilor Java și Android!