Streamy i wyrażenia lambda – Java 8

Share on facebook
Share on twitter
Share on linkedin

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:

  1. Stwórz model: User wraz z polami ID, Login, Password, Email, Age, Gender
  2. Stwórz liste Userów
  3. 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
  4. Przesortuj listę przy użyciu metody sort oraz wyrażenia lambda.

Kod z tej lekcji możesz znaleźć tutaj.

Kamil Klimek

Kamil Klimek

Pierwszy kalkulator napisany w języku Pascal w podstawówce. Później miałem trochę przygód z frontendem oraz PHP, na studiach poznałem C++ oraz Jave. Obecnie prawie 3 letnie doświadczenie jako Java full stack develop. Blog jest miejscem, dzięki któremu mogę się dzielić wiedzą i pomagać innym w nauce programowania.
Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x