Java este unul dintre cele mai utilizate limbaje în industria software-ului, iar de-a lungul anilor a primit o reputație nemeritată atunci când vine vorba despre scrierea codului. Acesta a fost clasificat drept un limbaj de programare care pune în evidență cele mai mici detalii, primind astfel din partea unora eticheta de limbaj “verbose”. Și da, câteodată ai de scris foarte multe linii de cod ca să poți acoperi o funcționalitate de bază, din cauza arhitecturii limbajului.

 

Având în vedere faptul că Java este un limbaj diversificat care acoperă destul de multe funcționalități, uneori poate fi destul de greu să ții pasul cu toate acestea, mai ales când vine vorba despre optimizarea unei aplicații. În acest articol, vă voi povesti despre câteva dintre funcționalitățile Java care ar putea să ajute la îmbunătățirea productivității în proiect.

 

 

 

Variabile locale de tip inferit

 

Să luăm ca exemplu clasica convenție în care trebuie să declarăm tipul unui obiect înainte de a-l folosi și în care trebuie să alegem o denumire pe care să o înțelegem atunci când scriem numele unei clase și al unui obiect.

 

ManagersOverviewPage managersOverviewPage = page.login(username, password);

 

 

 

 

De cele mai multe ori ne întâlnim cu expresii asemănătoare cu cea de mai sus în care numele obiectului este adesea derivat din tipul obiectului, sau poate chiar același nume, ceea ce face ca întreaga instrucțiune să fie adesea redundantă și necesită foarte mult timp pentru tastat.

 

Odată cu apariția versiunii 10, Java a introdus variabilele locale de tip inferit. Cu ce ne ajută acest lucru? Ei bine, ne scutește să declarăm explicit un obiect sau tipul variabilei respective. În cazul exemplului de mai sus cu “ManagersOverviewPage” putem renunța la acest nume pompos în detrimentul cuvântului cheie, “var”, după cum urmează:

 

var managersOverviewPage = page.login(username, password);

 

 

Ce părere ai despre acest lucru? Aceasta funcționalitate te-ar putea ajuta să câștigi timp.

 

Totuși, să fii atent atunci când utilizezi variabila locală inferit, deoarece aceasta nu poate fi însoțită fără o inițializare. Java este totuși un limbaj static, ceea ce face ca tipul de dată să fie dedus la compilare.

 

var managersOverviewPage; // eroare de compilare

 

 

 

Așa cum am citit încă din subtitlu, tipul inferit poate fi folosit doar în declararea variabilelor locale. Îl puteți folosi în interiorul metodelor, loop-urilor sau în interiorul condițiilor. În cazul în care vei declara o variabilă globală folosind acest tip, vei primi o eroare de compilare, indiferent dacă variabila este inițializată, așa cum am povestit mai sus. Următorul cod va produce o eroare:

 

public class TestClass {
private var managersOverviewPage = page.login(username, password);
}

 

 

Tipul inferit nu poate fi folosit în antetul metodelor, deoarece metoda apelată trebuie să cunoască tipul de argument folosit. Exemplu:

 

public class TestClass {
public TestClass(var myData) {} // intoarce o eroare
}

 

Nu orice tip de variabilă trebuie să fie un “var”. Odată ce vei începe să folosești acest tip, vei observa că îți va fi în avantaj, însă nu trebuie să abuzezi. Vor fi momente în care pot apărea ambiguități din cauza utilizării imprudente a cuvântului cheie “var”, după cum urmează:

 

var initialManagersList = new ArrayList();

 

 

 

Ce se întâmplă aici? Ce tip de dată este variabila declarată mai sus? Dacă deja ți-ai pus această întrebare și ți-ai răspuns că este un “ArrayList” de tipul “Objects”, felicitări! Ai avut dreptate!

 

În astfel de situații, cel mai bine este să declari tipul utilizat în partea dreapta a operandului. De exemplu, utilizarea operatorului “<>” ne va ajuta să specificăm tipul folosit pentru stocarea datelor.

 

