JavaRush /Blog Java /Random-PL /Przerwa kawowa #155. 10 najlepszych funkcji w Javie

Przerwa kawowa #155. 10 najlepszych funkcji w Javie

Opublikowano w grupie Random-PL

10 najlepszych funkcji w Javie

Źródło: DZone W tym artykule wymieniono dziesięć funkcji programowania w języku Java, które są często wykorzystywane przez programistów w ich codziennej pracy. Przerwa kawowa #155.  10 najlepszych funkcji w Javie - 1

1. Metoda fabryczna kolekcji

Kolekcje są jedną z najczęściej używanych funkcji w programowaniu. Służą jako pojemnik, w którym przechowujemy przedmioty i przekazujemy je dalej. Kolekcje służą również do sortowania, wyszukiwania i powtarzania obiektów, co znacznie ułatwia życie programisty. Mają kilka podstawowych interfejsów, takich jak Lista, Zestaw, Mapa, a także kilka implementacji. Wielu programistom tradycyjny sposób tworzenia kolekcji i map może wydawać się gadatliwy. Dlatego w Java 9 wprowadzono kilka zwięzłych metod fabrycznych. Lista :
List countries = List.of("Bangladesh", "Canada", "United States", "Tuvalu");
Ustawić :
Set countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Mapa :
Map countriesByPopulation = Map.of("Bangladesh", 164_689_383,
                                                            "Canada", 37_742_154,
                                                            "United States", 331_002_651,
                                                            "Tuvalu", 11_792);
Metoda fabryczna jest bardzo przydatna, gdy chcemy stworzyć niezmienne kontenery. Jeśli jednak zamierzasz tworzyć modyfikowalne kolekcje, zaleca się zastosowanie tradycyjnego podejścia.

2. Wnioskowanie o typie lokalnym

W Java 10 dodano wnioskowanie o typie zmiennych lokalnych. Wcześniej programiści musieli dwukrotnie określać typy podczas deklarowania i inicjowania obiektu. To było bardzo męczące. Spójrz na następujący przykład:
Map> properties = new HashMap<>();
Tutaj wskazany jest rodzaj informacji po obu stronach. Jeśli zdefiniujemy go w jednym miejscu, to czytnik kodu i kompilator Java bez problemu zrozumieją, że musi to być typ Map. Wnioskowanie o typie lokalnym właśnie to robi. Oto przykład:
var properties = new HashMap>();
Teraz wszystko jest napisane tylko raz i kod nie wygląda dużo gorzej. A kiedy wywołujemy metodę i zapisujemy wynik w zmiennej, kod staje się jeszcze krótszy. Przykład:
var properties = getProperties();
I dalej:
var countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Chociaż wnioskowanie o typie lokalnym wydaje się wygodną funkcją, niektórzy ją krytykują. Niektórzy programiści twierdzą, że zmniejsza to czytelność. A to jest ważniejsze niż zwięzłość.

3. Zaawansowane wyrażenia przełączające

Tradycyjna instrukcja switch jest obecna w Javie od samego początku i przypominała wówczas C i C++. To było w porządku, ale wraz z ewolucją języka operator ten nie oferował nam żadnych ulepszeń aż do Java 14. Oczywiście miał pewne wady. Najbardziej znany był błąd : aby rozwiązać ten problem, programiści użyli instrukcji break, które w dużej mierze są kodem szablonowym. Jednak w Javie 14 wprowadzono ulepszoną wersję instrukcji switch ze znacznie większą listą funkcji. Teraz nie musimy już dodawać instrukcji break, co rozwiązuje problem awarii. Dodatkowo instrukcja switch może zwrócić wartość, co oznacza, że ​​możemy jej użyć jako wyrażenia i przypisać ją do zmiennej.
int day = 5;
String result = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Unexpected value: " + day;
};

4. Zapisy

