Java 8
Od samego początku kursu namawiałem do używania JDK w wersji 8, czyli wersji Javy, do której weszło naprawdę sporo zmian. Większość z nich (a nawet wszystkie) warto znać, dlatego w tym wpisie pokażę Ci kilka z nich. Mam nadzieję, że od dziś będziesz z nich korzystał, a Twój kod będzie jeszcze czytelniejszy.
Wyrażenia lambda
Bardzo dużą zmianą było wprowadzenie wyrażeń lambda, które zastąpiły klasy anonimowe.
Czym są klasy anonimowe? Są to klasy, które są otworzone w dokładnie jednym egzemplarzu i zazwyczaj od razu używane w kodzie.
Przykładem takiej klasy może być:
interface Gretting { void hello(); } public class Main { public static void main(String[] args) { Gretting gretting = new Gretting() { @Override public void hello() { System.out.println("Hello world!"); } }; }
Spójrz, że ze względu, że naszym typem jest typ interfejsu to automatycznie musimy stworzyć implementację – nasza klasa jest anonimowa, w końcu nie ma żadnej nazwy, jest tylko przypisywana do pola gretting. 😉
Należy zwrócić uwagę, że tak naprawdę dla kompilatora jest to klasa, która tylko implementuje interfejs Gretting – nie jest instancją interfejsu Gretting!
Skoro już wiesz czym są klasy anonimowe to przytoczę jeszcze jedno ich zastosowanie – tworzenia komparatora:
List<String> names = new LinkedList<>(Arrays.asList("XYZ", "PABLO", "GETO", "GORGEU")); names.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } });
Czyli jako argument podajemy klasę anonimową implementującą interfejs Comparator, który wymaga metoda sort. I niestety jest to dłuższe rozwiązanie, które możemy zastąpić nowymi wyrażeniami lambda.
Używając lambdy wywołanie sort wygląda tak:
names.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
Już tłumaczę jak wygląda struktura lambdy:
() -> {}
Taką strukturę ma lambda, która nic nie zwraca.
(s1, s2) -> {}
Taką strukturę ma lambda, która przyjmuje argumenty s1 i s2 oraz nic nie zwraca
(s1, s2) -> s1.compareToIgnoreCase(s2)
A tak, która przyjmuje argument i zwraca wynik wywołania metody compareToIgnoreCase – dzięki temu osiągnęliśmy ten sam efekt bez tworzenia klas anonimowych.
Streamy
Kolejnym ważnym featurem, który wszedł są strumienie. Strumienie służą do przetwarzania danych – czyli do pracy na kolekcjach. Nie dają one tylko czytelniejszy zapis, ale również zrównoleglenie obliczeń na dużych zbiorach danych – czyli wykonywać operacje szybciej niż przy użyciu zwykłych pętli.
Najlepiej przejdźmy od razu do przykładu – przypuśćmy, że mamy klasę Student:
package pl.maniaq; import java.util.Objects; public class Student { private Long id; private String name; private String lastName; public Student(Long id, String name, String lastName) { this.id = id; this.name = name; this.lastName = lastName; } public Long getId() { return id; } public String getName() { return name; } public String getLastName() { return lastName; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return Objects.equals(id, student.id) && Objects.equals(name, student.name) && Objects.equals(lastName, student.lastName); } @Override public int hashCode() { return Objects.hash(id, name, lastName); } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", lastName='" + lastName + '\'' + '}'; } }
Oraz listę studentów:
public class Main { public static void main(String[] args) { List<Student> students = new LinkedList<>(Arrays.asList( new Student(1L, "Pablo", "Escabo"), new Student(2L, "Gorgeu", "Tury"), new Student(3L, "Geto", "Urlo") )); } }
W której chcemy znaleźć studenta o ID równym 1. Klasycznie zrobilibyśmy to tak:
final Long searchStudentId = 1L; for(Student student : students) { if(student.getId().equals(searchStudentId)) { System.out.println(student); break; } }
Jednak możemy to zrobić przy użyciu strumienii. Na początku bierzemy naszą kolekcję i tworzymy z niej strumień przy użyciu stream()
final Student foundStudent = students.stream()
Następnie na tym strumieniu przy użyciu lambdy dodajemy warunek filtrowania danych – warunkiem oczywiscie jest równość ID.
final Student foundStudent = students.stream() .filter(student -> student.getId().equals(searchStudentId))
Ze względu, że strumień jest ciągle zbiorem danych to musimy z niego wyciągnąć jakiś element:
final Student foundStudent = students.stream() .filter(student -> student.getId().equals(searchStudentId)) .findFirst()
Strumienie zwracają obiekt Optional<T> – czyli w naszym przypadku Optional<Student> to musimy wyciągnąć naszego studenta metodą get lub orElse.
final Student foundStudent = students.stream() .filter(student -> student.getId().equals(searchStudentId)) .findFirst() .orElse(new Student(1L, "DEFAULT", "DEFAULT"));
Więcej o Optionalach możesz przeczytać w tym artykule.
Po takich operacjach otrzymujemy:
Student{id=1, name='Pablo', lastName='Escabo'}
Czyli wszystko ładnie działa przy użyciu strumienii. 😉
Pokazałem ci tutaj tylko jedno z zastosować strumienii, a ich jest więcej np. map do zmiany stanu obiektów lub sorted do sortowania. Więcej o strumieniach możesz przeczytać tutaj.
Default i static
Póki co w interfejsach można było implementować tylko metody, które musiały implementować wszystkie klasy implementujące ten interfejs.
Od Javy 8 się to zmieniło – od tego momentu możemy definiować metody wraz z ich implementacją przy użyciu słowa kluczowe default.
public interface Vehicle { void drive(); default void stop() { System.out.println("Domyślnie wciskam hamulec"); }; }
Weszła również możliwość przypisywania metod statycznych do interfejsów – tak samo jak robimy to w zwykłych klasach
public interface Vehicle { static void vehicleInfo() { System.out.println("Jakies informacje o pojezdzie"); } void drive(); default void stop() { System.out.println("Domyślnie wciskam hamulec"); }; }
Zaimplementujmy sobie jeszcze ten interfejs:
package pl.maniaq; public class Car implements Vehicle { @Override public void drive() { System.out.println("Drive car"); } }
Zauważ, że nie musimy implementować metody stop(), ponieważ zawiera ona już implementację w interfejsie. Warto pamiętać też, że zawsze możemy nadpisać implementację z interfejsu:
package pl.maniaq; public class Car implements Vehicle { @Override public void drive() { System.out.println("Drive car"); } @Override public void stop() { System.out.println("Overrided method from interface."); } }
Podsumowanie
Pokazałem Ci tutaj według mnie trzy najważniejsze zmiany jakie weszły wraz z Java 8. Warto z pewnością z nich korzystać – niektóre poprawiają czytelność kodu, a niektóre potrafią nawet przyśpieszyć wykonywane operację, poniewaz są wykonywane wielowątkowo.
Aby utrwalić sobie wyżej opisane zmiany Javy wykonaj poniższe zadania:
- Stwórz model: User wraz z polami ID, Login, Password, Email, Age, Gender
- Stwórz liste Userów
- Przy użyciu strumieni przefiltruj listę tak, aby znajdowały się na niej same kobiety i zwiększ wiek każdej z nich o jeden
- Przesortuj listę przy użyciu metody sort oraz wyrażenia lambda.
Kod z tej lekcji możesz znaleźć tutaj.