var initialManagersList = new ArrayList<String>();

 

 

 

Acum vom ști cu siguranță că variabila declarată mai sus este definită ca fiind un “ArrayList” de tipul “String”.

 

Noile operații care au îmbunătățit eficiența stream-urilor

 

Java 9 a introdus două operații în API-ul Stream-ului. Acestea sunt takeWhile și dropWhile.

 

Operația “takeWhile” procesează elementele unei colecții și le păstrează atât timp cât o condiție, numită adesea predicat, este adevărată. Pe de altă parte, operația “dropWhile” este fix opusul. Aceasta ignoră elementele unei colecții cât timp condiția predicativă este adevărată.

 

Sa luăm exemplul de mai jos în care avem o lista de manageri care fac parte aleator din diferite departamente: Media, Research și Marketing. Vom folosi “takeWhile()” ca să păstrăm managerii care fac parte din departamentul de Marketing, însă operația se va opri atunci când va întâlni primul manager care face parte dintr-un alt departament.

 

var managersList = ManagersUtil.getAllManagers();
var marketingManagersList = managersList
.stream()
.takeWhile( manager → manager.getDepartment().equals(“MARKETING”))
.collect(Collectors.toList());

 

 

 

Similar pentru operația de “dropWhile()”, fiind inversa primei. Când vei invoca această metodă, vei descoperi că managerii găsiți în departamentul de Marketing din stream nu vor fi păstrați, deoarece aceștia îndeplinesc condiția adevărată.

 

var managersList = ManagersUtil.getAllManagers();
var marketingManagersList = managersList
.stream()
.dropWhile( manager → manager.getDepartment().equals(“MARKETING”))
.collect(Collectors.toList());

 

 

 

Poți sorta o colecție astfel încât să ai un rezultat deterministic. Dacă ești interesat de ștergerea tuturor elementelor care îndeplinesc condiția predicativă adevărată, atunci cel mai bine este să sortezi colecția înainte de aplicarea operațiilor “takeWhile()” și “dropWhile()”, ca de exemplu:

 

var managersList = ManagersUtil.getAllManagers();
var marketingManagersList = managersList
.stream()
.sorted(Comparator.comparing(Manager::department))
.takeWhile( manager → manager.getDepartment().equals(“MARKETING”))

.collect(Collectors.toList());

 

 

 

O întrebare destul de întâlnită ar fi ”Care este diferența dintre filter și takeWhile?”, deoarece ambele folosesc o condiție predicativă pentru a parcurge un stream.

 

Diferența este că operația “filter()” parcurge întreaga colecție de elemente și adună toate elementele care se potrivesc cu predicatul respectiv, în timp ce “takeWhile()” scurtează acest proces prin oprirea operației de îndată ce găsește un element care nu se potrivește cu predicatul.

 

Cele două operații “takeWhile” și “dropWhile” sunt destul de costisitoare din punct de vedere al performanței și este recomandat ca acestea să fie utilizate pe stream-uri diferite.

 

 

 

Expresiile switch

 

Odată cu apariția versiunii 12 din Java au luat naștere expresiile switch. Acestea ne permit inițializarea directă a unei variabile folosind un switch. În exemplul ce urmează, voi arăta cum aceasta expresie a devenit utilă în inițializarea unei variabile.

 

String age = switch(name) {
case “sister” → “15”;
case “brother” → “17”;
case “mother” → “45”;
default → “”;
};

 

 

Codul de mai sus arată cum expresia switch asignează caracterele “17” variabilei age, atunci când caracterele “brother” se regăsesc în variabila name.

 

Așa cum știm din switch-ul clasic, orice “case” continuă cu “:”, însă nu pentru switch-ul de asignare care a înlocuit semnul de punctuație “:” cu o săgeată. De asemenea, nu vei mai avea nevoie să urmărești fiecare case din switch pentru a te asigura că acesta se termină cu operatorul “break”, pentru a nu produce un comportament neașteptat.

 