Chociaż Rekordy to stosunkowo nowa funkcja wprowadzona w Javie 16, wielu programistów uważa ją za bardzo przydatną, głównie ze względu na tworzenie niezmiennych obiektów. Często potrzebujemy obiektów danych w naszym programie do przechowywania lub przekazywania wartości z jednej metody do drugiej. Przykładowo klasa do przenoszenia współrzędnych x, y i z, którą napiszemy następująco:
package ca.bazlur.playground;

import java.util.Objects;

public final class Point {
    private final int x;
    private final int y;
    private final int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public int x() {
        return x;
    }

    public int y() {
        return y;
    }

    public int z() {
        return z;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (Point) obj;
        return this.x == that.x &&
                this.y == that.y &&
                this.z == that.z;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y, z);
    }

    @Override
    public String toString() {
        return "Point[" +
                "x=" + x + ", " +
                "y=" + y + ", " +
                "z=" + z + ']';
    }

}
Klasa wydaje się zbyt rozwlekła. Za pomocą wpisów cały ten kod można zastąpić bardziej zwięzłą wersją:
package ca.bazlur.playground;

public record Point(int x, int y, int z) {
}

5. Opcjonalne

Metoda to umowa, w której definiujemy warunki. Określamy parametry wraz z ich typem, a także typem zwracanym. Oczekujemy wówczas, że po wywołaniu metody będzie ona zachowywała się zgodnie z umową. Jednak często kończy się to wartością null w metodzie zamiast wartości określonego typu. To jest błąd. Aby rozwiązać ten problem, inicjator zazwyczaj testuje wartość za pomocą warunku if, niezależnie od tego, czy wartość ma wartość null, czy nie. Przykład:
public class Playground {

    public static void main(String[] args) {
        String name = findName();
        if (name != null) {
            System.out.println("Length of the name : " + name.length());
        }
    }

    public static String findName() {
        return null;
    }
}
Spójrz na powyższy kod. Metoda findName powinna zwracać String , ale zwraca wartość null. Aby rozwiązać problem, inicjator musi teraz najpierw sprawdzić wartości null. Jeśli inicjator zapomni to zrobić, otrzymamy wyjątek NullPointerException . Z drugiej strony, gdyby sygnatura metody wskazywała na możliwość braku zwrotu, to rozwiązałoby to całe zamieszanie. I w tym przypadku Opcjonalne może nam pomóc .
import java.util.Optional;

public class Playground {

    public static void main(String[] args) {
        Optional optionalName = findName();
        optionalName.ifPresent(name -> {
            System.out.println("Length of the name : " + name.length());
        });
    }

    public static Optional findName() {
        return Optional.empty();
    }
}
W tym miejscu przepisaliśmy metodę findName z opcją opcjonalną , aby nie zwracała żadnej wartości. To ostrzega programistów z wyprzedzeniem i rozwiązuje problem.

6. API daty i godziny w Javie

Każdy programista jest w takim czy innym stopniu zdezorientowany, jeśli chodzi o obliczanie daty i godziny. To nie jest przesada. Było to głównie spowodowane brakiem dobrego API Java do pracy z datami i godzinami. Teraz ten problem nie jest już istotny, ponieważ Java 8 wprowadziła doskonały zestaw API w pakiecie java.time, który rozwiązuje wszystkie problemy związane z datą i czasem. Pakiet java.time posiada wiele interfejsów i klas, które eliminują większość problemów, łącznie ze strefami czasowymi. Najczęściej używane klasy w tym pakiecie to:
  • Data lokalna
  • Czas lokalny
  • Data i godzina lokalna
  • Czas trwania
  • Okres
  • StrefowaDataGodzina
Przykład wykorzystania klas z pakietu java.time:
import java.time.LocalDate;
import java.time.Month;

