Java 8 ÔÇö przewodnik po nowoczesnej Javie

> Dodaj do ulubionych

ÔÇ×Java jeszcze nie umar┼éa ÔÇö i ludzie zaczynaj─ů sobie zdawa─ç z tego spraw─ÖÔÇŁ.

Witajcie w moim wprowadzeniu do j─Özyka programowania Java 8. W niniejszym przewodniku krok po kroku omawiam wszystkie nowe sk┼éadniki j─Özyka. Dzi─Öki kr├│tkim, nieskomplikowanym przyk┼éadom kodu nauczycie si─Ö korzysta─ç z domy┼Ťlnych metod interfejs├│w, wyra┼╝e┼ä lambda, referencji do metod i powtarzalnych adnotacji. Poznacie najnowsze zmiany w interfejsach API dotycz─ůce m.in. strumieni, interfejs├│w funkcyjnych, rozszerze┼ä s┼éownik├│w oraz nowy API daty i czasu. Zero przegadanych wyja┼Ťnie┼ä ÔÇö tylko fragmenty kodu opatrzone komentarzami. Mi┼éej lektury!

Ten artyku┼é zosta┼é po raz pierwszy opublikowany na moim blogu. Zach─Öcam, by┼Ťcie ┼Ťledzili mnie na Twitterze.

Java 8 ÔÇö metody domy┼Ťlne w interfejsach

Java 8 umo┼╝liwia dodawanie do interfejs├│w implementacji metod nieabstrakcyjnych przy pomocy s┼éowa kluczowego default. Ten sk┼éadnik funkcjonalno┼Ťci nosi r├│wnie┼╝ nazw─Ö wirtualnych metod rozszerzaj─ůcych.

Oto nasz pierwszy przykład:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Interfejs Formula zawiera definicj─Ö nie tylko abstrakcyjnej metody calculate, lecz tak┼╝e domy┼Ťlnej metody sqrt. Klasy konkretne musz─ů implementowa─ç jedynie abstrakcyjn─ů metod─Ö calculate, natomiast domy┼Ťlna metoda sqrt jest dost─Öpna od razu.

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

Powyżej widzimy implementację obiektu anonimowego. Kod jest jednak nieco rozwlekły: proste działanie matematyczne sqrt(a * 100) zajmuje aż 6 wierszy. Jak się przekonamy w kolejnej sekcji artykułu, w Javie 8 pojedyncze obiekty metod można zaimplementować w o wiele lepszy sposób.

Wyra┼╝enia lambda

Zacznijmy od prostego przyk┼éadu ilustruj─ůcego sortowanie listy ┼éa┼äcuch├│w we wcze┼Ťniejszych wersjach Javy:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

Statyczna metoda pomocnicza Collections.sort przyjmuje list─Ö i komparator, dzi─Öki kt├│remu ustala kolejno┼Ť─ç element├│w przekazanej listy. Cz─Östo tworzymy wi─Öc anonimowe komparatory, kt├│re nast─Öpnie przekazujemy metodzie sortuj─ůcej.

Natomiast w ├│smej ods┼éonie Javy nie tworzy si─Ö ju┼╝ w k├│┼éko obiekt├│w anonimowych ÔÇö dost─Öpna jest znacznie kr├│tsza sk┼éadnia wyra┼╝e┼ä lamba:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

Jak wida─ç, kod jest o wiele kr├│tszy i czytelniejszy. A da si─Ö go skr├│ci─ç jeszcze bardziej:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

W przypadku metod, kt├│rych tre┼Ť─ç g┼é├│wna zawiera si─Ö w jednym wierszu mo┼╝na zrezygnowa─ç z klamr i s┼éowa kluczowego return. To jednak nie koniec skracania:

names.sort((a, b) -> b.compareTo(a));

Lista zawiera teraz metod─Ö sort. Ponadto kompilator Javy zna ju┼╝ typy przekazanych parametr├│w, zatem i je mo┼╝emy pomin─ů─ç. Przyjrzyjmy si─Ö teraz praktycznym zastosowaniom wyra┼╝e┼ä lambda.

Interfejsy funkcyjne

Jak wyra┼╝enia lambda wpisuj─ů si─Ö w system typ├│w Javy? Ka┼╝da lambda odpowiada danemu typowi, okre┼Ťlonemu w interfejsie. Tak zwany interfejs funkcyjny musi zawiera─ç dok┼éadnie jedn─ů deklaracj─Ö metody abstrakcyjnej. Ka┼╝de wyra┼╝enie lambda danego typu zostanie powi─ůzane z t─ů metod─ů. Jako ┼╝e metody domy┼Ťlne nie s─ů abstrakcyjne, nic nie stoi na przeszkodzie by doda─ç je do interfejsu funkcyjnego.

Posta─ç wyra┼╝enia lambda mo┼╝e przyj─ů─ç dowolny interfejs, o ile zawiera tylko jedn─ů metod─Ö abstrakcyjn─ů. By mie─ç pewno┼Ť─ç, ┼╝e warunek ten jest spe┼éniony, nale┼╝y doda─ç do kodu adnotacj─Ö @FunctionalInterface. Kompilator zna t─Ö adnotacj─Ö i zg┼éosi b┼é─ůd kompilacji, gdy tylko spr├│bujemy doda─ç do interfejsu drug─ů deklaracj─Ö metody abstrakcyjnej.

Przykład:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

Warto pamiętać, że powyższy kod będzie działał prawidłowo także bez adnotacji @FunctionalInterface.

Referencje do metod i konstruktor├│w

Przytoczony wy┼╝ej przyk┼éad mo┼╝na upro┼Ťci─ç jeszcze bardziej, pos┼éuguj─ůc si─Ö referencjami do metod statycznych:

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

W Javie 8 referencje do metod i konstruktor├│w mog─ů by─ç przekazywane za pomoc─ů s┼éowa kluczowego ::. Powy┼╝szy przyk┼éad ilustruje referencj─Ö do metody statycznej, jednak mo┼╝liwe jest tak┼╝e tworzenie referencji do metod obiekt├│w:

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

Zobaczmy jak s┼éowo kluczowe :: dzia┼éa w przypadku konstruktor├│w. Na pocz─ůtek zdefiniujmy przyk┼éadowy komponent bean z r├│┼╝nymi konstruktorami:

class Person {
    String firstName;
    String lastName;

    Person() {}

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

Nast─Öpnie okre┼Ťlimy interfejs PersonFactory s┼éu┼╝─ůcy do dodawania nowych os├│b:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

Nie b─Ödziemy implementowa─ç fabryki r─Öcznie ÔÇô zamiast tego po┼é─ůczymy wszystkie sk┼éadniki, korzystaj─ůc z odwo┼éania do konstruktora:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

Utworzyli┼Ťmy referencj─Ö do konstruktora Person za pomoc─ů sk┼éadni Person::new. Kompilator Javy automatycznie wybierze odpowiedni konstruktor o sygnaturze zgodnej z PersonFactory.create.

Zakres wyrażeń lambda

Odwo┼éywanie si─Ö z wyra┼╝e┼ä lambda do zmiennych z zakresu zewn─Ötrznego dzia┼éa podobnie jak w przypadku obiekt├│w anonimowych. Zmienne finalne s─ů dost─Öpne z lokalnego zasi─Ögu zewn─Ötrznego, p├│l instancji i zmiennych statycznych.

Dost─Öp do zmiennych lokalnych

Finalne zmienne lokalne można odczytać z poziomu zakresu zewnętrznego wyrażeń lambda:

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

W przeciwieństwie do obiektów anonimowych zmienna num nie musi być zadeklarowana jako finalna. Poniższy kod jest zatem prawidłowy:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

By kod zosta┼é skompilowany, warto┼Ť─ç num musi by─ç jednak finalna niejawnie. Kompilacja tego kodu nie powiedzie si─Ö:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

Zabronione jest r├│wnie┼╝ nadpisywanie warto┼Ťci num z poziomu wyra┼╝enia lambda.

Dost─Öp do p├│l i zmiennych statycznych

W przeciwieństwie do zmiennych lokalnych, pola instancji i zmienne statyczne można zarówno odczytać jak i nadpisać z poziomu lambd. Podobnie działa to w obiektach anonimowych.

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

Dost─Öp do domy┼Ťlnych metod interfejs├│w

Pami─Ötacie przyk┼éad obiektu formula przytoczony w pierwszej sekcji artyku┼éu? Interfejs Formula definiuje domy┼Ťln─ů metod─Ö sqrt, do kt├│rej mo┼╝na uzyska─ç dost─Öp z ka┼╝dego egzemplarza, w tym z obiektu anonimowego. W przypadku lambd nie jest to mo┼╝liwe.

Z poziomu wyra┼╝enia lambda nie mo┼╝na uzyska─ç dost─Öpu do metod domy┼Ťlnych. Kompilacja poni┼╝szego kodu zako┼äczy si─Ö niepowodzeniem:

Formula formula = (a) -> sqrt( a * 100);

Wbudowane interfejsy funkcyjne

API JDK 1.8 zawiera wiele wbudowanych interfejs├│w funkcyjnych. Niekt├│re z nich, np. Comparator czy Runnable, s─ů nam dobrze znane ze starszych wersji Javy. Te istniej─ůce ju┼╝ interfejsy zosta┼éy rozszerzone o obs┼éug─Ö lambd za pomoc─ů adnotacji @FunctionalInterface.

API Javy 8 jest jednak bogate r├│wnie┼╝ w wiele nowych interfejs├│w, kt├│re maj─ů za zadanie u┼éatwi─ç nam prac─Ö. Niekt├│re z nich wywodz─ů si─Ö z biblioteki Google Guava. Nawet je┼Ťli jest wam ona znana, zwr├│─çcie uwag─Ö na to jak zaczerpni─Öte z niej interfejsy zosta┼éy rozszerzone dzi─Öki zastosowaniu u┼╝ytecznych rozszerze┼ä metod.

Predykaty

Predykaty to jednoargumentowe funkcje o warto┼Ťci logicznej. Ich interfejs zawiera r├│┼╝ne metody domy┼Ťlne s┼éu┼╝─ůce do ┼é─ůczenia predykat├│w w z┼éo┼╝one operacje logiczne (koniunkcj─Ö, alternatyw─Ö, negacj─Ö).

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

Funkcje

Funkcje przyjmuj─ů jeden argument i zwracaj─ů wynik. Kilka funkcji (np. compose, andThen) mo┼╝na po┼é─ůczy─ç w ┼éa┼äcuch za pomoc─ů metod domy┼Ťlnych.

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

Dostawcy

Dostawcy zwracaj─ů wynik o okre┼Ťlonym typie generycznym. W przeciwie┼ästwie do funkcji dostawcy nie przyjmuj─ů argument├│w.

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

Konsumenci

Konsumenci reprezentuj─ů operacje, jakie maj─ů zosta─ç wykonane na pojedynczym argumencie wej┼Ťciowym.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Komparatory

Komparatory s─ů nam dobrze znane z poprzednich wersji Javy. W Javie 8 interfejs ten zosta┼é rozszerzony o metody domy┼Ťlne.

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

Warto┼Ťci opcjonalne

Optional to nie interfejs funkcyjny, lecz sprytna klasa pomagaj─ůca unikn─ů─ç wyj─ůtku NullPointerException. Odgrywa ona wa┼╝n─ů rol─Ö w kontek┼Ťcie kolejnej sekcji, zatem przyjrzyjmy si─Ö na czym polega jej dzia┼éanie.

Egzemplarz klasy Optional to prosty kontener przechowuj─ůcy warto┼Ť─ç null lub inn─ů ni┼╝ null. Wyobra┼║my sobie metod─Ö, kt├│ra mo┼╝e zwraca─ç wynik o warto┼Ťci innej ni┼╝ null, a czasem mo┼╝e nie zwraca─ç niczego. Zamiast warto┼Ťci null, w Javie 8 zwracamy obiekt klasy Optional.

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

Strumienie

Strumie┼ä stanowi sekwencj─Ö element├│w, na kt├│rych mo┼╝e zosta─ç wykonana co najmniej jedna operacja. Operacje strumieniowe mog─ů by─ç po┼Ťrednie lub ko┼äcz─ůce. Operacje ko┼äcz─ůce zwracaj─ů wynik okre┼Ťlnego typu, natomiast po┼Ťrednie zwracaj─ů sam strumie┼ä, co pozwala po┼é─ůczy─ç w ┼éa┼äcuch kilka wywo┼éa┼ä metody z rz─Ödu. Strumienie s─ů tworzone na podstawie ┼║r├│d┼éa, np. kolekcji takiej jak lista czy zbi├│r (s┼éowniki nie s─ů obs┼éugiwane). Operacje strumieniowe mog─ů by─ç wykonywane sekwencyjnie b─ůd┼║ r├│wnolegle.

Zach─Öcam do zapoznania si─Ö tak┼╝e z bibliotek─ů Stream.js, javascriptowym przeniesieniem strumieni z Javy 8.

Na pocz─ůtek zobaczymy, jak dzia┼éaj─ů strumienie sekwencyjne. Zaczniemy od utworzenia prostego ┼║r├│d┼éa w postaci listy ┼éa┼äcuch├│w:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Poniewa┼╝ kolekcje w Javie 8 s─ů rozszerzone, strumienie mo┼╝na utworzy─ç w ┼éatwy spos├│b, wywo┼éuj─ůc metod─Ö Collection.stream() lub Collection.parallelStream(). W poni┼╝szych sekcjach opisuj─Ö najcz─Ö┼Ťciej stosowane operacje strumieniowe.

Filter

Operacja filter przyjmuje predykat, na podstawie kt├│rego filtruje wszystkie elementy strumienia. To operacja po┼Ťrednia, dzi─Öki czemu na wyniku mo┼╝emy wykona─ç inn─ů operacj─Ö strumieniow─ů (forEach). ForEach przyjmuje konsumenta, kt├│rego operacje maj─ů zosta─ç wykonane dla ka┼╝dego elementu w przefiltrowanym strumieniu. To operacja ko┼äcz─ůca. Typ zwrotny to void, dlatego nie mo┼╝na wywo┼éa─ç kolejnej operacji strumieniowej.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"

Sorted

Sorted to operacja po┼Ťrednia zwracaj─ůca posortowany widok strumienia. Elementy s─ů sortowane w porz─ůdku naturalnym, chyba ┼╝e przeka┼╝emy w┼éasny komparator.

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa1", "aaa2"

Nale┼╝y pami─Öta─ç, ┼╝e operacja sorted tworzy jedynie posortowany widok strumienia, nie manipuluj─ůc przy tym porz─ůdkiem element├│w samej kolekcji ┼║r├│d┼éowej. Porz─ůdek element├│w w stringCollection pozostaje bez zmian:

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map

Po┼Ťrednia operacja map konwertuje ka┼╝dy element na inny obiekt za po┼Ťrednictwem podanej funkcji. W poni┼╝szym przyk┼éadzie zamieniamy ┼éa┼äcuch na ┼éa┼äcuch zapisany wielkimi literami, lecz za pomoc─ů tej operacji mogliby┼Ťmy te┼╝ zmieni─ç typ dowolnego obiektu na inny. Typ generyczny wynikowego strumienia zale┼╝y od typu generycznego przekazanej funkcji.

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match

Korzystaj─ůc z r├│┼╝nych operacji dopasowania mo┼╝na sprawdzi─ç, czy dany predykat odpowiada strumieniowi. Wszystkie tego typu operacje s─ů ko┼äcz─ůce i zwracaj─ů warto┼Ť─ç logiczn─ů.

boolean anyStartsWithA =
    stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA =
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ =
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // true

Count

Count to operacja ko┼äcz─ůca, kt├│ra zwraca liczb─Ö element├│w strumienia jako warto┼Ť─ç typu long.

long startsWithB =
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);    // 3

