Java 8 pentru dezvoltarea Android metode standard și statice

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.

Scrieți expresii Lambda de curățare, cu referințe de metodă

Î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:

O metodă statică

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

O metodă de instanță a unui obiect specific

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

O metodă de instanță a unui obiect arbitrar de tip particular

Aceasta este o metodă de instanță a unui obiect arbitrar care va fi furnizat ulterior și scris în formatul următor:

ContainingType :: METHODNAME

Constructor Referințe

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 inteste int [] :: noi.

Adăugați metode prestabilite la interfețele dvs.

Î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 Stream flux () 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 (); 

Utilizarea metodelor statice în interfețele dvs. Java 8

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.

Astfel, interfețele sunt în esență doar clase abstracte?

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:

  • Clasele Abstract pot avea variabile finale, non-finale, statice și non-statice, în timp ce o interfață poate avea doar variabile statice și finale.
  • Clasele abstracte vă permit să declarați câmpuri care nu sunt statice și finale, în timp ce câmpurile unei interfețe sunt în mod inerent statice și finale.
  • În interfețe, toate metodele pe care le declarați sau le definiți ca metode implicite sunt în mod inerent publice, în timp ce în clasele abstracte puteți defini metode concrete publice, protejate și private.
  • Clasele abstracte sunt clase, și, prin urmare, poate avea stat; interfețele nu pot avea stat asociate cu acestea.
  • Puteți defini constructorii într-o clasă abstractă, ceva care nu este posibil în interiorul interfețelor Java.
  • Java vă permite să extindeți o clasă (indiferent dacă este abstractă), dar sunteți liber să implementați cât mai multe interfețe pe care le doriți. Acest lucru înseamnă că interfețele au de obicei marginea atunci când aveți nevoie de mai multe moșteniri, deși nu trebuie să fiți atenți la diamantele mortale ale morții!

Aplicați aceeași adnotare la fel de mult cât doriți

Î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:

  • Marcați adnotarea în discuție cu @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ă.
  • Declarați tipul de adnotare care conține. Acesta trebuie să aibă un atribut care este o matrice a tipului de adnotări repetate, de exemplu:
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.

Concluzie

Î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!

Cod