public class Playground3 {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2022, Month.APRIL, 4);
        System.out.println("year = " + date.getYear());
        System.out.println("month = " + date.getMonth());
        System.out.println("DayOfMonth = " + date.getDayOfMonth());
        System.out.println("DayOfWeek = " + date.getDayOfWeek());
        System.out.println("isLeapYear = " + date.isLeapYear());
    }
}
Przykład wykorzystania klasy LocalTime do obliczania czasu:
LocalTime time = LocalTime.of(20, 30);
int hour = time.getHour();
int minute = time.getMinute();
time = time.withSecond(6);
time = time.plusMinutes(3);
Dodawanie strefy czasowej:
ZoneId zone = ZoneId.of("Canada/Eastern");
LocalDate localDate = LocalDate.of(2022, Month.APRIL, 4);
ZonedDateTime zonedDateTime = date.atStartOfDay(zone);

7. Wyjątek NullPointer

Każdy programista nienawidzi wyjątku NullPointerException. Może to być szczególnie trudne, gdy StackTrace nie dostarcza przydatnych informacji o tym, na czym dokładnie polega problem. Aby to zademonstrować, spójrzmy na przykładowy kod:
package com.bazlur;

public class Main {

    public static void main(String[] args) {
        User user = null;
        getLengthOfUsersName(user);
    }

    public static void getLengthOfUsersName(User user) {
        System.out.println("Length of first name: " + user.getName().getFirstName());
    }
}

class User {
    private Name name;
    private String email;

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

   //getter
   //setter
}

class Name {
    private String firstName;
    private String lastName;

    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

   //getter
   //setter
}
Spójrz na podstawową metodę w tym fragmencie. Widzimy, że w następnej kolejności zostanie zgłoszony wyjątek NullPointerException . Jeśli uruchomimy i skompilujemy kod w wersji wcześniejszej niż Java 14, otrzymamy następujący StackTrace:
Exception in thread "main" java.lang.NullPointerException
at com.bazlur.Main.getLengthOfUsersName(Main.java:11)
at com.bazlur.Main.main(Main.java:7)
Jest tu bardzo mało informacji o tym, gdzie i dlaczego wystąpił wyjątek NullPointerException . Jednak w Javie 14 i nowszych wersjach znacznie więcej informacji otrzymujemy w StackTrace, co jest bardzo wygodne. W Javie 14 zobaczymy:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "ca.bazlur.playground.User.getName()" because "user" is null
at ca.bazlur.playground.Main.getLengthOfUsersName(Main.java:12)
at ca.bazlur.playground.Main.main(Main.java:8)

8. Pełna przyszłość