Reduce

Operacja ko┼äcz─ůca, kt├│ra dokonuje redukcji element├│w strumienia przy u┼╝yciu podanej funkcji. Wynikiem jest kontener Optional zawieraj─ůcy zredukowan─ů warto┼Ť─ç.

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Strumienie równoległe

Jak ju┼╝ wspomnieli┼Ťmy, strumienie mog─ů by─ç sekwencyjne lub r├│wnoleg┼ée. Operacje na strumieniach sekwencyjnych s─ů jednow─ůtkowe, natomiast operacje na strumieniach r├│wnoleg┼éych wykonywane s─ů w kilku w─ůtkach jednocze┼Ťnie.

Poni┼╝szy przyk┼éad pokazuje, jak ┼éatwo mo┼╝na zwi─Ökszy─ç wydajno┼Ť─ç aplikacji dzi─Öki zastosowaniu strumieni r├│wnoleg┼éych.

Najpierw utworzymy du┼╝─ů list─Ö sk┼éadaj─ůc─ů si─Ö z r├│┼╝nych element├│w:

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

Teraz sprawdzimy, ile czasu zajmie posortowanie strumienia tej kolekcji.

Sortowanie sekwencyjne w Javie 8

long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// sortowanie sekwencyjne zajęło 899 ms

Sortowanie równoległe w Javie 8

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// sortowanie równoległe zajęło 472 ms

Jak wida─ç, r├│┼╝nice pomi─Ödzy tymi dwoma fragmentami kodu s─ů minimalne, lecz sortowanie r├│wnoleg┼ée jest o oko┼éo 50% szybsze. By z niego skorzysta─ç, wystarczy tylko zmieni─ç stream() na parallelStream().

