Java 8 pentru dezvoltarea Android API-ul Stream și bibliotecile de dată și oră

În această serie de trei părți, am explorat toate caracteristicile majore Java 8 pe care le puteți utiliza astăzi în proiectele Android.

În codul mai curat cu expresii Lambda, ne-am concentrat pe tăierea plăcii de tăiere din proiectele dvs. folosind expresii lambda, iar apoi în Metodele implicite și statice, am văzut cum să facem aceste expresii lambda mai concise, combinându-le cu referințe de metode. De asemenea, am abordat adnotările repetate și modul de declarare a metodelor non-abstracte în interfețele dvs. utilizând metode de interfață implicită și statică.

În acest post final, vom analiza adnotările de tip, interfețele funcționale și modul de abordare mai funcțională a procesării datelor cu noul API Stream Java 8.

Vă voi arăta, de asemenea, cum să accesați câteva caracteristici suplimentare Java 8 care în prezent nu sunt acceptate de platforma Android, utilizând bibliotecile Joda-Time și ThreeTenABP.

Adnotări de tip

Adnotările vă ajută să scrieți un cod mai robust și mai puțin predispus la erori, informând instrumentele de control al codului, cum ar fi Lint, despre erorile pe care ar trebui să le privească. Aceste instrumente de inspecție vă vor avertiza atunci când o bucată de cod nu se conformează regulilor prevăzute de aceste adnotări.

Adnotările nu sunt o caracteristică nouă (de fapt, datează din Java 5.0), dar în versiunile anterioare de Java a fost posibilă numai aplicarea adnotărilor la declarații.

Odată cu lansarea Java 8, acum puteți folosi adnotări oriunde ați folosit un tip, inclusiv receptoare de metode; expresii de creare a instanțelor de clasă; implementarea interfețelor; generice și matrice; specificația aruncă și ustensile clauze; și turnarea de tip.

În mod frustrat, deși Java 8 face posibilă utilizarea adnotărilor în mai multe locații decât oricând înainte, nu oferă niciun fel de adnotări specifice tipurilor.

Baza de suport a adnotărilor Android oferă acces la unele adnotări adiționale, cum ar fi @Nullable, @NonNull, și adnotări pentru validarea tipurilor de resurse, cum ar fi  @DrawableRes, @DimenRes, @ColorRes, și @StringRes. Cu toate acestea, este posibil să doriți și să utilizați un instrument de analiză statică terță parte, cum ar fi Checker Framework, care a fost co-dezvoltat cu specificația JSR 308 (specificația Adnotări privind tipurile Java). Acest cadru oferă propriul set de adnotări care pot fi aplicate tipurilor, plus un număr de "dame" (procesoare de adnotări) care se cuplează în procesul de compilare și realizează "verificări" specifice pentru fiecare adnotare de tip inclusă în cadrul Checker Framework.

Deoarece adnotările de tip nu afectează funcționarea în timpul rulării, puteți utiliza adnotările de tip Java 8 în proiectele dvs., rămânând în același timp compatibile cu versiunile anterioare de Java.

Stream API

API-ul Stream oferă o alternativă, abordarea "pipe-and-filters" pentru procesarea colecțiilor.

Înainte de Java 8, ați manipulat colecțiile manual, de obicei prin iterarea peste colectarea și operarea pe fiecare element la rândul său. Această buclă explicită a necesitat o mulțime de plăci de bord, plus este dificil să se înțeleagă structura for-loop până când ajungeți la corpul bucla.

API-ul Stream vă oferă o modalitate mai eficientă de prelucrare a datelor, efectuând o singură executare asupra acestor date - indiferent de cantitatea de date pe care o prelucrați sau dacă efectuați mai multe calcule.

În Java 8, fiecare clasă care implementează java.util.Collection are o curent care poate converti instanțele sale Curent obiecte. De exemplu, dacă aveți un mulțime:

String [] myArray = String nou [] "A", "B", "C", "D";

Apoi îl puteți converti într-un Stream cu următoarele:

Curent myStream = Arrays.stream (MyArray);

API-ul Stream procesează datele prin valorile de la o sursă, printr-o serie de etape de calcul, cunoscute sub numele de a fluxul de conducte. O conductă de flux este compusă din următoarele:

  • O sursă, cum ar fi a Colectie, array sau funcția generator.
  • Zero sau mai multe operațiuni intermitente "leneși". Operațiunile intermediare nu pornesc elemente de procesare până când nu se invocă a terminale-de aceea sunt considerați leneși.De exemplu, apelarea Stream.filter () pe o sursă de date, doar stabilește conducta de flux; nici o filtrare nu apare până când nu apelați operația terminalului. Acest lucru face posibilă strângerea mai multor operațiuni împreună, apoi efectuarea tuturor acestor calcule într-o singură trecere a datelor. Operațiile intermediare produc un flux nou (de exemplu, filtru va produce un flux conținând elementele filtrate) fără modificarea sursei de date, astfel încât să aveți libertatea de a utiliza datele originale în altă parte a proiectului dvs. sau să creați mai multe fluxuri din aceeași sursă.
  • O operație terminal, cum ar fi Stream.forEach. Când invoca operația terminalului, toate operațiile intermediare vor rula și vor produce un flux nou. Un flux nu este capabil să stocheze elemente, așa că, de îndată ce invocați o operațiune terminală, acest flux este considerat "consumat" și nu mai este utilizabil. Dacă doriți să revizuiți elementele unui flux, atunci va trebui să generați un flux nou din sursa originală de date.

Crearea unui flux

Există mai multe modalități de a obține un flux de la o sursă de date, inclusiv:

  • Stream.of ()Creează un flux din valorile individuale:

Curent flux = flux ("A", "B", "C");
  • IntStream.range () Creează un flux dintr-o serie de numere:

IntStream i = IntStream.range (0, 20);
  • Stream.iterate () Creează un flux aplicând în mod repetat un operator pentru fiecare element. De exemplu, aici creăm un flux în care fiecare element crește cu o valoare:

Curent s = Stream.iterate (0, n -> n + 1);

Transformarea unui flux cu operațiuni

Există o multitudine de operații pe care le puteți utiliza pentru a efectua calcule în stilul dvs. funcțional în fluxurile dvs. În această secțiune, voi acoperi câteva dintre cele mai utilizate operațiuni de flux.

Hartă

Hartă() operația ia o expresie lambda ca singurul său argument și folosește această expresie pentru a transforma valoarea sau tipul fiecărui element din flux. De exemplu, următoarele ne dau un nou flux, în care fiecare Şir a fost convertită la majusculă:

Curent myNewStream = myStream.map (s -> s.toUpperCase ());

Limită

Această operație stabilește o limită a dimensiunii unui flux. De exemplu, dacă doriți să creați un flux nou care să conțină maximum cinci valori, atunci utilizați următoarele:

Listă number_string = numbers.stream () .limit (5)

Filtru

Filtru (Predicatul) permite definirea criteriilor de filtrare folosind o expresie lambda. Această expresie lambda trebuie sa returnați o valoare booleană care determină dacă fiecare element trebuie inclus în fluxul rezultat. De exemplu, dacă ați avea o serie de șiruri de caractere și ați vrut să filtrați orice șiruri care conțin mai puțin de trei caractere, ați folosi următoarele:  

Arrays.stream (myArray) .filter (s -> s.length ()> 3). Pentru fiecare (System.out :: println); 

Sortare

Această operație sortează elementele unui flux. De exemplu, următorul text returnează un flux de numere aranjate în ordine ascendentă:

Listă listă = Arrays.asList (10, 11, 8, 9, 22); list.stream () .sorted () .forEach (System.out :: println);

Procesare paralelă

Toate operațiile de flux pot fi executate în serie sau în paralel, deși fluxurile sunt secvențiale, cu excepția cazului în care specificați altfel în mod explicit. De exemplu, următoarele vor procesa fiecare element unul câte unul:

Stream.of ("a", "b", "c", "d", "e"). Pentru fiecare (System.out :: print);

Pentru a executa un flux în paralel, trebuie să marcați în mod explicit acel flux ca fiind paralel, folosind paralel() metodă:

Stream.of ("a", "b", "c", "d", "e") .paralelă () pentruFiecare (System.out :: print);

Sub capotă, fluxurile paralele utilizează Fork / Join Framework, astfel încât numărul de fire disponibile este întotdeauna egal cu numărul de nuclee disponibile în CPU.

Neajunsul la fluxurile paralele este acela că diferitele nuclee pot fi implicate de fiecare dată când codul este executat, deci veți obține de obicei o ieșire diferită cu fiecare execuție. Prin urmare, ar trebui să utilizați numai fluxuri paralele atunci când ordinea de procesare este neimportantă și evitați fluxurile paralele atunci când efectuați operațiuni bazate pe ordine, cum ar fi findFirst ().

Terminal Operations