Programy piszemy linia po linii i zazwyczaj są one wykonywane linia po linii. Są jednak chwile, kiedy potrzebujemy wykonania równoległego, aby przyspieszyć program. W tym celu zwykle używamy wątku Java. Programowanie wątków w Javie nie zawsze polega na programowaniu równoległym. Zamiast tego daje nam możliwość skomponowania kilku niezależnych modułów programu, które będą wykonywać się niezależnie, a często nawet asynchronicznie. Jednak programowanie wątków jest dość trudne, szczególnie dla początkujących. Właśnie dlatego Java 8 oferuje prostsze API, które pozwala na asynchroniczne wykonywanie części programu. Zobaczmy przykład. Załóżmy, że musimy wywołać trzy interfejsy API REST, a następnie połączyć wyniki. Możemy je wywoływać jeden po drugim. Jeśli każdy z nich zajmie około 200 milisekund, to całkowity czas ich odebrania wyniesie 600 milisekund. A gdybyśmy mogli uruchomić je równolegle? Ponieważ nowoczesne procesory są zazwyczaj wielordzeniowe, mogą z łatwością obsłużyć trzy wywołania odpoczynku na trzech różnych procesorach. Dzięki CompletableFuture możemy to łatwo zrobić.
package ca.bazlur.playground;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class SocialMediaService {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        var service = new SocialMediaService();

        var start = Instant.now();
        var posts = service.fetchAllPost().get();
        var duration = Duration.between(start, Instant.now());

        System.out.println("Total time taken: " + duration.toMillis());
    }

    public CompletableFuture> fetchAllPost() {
        var facebook = CompletableFuture.supplyAsync(this::fetchPostFromFacebook);
        var linkedIn = CompletableFuture.supplyAsync(this::fetchPostFromLinkedIn);
        var twitter = CompletableFuture.supplyAsync(this::fetchPostFromTwitter);

        var futures = List.of(facebook, linkedIn, twitter);

        return CompletableFuture.allOf(futures.toArray(futures.toArray(new CompletableFuture[0])))
                .thenApply(future -> futures.stream()
                        .map(CompletableFuture::join)
                        .toList());
    }
    private String fetchPostFromTwitter() {
        sleep(200);
        return "Twitter";
    }

    private String fetchPostFromLinkedIn() {
        sleep(200);
        return "LinkedIn";
    }

    private String fetchPostFromFacebook() {
        sleep(200);
        return "Facebook";
    }

    private void sleep(int millis) {
        try {
            TimeUnit.MILLISECONDS.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

9. Wyrażenia lambda

Wyrażenia lambda są prawdopodobnie najpotężniejszą funkcją języka Java. Zmienili sposób, w jaki piszemy kod. Wyrażenie lambda przypomina funkcję anonimową, która może przyjmować argumenty i zwracać wartość. Możemy przypisać funkcję do zmiennej i przekazać ją jako argumenty do metody, a metoda może ją zwrócić. On ma ciało. Jedyną różnicą w stosunku do tej metody jest brak nazwy. Wyrażenia są krótkie i zwięzłe. Zwykle nie zawierają dużej ilości szablonowego kodu. Zobaczmy przykład, w którym musimy wyświetlić listę wszystkich plików w katalogu z rozszerzeniem .java.
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(".java");
    }
});
Jeśli przyjrzysz się bliżej temu fragmentowi kodu, zauważymy, że przekazaliśmy metodę anonimowej klasy wewnętrznej list() . Natomiast w klasie wewnętrznej umieściliśmy logikę filtrowania plików. Zasadniczo interesuje nas ta część logiki, a nie wzór wokół logiki. Wyrażenie lambda pozwala nam usunąć cały szablon i możemy napisać interesujący nas kod. Oto przykład:
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
Oczywiście to tylko jeden przykład; wyrażenia lambda mają wiele innych zalet.

10. API strumieniowe

W naszej codziennej pracy jednym z typowych zadań jest przetwarzanie zbioru danych. Ma kilka typowych operacji, takich jak filtrowanie, przekształcanie i zbieranie wyników. Przed wersją Java 8 takie operacje były z natury koniecznością. Musieliśmy napisać kod uwzględniający nasze intencje (tj. to, co chcieliśmy osiągnąć) i sposób, w jaki chcielibyśmy to zrobić. Dzięki wynalezieniu wyrażenia lambda i Stream API możemy teraz deklaratywnie pisać funkcje przetwarzania danych. Wskazujemy jedynie nasz zamiar i nie musimy zapisywać, w jaki sposób uzyskamy wynik. Oto przykład: Mamy listę książek i chcemy znaleźć wszystkie nazwy książek Java, oddzielone przecinkami i posortowane.
public static String getJavaBooks(List books) {
    return books.stream()
            .filter(book -> Objects.equals(book.language(), "Java"))
            .sorted(Comparator.comparing(Book::price))
            .map(Book::name)
            .collect(Collectors.joining(", "));
}
Powyższy kod jest prosty, czytelny i zwięzły. Ale poniżej możesz zobaczyć alternatywny kod imperatywny:
public static String getJavaBooksImperatively(List books) {
    var filteredBook = new ArrayList();
    for (Book book : books) {
        if (Objects.equals(book.language(), "Java")){
            filteredBook.add(book);
        }
    }
    filteredBook.sort(new Comparator() {
        @Override
        public int compare(Book o1, Book o2) {
            return Integer.compare(o1.price(), o2.price());
        }
    });

    var joiner = new StringJoiner(",");
    for (Book book : filteredBook) {
        joiner.add(book.name());
    }

    return joiner.toString();
}
Chociaż obie metody zwracają tę samą wartość, wyraźnie widać różnicę.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION