Hubert Popielarz
Santander Bank Polska
Hubert PopielarzSenior Dev Engineer @ Santander Bank Polska

Przydatne funkcje współczesnej Javy, które warto wykorzystać

Poznaj 5 funkcji współczesnych wersji od Javy 9 do 14, które przydadzą Ci się w codziennej pracy. Zobacz też, w jakim kierunku Java może rozwijać się w kolejnych wersjach.
11.05.20205 min
Przydatne funkcje współczesnej Javy, które warto wykorzystać

Według większości programistów posługujących się językiem Java, wersja 8. była przełomowa. Największą rewolucją w tej wersji było oczywiście dodanie wyrażeń lambda, które niejako pokazały kierunek, w którym będzie rozwijać się ten język. Jednak na tym nie koniec. Każda kolejna wersja daje nam zestaw nowych funkcji, których wykorzystanie pozwoli zmniejszyć ilość tzw. boilerplateu, albo na przykład uchroni nas przed NullPointerException. W tym artykule chciałbym pokazać, że Java cały czas ewoluuje, a nowe feature’y znacząco ułatwiają codzienną pracę.

Rekord (Java 14, preview)

Jako jedną z większych zmian, będącą póki co w wersji preview, jest wprowadzenie rekordów do składni Java 14. Z pewnością zastąpią one tzw. warstwę przenoszenia danych w aplikacji, czyli kolokwialnie mówiąc, wszelkiego rodzaju pojemniki na dane. Wprowadzenie nowej konstrukcji tego typu przyniesie bardzo dużo korzyści, poczynając od skrócenia zapisu z kilkudziesięciu linii kodu do zaledwie kilku, kończąc na odejściu od Lomboka w projekcie, co bardzo spodoba się purystom języka Java.

Rekord wygeneruje za nas konstruktor, hashCode(), equals() oraz toString(). Dodatkowo rekordy są niemutowalne, przez co raz utworzony obiekt nie może być modyfikowany.

Przykład modelu wykorzystującego tradycyjną klasę, która posiada tylko dwa pola. W rzeczywistości modele posiadają ich o wiele więcej:

public class User {

   private final String name;
   private final String lastName;

   public User(String name, String lastName) {
       this.name = name;
       this.lastName = lastName;
   }

   public String getName() {
       return name;
   }

   public String getLastName() {
       return lastName;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (!(o instanceof User)) return false;
       User user = (User) o;
       return Objects.equals(getName(), user.getName()) &&
               Objects.equals(getLastName(), user.getLastName());
   }

   @Override
   public int hashCode() {
       return Objects.hash(getName(), getLastName());
   }

   @Override
   public String toString() {
       return "User{" +
               "name='" + name + '\'' +
               ", lastName='" + lastName + '\'' +
               '}';
   }
}


Poniżej przykład będący prawie tym samym, ale tu wykorzystany zostanie Rekord w Javie 14 (z ustawionym przełącznikiem --preview).

record User(String name, String lastName){}


Tak zapisany typ User, będzie posiadał:

  • publiczny konstruktor zawierający wszystkie parametry zdefiniowane powyżej,
  • publiczne modyfikatory dostępu przy polach, czyli do zmiennej name będziemy się odnosili poprzez name(), nie getName(),
  • automatycznie zaimplementowane metody equals(Object o), hashCode() oraz toString().


Projektanci składni Java po raz kolejny stawiają na obiekt niemutowalny. Możemy się domyślać rozwoju Javy w kierunku programowania funkcyjnego.

Collection factory method (Java 9)

Wraz z wersją dziewiątą otrzymaliśmy bardzo przyjemny mechanizm umożliwiający zmniejszenie boilerplate’u, czyli fabryki dla kolekcji. Przykładowo gdy potrzebujemy utworzyć gdzieś w kodzie predefiniowaną kolekcję dowolnego typu, to do jej utworzenia możemy wykorzystać fabrykę, jak na przykładzie poniżej. 

Set<String> newLanguages = Set.of("pl","de","en");


Co ważne, kolekcje utworzone w taki sposób są niemutowalne. Próba modyfikacji stanu takiej kolekcji spowoduje wyjątek UnsupportedOperationException.

Ilość boilerplate’u uległa zmniejszeniu i pewnie to będzie kolejnym kryterium rozwoju składni Java.

Rozszerzenie Stream API (Java 9)

Java 8 wprowadziła do składni streamy, a w kolejnej wersji zostały dodane cztery dodatkowe metody, które mogą okazać się pomocne przy obcowaniu z tą klasą.

takeWhile() - przyjmuje predykat, następnie tworzy stream z elementów, dopóki wartość predykatu to true. Jeśli warunek dla elementu nie zostanie spełniony, to kolejne elementy nie są rozpatrywane.