Colectați rezultatele dintr-un flux utilizând o operație terminală, care este mereu ultimul element dintr-un lanț de metode de flux și întoarce mereu altceva decât un flux.

Există câteva tipuri diferite de operațiuni terminale care returnează diferite tipuri de date, dar în această secțiune vom examina două dintre cele mai utilizate operațiuni terminale.

Colectarea

Colectarea operațiunea adună toate elementele procesate într-un container, cum ar fi Listă sau A stabilit. Java 8 oferă o Colecționari clasa de utilitate, deci nu trebuie să vă faceți griji cu privire la implementarea Colecționari interfață, plus fabrici pentru mulți colectori comuni, inclusiv a lista(), a seta(), și toCollection ().

Următorul cod va produce a Listă care conține numai roșu:

fișiere (s -> s.getColor () este egal ("roșu")) .collect (Collectors.toList ());

Alternativ, puteți colecta aceste elemente filtrate într-un A stabilit:

 .colecta (Collectors.toSet ());

pentru fiecare

pentru fiecare() operațiunea efectuează o anumită acțiune asupra fiecărui element al fluxului, făcându-l echivalentul unui API Stream pentru o declarație pentru fiecare sumă.

Dacă ai avut un articole listă, atunci ați putea folosi pentru fiecare pentru a imprima toate elementele incluse în acest document Listă:

items.forEach (item-> System.out.println (element));

În exemplul de mai sus, folosim o expresie lambda, astfel încât este posibil să efectuăm aceeași lucrare în mai puțin cod, folosind o referință a metodei:

items.forEach (System.out :: println);

Interfețele funcționale

O interfață funcțională este o interfață care conține exact o metodă abstractă, cunoscută sub numele de metoda funcțională.

Conceptul de interfețe cu o singură metodă nu este nou-runnable, comparator, nevărsat, și OnClickListener sunt toate exemplele acestui tip de interfață, deși în versiunile anterioare de Java au fost cunoscute ca interfețe unice de metodă abstractă (interfețe SAM).  

Aceasta este mai mult decât o schimbare simplă a numelui, deoarece există unele diferențe notabile în modul în care lucrați cu interfețe funcționale (sau SAM) în Java 8, comparativ cu versiunile anterioare.

Înainte de Java 8, de obicei ați instanțiat o interfață funcțională utilizând o implementare de clasă anonimă voluminoasă. De exemplu, aici creăm un exemplu de runnable folosind o clasă anonimă:

Runnable r = nouă Runnable () @Override public void run () System.out.println ("Runnable"); ;

După cum am văzut în prima parte, atunci când aveți o interfață cu o singură metodă, puteți instanțiza această interfață utilizând o expresie lambda, mai degrabă decât o clasă anonimă. Acum, putem actualiza această regulă: puteți să instanțiați interfețe funcționale, folosind o expresie lambda. De exemplu:

Runnable r = () -> System.out.println ("Runnable");

Java 8 introduce, de asemenea, a @FunctionalInterface adnotare care vă permite să marcați o interfață ca interfață funcțională:

Interfața funcțională interfață interfață MyFuncInterface public void doSomething (); 

Pentru a asigura compatibilitatea cu versiunile anterioare de Java, @FunctionalInterface adnotarea este opțională; cu toate acestea, este recomandat să vă asigurați că implementați corect interfețele dvs. funcționale.

Dacă încercați să implementați două sau mai multe metode într-o interfață care este marcată ca @FunctionalInterface, atunci compilatorul se va plânge că s-au descoperit mai multe metode abstracte care nu depășesc limitele. De exemplu, următoarele nu se vor compila:

Interfața Interfață FuncționalăInterface MyFuncInterface void doSomething (); // Definirea unei a doua metode abstracte // void doSomethingElse ();  

Și dacă încercați să compilați a @FunctionInterface interfață care conține metode zero, atunci veți întâlni a Nu a fost găsită nicio metodă vizată eroare.

Interfețele funcționale trebuie să conțină exact o metodă abstractă, dar din moment ce metodele implicite și statice nu au un corp, ele sunt considerate ne-abstracte. Aceasta înseamnă că puteți include mai multe metode implicite și statice într-o interfață, marcați-o ca @FunctionalInterface, și asta se va întâmpla încă compila.

Java 8 a adăugat, de asemenea, un pachet java.util.function care conține o mulțime de interfețe funcționale. Este bine să vă luați timp pentru a vă familiariza cu toate aceste noi interfețe funcționale, doar pentru a ști exact ce este disponibil din cutie.

JSR-310: API-ul Java New Date and Time

Lucrul cu data și ora în Java nu a fost niciodată foarte simplu, cu multe API-uri care omit funcții importante, cum ar fi informații despre zona de timp.

Java 8 a introdus un nou API pentru date și timp (JSR-310), care are ca scop rezolvarea acestor probleme, dar, din păcate, la momentul redactării acestui API nu este acceptat pe platforma Android. Cu toate acestea, puteți utiliza unele dintre noile caracteristici Data și ora din proiectele dvs. Android astăzi, utilizând o bibliotecă terță parte.

În această secțiune finală, vă voi arăta cum să configurați și să utilizați două biblioteci populare de terțe părți care să permită utilizarea API-ului Data and Time Java 8 pe Android.

Trei Android Backport

ThreeTen Android Backport (cunoscut și ca ThreeTenABP) este o adaptare a popularului proiect backport ThreeTen, care oferă o implementare a JSR-310 pentru Java 6.0 și Java 7.0. ThreeTenABP este proiectat pentru a oferi acces la toate clasele de date API (cu același nume de pachet diferit) fără adăugarea unui număr mare de metode în proiectele Android.

Pentru a începe să utilizați această bibliotecă, deschideți nivelul modulului build.gradle fișier și adăugați ThreeTenABP ca dependență de proiect:

dependente // Adăugați următoarea linie // compilați com.jakewharton.threetenabp: threetenabp: 1.0.5 '

Apoi trebuie să adăugați declarația de import ThreeTenABP:

import com.jakewharton.threetenabp.AndroidThreeTen;

Și inițializați informațiile din zona de timp în Application.onCreate () metodă:

@Override publice void onCreate () super.onCreate (); AndroidThreeTen.init (aceasta); 

ThreeTenABP conține două clase care afișează două "tipuri" de informații despre timp și dată:

  • LocalDateTime, care stochează un timp și o dată în format 2017-10-16T13: 17: 57.138
  • ZonedDateTime, care este o zonă de timp conștientă și stochează informații despre data și ora în următorul format: 2011-12-03T10: 15: 30 + 01: 00 [Europa / Paris]

Pentru a vă oferi o idee despre modul în care ați folosi această bibliotecă pentru a prelua informațiile despre dată și oră, hai să folosim LocalDateTime pentru a afișa data și ora curente:

import șiroid.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.jakewharton.threetenabp.AndroidThreeTen; import șiroid.widget.TextView; import org.threeten.bp.LocalDateTime; clasa publica MainActivity extinde AppCompatActivity @Override protejat void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); AndroidThreeTen.init (getApplication ()); setContentView (R.layout.activity_main); TextView textView = TextView nou (acesta); textView.setText ("Ora:" + LocalDateTime.now ()); setContentView (TextView); 

Aceasta nu este modalitatea cea mai prietenoasă de a afișa data și ora! Pentru a analiza aceste date brute în ceva mai ușor de citit de om, puteți folosi DateTimeFormatter clasa și setați-o la una dintre următoarele valori:

  • BASIC_ISO_DATE. Formează data ca 2017-1016 + 01.00
  • ISO_LOCAL_DATE. Formează data ca 2017-10-16
  • ISO_LOCAL_TIME. Formează timpul ca 14: 58: 43.242
  • ISO_LOCAL_DATE_TIME. Formează data și ora ca 2017-10-16T14: 58: 09.616
  • ISO_OFFSET_DATE. Formează data ca 2017-10-16 + 01.00
  • ISO_OFFSET_TIME.  Formează timpul ca 14: 58: 56.218 + 01: 00
  • ISO_OFFSET_DATE_TIME. Formează data și ora ca 2017-10-16T14: 5836.758 + 01: 00
  • ISO_ZONED_DATE_TIME. Formează data și ora ca 2017-10-16T14: 58: 51.324 + 01: 00 (Europe / London)
  • ISO_INSTANT. Formează data și ora ca 2017-10-16T13: 52: 45.246Z
  • ISO_DATE. Formează data ca 2017-10-16 + 01: 00
  • ISO_TIME. Formează timpul ca 14: 58: 40.945 + 01: 00
  • ISO_DATE_TIME. Formează data și ora ca 2017-10-16T14: 55: 32.263 + 01: 00 (Europe / London)
  • ISO_ORDINAL_DATE. Formează data ca 2017-289 + 01: 00
  • ISO_WEEK_DATE. Formează data ca 2017-W42-1 + 01: 00
  • RFC_1123_DATE_TIME. Formează data și ora ca Luni, 16 OCT 2017 14: 58: 43 + 01: 00

Aici, actualizăm aplicația noastră pentru a afișa data și ora cu DateTimeFormatter.ISO_DATE formatare:

import șiroid.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.jakewharton.threetenabp.AndroidThreeTen; import șiroid.widget.TextView; // Adăugați declarația de import DateTimeFormatter // import org.threeten.bp.format.DateTimeFormatter; import org.threeten.bp.ZonedDateTime; clasa publica MainActivity extinde AppCompatActivity @Override protejat void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); AndroidThreeTen.init (getApplication ()); setContentView (R.layout.activity_main); TextView textView = TextView nou (acesta); DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; String formattedZonedDate = formatter.format (ZonedDateTime.now ()); textView.setText ("Timp:" + formattedZonedDate); setContentView (TextView); 

Pentru a afișa aceste informații într-un format diferit, pur și simplu înlocuiți-le DateTimeFormatter.ISO_DATE pentru o altă valoare. De exemplu:

DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

Joda-Time

Înainte de Java 8, biblioteca Joda-Time a fost considerată biblioteca standard pentru manipularea datei și a timpului în Java, până la punctul în care noua aplicație Java Data & Time API trage "cu adevărat pe experiența acumulată în proiectul Joda-Time".

În timp ce site-ul Web Joda-Time recomandă ca utilizatorii să migreze cât mai curând posibil la Java 8 Date și Time, deoarece Android nu suportă în prezent acest API, Joda-Time este încă o opțiune viabilă pentru dezvoltarea Androidului. Cu toate acestea, rețineți că Joda-Time are un API mare și încarcă informații despre zona de timp utilizând o resursă JAR, ambele putând afecta performanța aplicației.

Pentru a începe să lucrați cu biblioteca Joda-Time, deschideți-vă nivelul modulului build.gradle fișier și adăugați următoarele:

dependențe compile 'joda-time: joda-time: 2.9.9' ... 

Biblioteca Joda-Time are șase clase importante pentru data și ora:

  • clipă: Reprezintă un punct în cronologie; de exemplu, puteți obține data și ora curente prin apelare Instant.now ().
  • DateTime: Înlocuire generală pentru JDK Calendar clasă.
  • LOCALDATE: O dată fără dată sau orice referire la o fus orar.
  • Ora locala: Un moment fără o dată sau orice referire la o fus orar, de exemplu 14:00:00.
  • LocalDateTime: O dată și o oră locală, fără informații despre zona de timp.
  • ZonedDateTime: O dată și o oră cu un fus orar.

Să aruncăm o privire la modul în care veți tipări data și ora folosind Joda-Time. În exemplul următor, reluăm codul din exemplul nostru ThreeTenABP, pentru a face lucrurile mai interesante pe care le folosesc și eu withZone pentru a converti data și ora într-un ZonedDateTime valoare.

import șiroid.support.v7.app.AppCompatActivity; import android.os.Bundle; import șiroid.widget.TextView; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; clasa publica MainActivity extinde AppCompatActivity @Override protejat void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); DateTime astăzi = new DateTime (); // Returnați un nou formator (folosind cu Zone) și specificați fusul orar, folosind ZoneId // DateTime todayNy = today.withZone (DateTimeZone.forID ("America / New_York")); TextView textView = TextView nou (acesta); textView.setText ("Ora:" + astăziNu); setContentView (TextView); 

Veți găsi o listă completă a fusurilor orare în documentele oficiale Joda-Time.

Concluzie

În acest post, am analizat cum să creăm un cod mai robust utilizând adnotările de tip și am explorat abordarea "conducte și filtre" pentru prelucrarea datelor cu noul API Stream Java 8.

De asemenea, am analizat modul în care au evoluat interfețele în Java 8 și cum le putem folosi în combinație cu alte caracteristici pe care le-am explorat pe parcursul seriei, inclusiv expresii lambda și metode de interfață statică.

Pentru a încheia lucrurile, v-am arătat cum să accesați câteva caracteristici Java 8 suplimentare pe care Android în prezent nu le acceptă în mod implicit, utilizând proiectele Joda-Time și ThreeTenABP.

Puteți afla mai multe despre lansarea Java 8 pe site-ul Oracle.

Și în timp ce sunteți aici, verificați câteva dintre celelalte postări despre Java 8 și dezvoltarea Android!

Cod