Vor fi situații în care vei avea nevoie să abordezi mai multe cazuri într-un singur bloc. Poți face acest lucru despărțind fiecare caz prin intermediul unei virgule, după cum este prezentat mai jos:

 

String age = switch(name) {
case “sister”, “brother” → “15”;
case “father” → “47”;

case “mother” → “45”;
default → “”;
};

 

 

 

Astfel, oricare din cele două cazuri, “sister” sau “brother” va întoarce în variabila age, valoarea “15”.

 

Ca regulă de aur, folosește expresia switch atunci când vrei să inițializezi o variabilă folosind diferite valori, iar switch-ul clasic atunci când vrei să invoci mai multe instrucțiuni pe un singur caz.

 

 

Blocuri de texte

 

Adesea reprezentarea unui bloc complex de text poate fi foarte anevoioasă, deoarece trebuie să urmărești cu atenție întreg șirul de caractere pentru a te asigura că acesta este corect. În exemplul de mai jos, toate ghilimelele sunt precedate de operatorul “\”, caracterul pentru început de linie “\n” este adăugat la fiecare sfârșit de linie și semnul plus este necesar pentru unirea fiecărei linii:

 

String data =
“[\n” +
”  {\n” +
    ”    \”id\”: 78895,\n” +
    ”    \”managerId\”: 3976,\n” +
    ”    \”department\”: \”MARKETING\”,\n” +
    ”    \”experience\”: \“MIDDLE\”,\n” +
    ”  },\n” +
    ”  {\n” +
    ”    \”id\”: 78896,\n” +
    ”    \”managerId\”: 3977,\n” +
    ”    \”department\”: \”RESEARCH\”,\n” +
    ”    \”experience\”: \”SENIOR\”,\n” +
    ”  }\n” +
    “]”;

 

 

 

 

Textele bloc, introduse odată cu versiunea Java 13, ne permit să utilizăm trei ghilimele succesive pentru a deschide și a închide o bucată de text, după cum urmează:

 

String data = “””
       [
          {
            “id”: 78895,
            “managerId”: 3976,
            “department”: “MARKETING”,
            “experience”: “MIDDLE”
          },
          {
            “id”: 78896,
            “managerId”: 3977,
            “department”: “RESEARCH”,
            “experience”: “SENIOR”
          }
        ]
       “””;

 

 

 

În felul acesta nu va mai scăpa nimic din vedere atunci când vei analiza textul și acesta este mult mai concis.

Există însă o condiție de care trebuie sa ținem cont. Ghilimelele de deschidere și de închidere nu trebuie să se regăsească pe același rând, altfel compilatorul v-a semnala o eroare.

 

return ””” Hello, world! ”””; // eroare de compilare

 

 

Acest impediment poate fi rezolvat mutând textul și ghilimelele de închidere pe rândul următor.

 

return ”””
Hello, world! ”””;

 

 

Cu toate că eroarea a fost rezolvată, este de preferat ca alinierea ghilimelelor să fie făcută pe rânduri proprii astfel încât textul să fie poziționat între acestea, de exemplu:

 

return ”””
          Hello, world!
          ”””;

 

 

Concluzie

 

Acestea sunt doar câteva din noile funcționalități apărute în versiunile recente de Java pe care limbajul le oferă. După cum vei observa, limbajul s-a îmbunătățit cu siguranță în domeniul verbozității și adoptă tendințe moderne cu fiecare versiune.

 

Dacă ești curios să mai afli câteva idei despre cum să fi mai productiv și la ce să oferi atenție, am vorbit mai multe despre cât de mult contează calitatea codului aici: Calitatea codului (nu) contează? – Softia

 

 

Surse

https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html
https://blogs.oracle.com/javamagazine/modern-java-toys-that-boost-productivity-from-type-inference-to-text-blocks