Stream.of(3, 6, 9, 10, 12)
       .takeWhile(n -> n % 3 == 0)
       .forEach(System.out::println);


Taki stream z metodą takeWhile() zwróci:

3
6
9


Dla elementu 10 predykat zmienia swoją wartość na false i dalsza część streamu nie jest rozpatrywana, powstaje nowy stream z elementami (3, 6, 9).

dropWhile() - jest przeciwieństwem takeWhile(), tworzy stream z elementów, które zostałyby odrzucone przy metodzie takeWhile().

Stream.of(3, 6, 9, 10, 12)
       .dropWhile(n -> n % 3 == 0)
       .forEach(System.out::println);


Po takiej operacji zostanie zwrócony stream (10, 12).

ofNullable() - została wprowadzona, aby uniknąć null checków w streamie. Jeżeli wartość sprawdzana będzie nullem, zostanie zwrócony pusty Stream.

long countOne = Stream.ofNullable("Element").count();
System.out.println(countOne);


Z powyższego zostanie zwrócona wartość 1.

long countTwo = Stream.ofNullable(null).count();
System.out.println(countTwo);


Tutaj zostanie zwrócone 0, ponieważ przekazaliśmy wartość null do streama.

iterate() - jako parametr przyjmuje predykat hasNext, który zatrzyma iterowanie po kolekcji, jeżeli predykat nie będzie spełniony.

IntStream
       .iterate(2, x -> x <= 8, x -> x+ 2)
       .forEach(System.out::println);


Na wyjściu otrzymamy:

2
4
6
8

Instrukcja/wyrażenie switch

W Java 12 instrukcja switch otrzymała dwa nowe udogodnienia. Pierwszym z nich jest możliwość zwrócenia wartości z tej konstrukcji i przypisania jej do zmiennej, dzięki czemu switch może od tej pory być traktowany jako wyrażenie lub po prostu instrukcja sterująca. Projektanci składni zadbali również o bardziej eleganckie wywołanie case. Upodobnili składniowo switch case do wyrażenia lambda. W case możemy teraz także po przecinku wskazać kilka wartości.

String httpMessage = switch (httpCode) {
   case 200 -> "OK";
   case 404 -> "NOT FOUND";
   case 418, 696 -> "I'M A TEAPOT";
   default: {
       return "N/A";
   }
};


Java 13 przyniosła kolejne usprawnienie do składni switcha. Dostaliśmy słówko kluczowe yield, dzięki któremu możemy zwrócić wartość z case’ów. Nie musimy do tego celu używać słowa break. Dzięki temu powyższy przykład możemy teraz zapisać w taki sposób:

String httpMessage = switch (httpCode) {
   case 200 -> "OK";
   case 404 -> "NOT FOUND";
   case 418 -> "I'M A TEAPOT";
   default: {
       yield "N/A";
   }
};

String - text blocks

Wraz z wersją 13 pojawiła się w Javie opcja zapisania bloku tekstu, czyli zapisanie w Stringu wartości wieloliniowej bez używania znaku dodawania. Dodatkowo mamy możliwość łatwiejszego sterowania nową linią. Weszło to jednak jedynie w wersji preview. Domyślnie ta funkcja ma w pełni zadebiutować w Java 15. Więcej informacji na ten temat w dokumentacji JEP 378.

Poniższy przykład pokazuje nam zapisanie wieloliniowej wartości w Stringu we wcześniejszych wersjach Javy.

String menu = "1. Add new product \n"
       + "2. Delete product \n"
       + "3. Exit";


Nie trudno zauważyć, że przykład powyżej jest mało czytelny. Sterujemy pojawieniem się nowej linii za pomocą \n. W przypadku nowego zapisu zrobimy to po prostu w taki sposób:

String menu =
"""
1. Add new product
2. Delete product
3. Exit
""";


Wieloliniowe ciągi znaków są umieszczane między potrójnym cudzysłowem. Zapis wartości wieloliniowej z pomocą bloku tekstowego znacznie poprawia czytelność. Nie “zaśmiecamy” również tekstu znakami sterującymi, jak \t czy \n.

Podsumowanie

Java przechodzi bardzo powolny, ale rozważny rozwój składni. Takie podejście twórców języka ma bardzo pozytywny wpływ na stabilność zbudowanych na niej procesów biznesowych. Wydaje się, że nowy feature musi się sprawdzić w innym języku, by następnie mógł zostać dodany do składni Java. Gdy tak już się dzieje, jest zwykle testowany w wersji preview, kiedy społeczność ma szansę na zgłoszenie swoich uwag. W tym artykule pokazałem, moim zdaniem, najistotniejsze zmiany składniowe jakie otrzymaliśmy lub za niedługo otrzymamy wraz z nową wersją JDK.

<p>Loading...</p>