Słowniki

Jak zd─ů┼╝yli┼Ťmy ju┼╝ powiedzie─ç, s┼éowniki nie obs┼éuguj─ů strumieni bezpo┼Ťrednio. W samym interfejsie Map nie ma metody stream(), jednak mo┼╝emy utworzy─ç specjalne strumienie zawieraj─ůce klucze, warto┼Ťci b─ůd┼║ pozycje s┼éownika za pomoc─ů metod map.keySet().stream(), map.values().stream() i map.entrySet().stream().

Ponadto s┼éowniki obs┼éuguj─ů wiele nowych przydatnych metod u┼éatwiaj─ůcych wykonywanie najcz─Östszych zada┼ä.

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

map.forEach((id, val) -> System.out.println(val));

Powy┼╝szy kod powinien by─ç zrozumia┼éy: putIfAbsent uwalnia nas od pisania dodatkowych instrukcji sprawdzaj─ůcych czy warto┼Ť─ç wynosi null; forEach przyjmuje konsumenta, by wykona─ç operacje dla ka┼╝dej warto┼Ťci s┼éownika.

Oto przyk┼éad pokazuj─ůcy, jak wykona─ç na s┼éowniku operacje za pomoc─ů funkcji:

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33

Nast─Öpnie mo┼╝emy usun─ů─ç pozycj─Ö przypisan─ů kluczowi, o ile jest on obecnie przyporz─ůdkowany do podanej warto┼Ťci:

map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null

Inna pomocna metoda:

map.getOrDefault(42, "nie znaleziono");  // nie znaleziono

Scalanie pozycji słownika to nic trudnego:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

Funkcja scalaj─ůca albo doda par─Ö klucz/warto┼Ť─ç do s┼éownika, je┼Ťli klucz nie mia┼é przypisanej warto┼Ťci, albo zmieni istniej─ůc─ů ju┼╝ warto┼Ť─ç.

API daty i czasu

Java 8 zawiera zupe┼énie nowy interfejs API daty i czasu w pakiecie java.time. Jest on por├│wnywalny z bibliotek─ů Joda-Time, lecz to nie to samo. Poni┼╝sze fragmenty ilustruj─ů najwa┼╝niejsze elementy nowego API Date.

Zegar

Zegary umo┼╝liwiaj─ů dost─Öp do aktualnej daty i czasu. Uwzgl─Ödniaj─ů stref─Ö czasow─ů, dlatego mo┼╝na nimi zast─ůpi─ç System.currentTimeMillis(), by sprawdzi─ç liczony w milisekundach czas, jaki up┼éyn─ů┼é od pocz─ůtku epoki Uniksa. Taki bie┼╝─ůcy punkt na linii czasu jest tak┼╝e reprezentowany przez klas─Ö Instant. Z jej pomoc─ů mo┼╝na utworzy─ç historyczne obiekty java.util.Date.

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date

Strefy czasowe

Strefy czasowe oznaczone s─ů przez identyfikator ZoneId. S─ů ┼éatwo dost─Öpne za po┼Ťrednictwem statycznych metod wytw├│rczych. Strefy czasowe okre┼Ťlaj─ů warto┼Ť─ç przesuni─Öcia potrzebn─ů do konwersji pomi─Ödzy instancjami klasy Instant a lokalnymi datami i godzinami.

System.out.println(ZoneId.getAvailableZoneIds());
// wy┼Ťwietla wszystkie dost─Öpne identyfikatory stref czasowych

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]

Czas lokalny

Czas lokalny (LocalTime) reprezentuje czas bez strefy czasowej, np. 10 czy 17:30:15. W poni┼╝szym przyk┼éadzie tworzymy dwa czasy lokalne dla stref czasowych zdefiniowanych powy┼╝ej. Nast─Öpnie por├│wnamy je ze sob─ů i obliczymy mierzon─ů w godzinach i minutach r├│┼╝nic─Ö, jaka je dzieli.

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

Klasa LocalTime dostarcza wielu metod wytw├│rczych upraszczaj─ůcych tworzenie nowych instancji, w tym metod─Ö do parsowania ┼éa┼äcuch├│w oznaczaj─ůcych czas.

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37

Data lokalna

Data lokalna (LocalDate) reprezentuje konkretn─ů dat─Ö, np. 2014-03-11. To klasa niemodyfikowalna, dzia┼éaj─ůca analogicznie do LocalTime. Na poni┼╝szym przyk┼éadzie demonstrujemy, jak oblicza─ç nowe daty przez dodawanie b─ůd┼║ odejmowanie dni, miesi─Öcy lub lat. Nale┼╝y pami─Öta─ç, ┼╝e ka┼╝da manipulacja zwraca now─ů instancj─Ö klasy.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // FRIDAY

Przetworzenie daty lokalnej z łańcucha jest równie łatwe co przetworzenie czasu lokalnego:

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

LocalDateTime

LocalDateTime to po┼é─ůczenie daty i czasu ze wspomnianych wy┼╝ej klas w jedn─ů instancj─Ö. Obiekt LocalDateTime jest niemodyfikowalny i dzia┼éa podobnie jak LocalTime i LocalDate. Korzystaj─ůc z odpowiednich metod, mo┼╝na pobra─ç dane przechowywane w polach LocalDateTime:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

Po dodaniu informacji o strefie czasowej, obiekt LocalDateTime można zamienić na typ Instant. Ten z kolei da się łatwo przekonwertować na datę starego typu java.util.Date.

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

Formatowanie po┼é─ůczonej daty i czasu wykonuje si─Ö identycznie co formatowanie daty b─ůd┼║ czasu z osobna. Mo┼╝emy skorzysta─ç z predefiniowanego formatu lub utworzy─ç nowy na podstawie w┼éasnego wzorca.

DateTimeFormatter formatter =
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13

W przeciwie┼ästwie do java.text.NumberFormat nowy formater DateTimeFormatter jest niemodyfikowalny i bezpieczny pod k─ůtem dzia┼éania w─ůtk├│w.

Szczegółowe informacje na temat składni wzorców można znaleźć tutaj.

Adnotacje

W Javie 8 adnotacje s─ů powtarzalne. By zrozumie─ç na czym to polega, przejd┼║my prosto do przyk┼éadu.

Zaczynamy od zdefiniowania adnotacji opakowuj─ůcej, kt├│ra przechowuje tablic─Ö w┼éa┼Ťciwych adnotacji:

@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}

W Javie 8 mo┼╝na korzysta─ç z wielu adnotacji tego samego typu ÔÇô nale┼╝y w tym celu zadeklarowa─ç adnotacj─Ö @Repeatable.

Wariant 1: adnotacje kontenera (stara szkoła)

@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

Wariant 2: adnotacje powtarzalne (nowa szkoła)

@Hint("hint1")
@Hint("hint2")
class Person {}

W przypadku wariantu 2. kompilator Javy dodaje adnotacj─Ö @Hints niejawnie ÔÇ×pod mask─ůÔÇŁ. To wa┼╝ne, by m├│c korzysta─ç z adnotacji za pomoc─ů refleksji.

Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2

Mimo i┼╝ nie zadeklarowali┼Ťmy adnotacji @Hints w klasie Person, to mo┼╝emy j─ů odczyta─ç poprzez metod─Ö getAnnotation(Hints.class) . Wygodniej jest to jednak zrobi─ç za po┼Ťrednictwem metody getAnnotationsByType, kt├│ra daje nam dost─Öp do wszystkich adnotacji @Hint.

Ponadto w Javie 8 z adnotacji mo┼╝na korzysta─ç na dwa nowe sposoby:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

Sko┼äczy┼éem lektur─Ö ÔÇö co dalej?

W tym miejscu ko┼äczy si─Ö m├│j przewodnik po Javie. Je┼Ťli chcieliby┼Ťcie dowiedzie─ç si─Ö czego┼Ť wi─Öcej o klasach i innych sk┼éadnikach API JDK 8, zach─Öcam do zapoznania si─Ö z narz─Ödziem JDK8 API Explorer mojego autorstwa. Pomo┼╝e wam ono odkry─ç nowe klasy i kryj─ůce si─Ö w ostatniej wersji Javy pere┼éki, mi─Ödzy innymi Arrays.parallelSort, StampedLock czy CompletableFuture.

W nawi─ůzaniu do tego przewodnika opublikowa┼éem te┼╝ na blogu kilka artyku┼é├│w, kt├│re mog─ů was zainteresowa─ç:

Dzi─Öki za lektur─Ö!

Autor: Benjamin Winterberg

Źródło: https://github.com/winterbe/java8-tutorial

Tłumaczenie: Joanna Liana

Tre┼Ť─ç tej strony jest dost─Öpna na zasadach licencji MIT