Ukládání Java 8 LocalDateTime do JSON pomocí Jackson

V případě, že se pokusíte uložit Java 8 LocalDateTime do formátu JSON pomocí knihovny Jackson, dostanete následující výsledek:

"from": {
	"hour": 14,
	"minute": 30,
	"nano": 0,
	"second": 0,
	"monthValue": 8,
	"year": 2016,
	"dayOfMonth": 21,
	"dayOfWeek": "SUNDAY",
	"dayOfYear": 234,
	"month": "AUGUST",
	"chronology": {
		"id": "ISO",
		"calendarType": "iso8601"
	}
}

Ve většině případů to asi nebude přesně to, co byste chtěli. Jackson nabízí jednoduché řešení a to registraci JavaTimeModule. Tento modul umožní správné uložení a načtení Java 8 DateTime typů.

Číst dálUkládání Java 8 LocalDateTime do JSON pomocí Jackson

Čas v různých časových zónách v Javě 8

Pro určení časového pásma v Javě 8 slouží ZoneId. K jeho vytvoření se používá řetězec ve stylu kontinent/město. Seznam časových zón naleznete například na wiki.

ZoneId pragueZI = ZoneId.of("Europe/Prague");
ZoneId newYorkZI = ZoneId.of("America/New_York");
ZoneId vladivostokZI = ZoneId.of("Asia/Vladivostok");
ZoneId hawaiiZI = ZoneId.of("US/Hawaii");

System.out.println(pragueZI);
System.out.println(newYorkZI);
System.out.println(vladivostokZI);
System.out.println(hawaiiZI);

Výsledek

Europe/Prague
America/New_York
Asia/Vladivostok
US/Hawaii

Pro uchování/vytvoření času, který obsahuje i údaj o časové zóně slouží ZonedDateTime

ZonedDateTime pragueCurrent = ZonedDateTime.now(pragueZI);
ZonedDateTime newYorkCurrent = ZonedDateTime.now(newYorkZI);
ZonedDateTime vladivostokCurrent = ZonedDateTime.now(vladivostokZI);
ZonedDateTime hawaiiCurrent = ZonedDateTime.now(hawaiiZI);

System.out.println(pragueCurrent);
System.out.println(newYorkCurrent);
System.out.println(vladivostokCurrent);
System.out.println(hawaiiCurrent);

Výsledek

2016-09-21T16:35:32.602+02:00[Europe/Prague]
2016-09-21T10:35:32.609-04:00[America/New_York]
2016-09-22T00:35:32.609+10:00[Asia/Vladivostok]
2016-09-21T04:35:32.609-10:00[US/Hawaii]

Výjimka java.time.DateTimeException: Invalid ID for ZoneOffset, invalid format

V Java 8 DateTime API existuje třída pro uchovávání data včetně časové zóny ZonedDateTime. Údaj o časové zóně může být reprezentován třídami ZoneId a ZoneOffset, kdy ZoneId uchovává časovou zónu a ZoneOffset posun oproti Greenwich času. Pokud tedy máme ZonedDateTime 2016-09-07T08:22:40.492+02:00[Europe/Prague] pak Europe/Prague představuje ZoneId a +02:00 ZoneOffset. ZoneOffset dědí ze ZoneId. Obě tyto třídy poskytují statickou metodu of(String zoneId), která na typu textového parametru vrací buď instanci ZoneId nebo ZoneOffset.

ZoneId zi1 = ZoneId.of("+3");
Vrátí ZoneOffset a protože ZoneOffset dědí ze ZoneId, je možné do datového typu ZoneId přiřadit ZoneOffset.

ZoneOffset zo1 = ZoneOffset.of("+3");
Vrátí ZoneOffset.

ZoneId zi2 = ZoneId.of("Asia/Vladivostok");
Vrátí ZoneId.

ZoneOffset zo2 = ZoneOffset.of("Asia/Vladivostok");
Vyhodí výjimku java.time.DateTimeException: Invalid ID for ZoneOffset, invalid format – do ZoneOffset se snažíme přiřadit ZoneId.

Převod streamu primitivních datových typů na stream objektů

V předchozím příspěvku jsem se věnoval streamům, které jako elementy obsahují primitivní datové typy. Jednalo se o následující streamy: IntSteam, DoubleStream, LongStream. Pomocí metody boxed() je možné stream primitivních datových typů převést na stream objektů odpovídajícího datového typu.

// Převod na stream objektů.
Stream<Integer> si = IntStream.range(1, 10).boxed();
Stream<Long> sl = LongStream.of(1_415, 402_112, 999).boxed();
Stream<Double> ds = DoubleStream.generate(() -> Math.random()).limit(5).boxed();

// Zobrazení elementů streamu.
si.forEach(System.out::println);
sl.forEach(System.out::println);
ds.forEach(System.out::println);

Streamy primitivních datových typů v Javě

Java kromě Stream<Object> nabízí i streamy primitivních datových typu. Přesněji řečeno má takové streamy tři:

  • IntStream
  • DoubleStream
  • LongStream

Vytvoření streamu primitivních datových typů
K vytvoření streamu primitivních datových typů je možné, tak jako pro stream objektů, použít metody generate() a iterate().

// IntStream obsahující deset sedmiček.
IntStream isg = IntStream.generate(() -> 7).limit(10);
// IntStream s elementy 0, 1, 2, 3, 4.
IntStream isi = IntStream.iterate(0, i -> i + 1).limit(5);

// Nekonečný DoubleStream, který obsahuje náhodná čísla.
DoubleStream dsg = DoubleStream.generate(() -> Math.random());
// DoubleStream s elementy 1.1, 2.2, 4.4, 8.8, 17.6.
DoubleStream dsi = DoubleStream.iterate(1.1, i -> i + i).limit(5);

// Nekonečný LongStream, který obsahuje čísla 9876543210.
LongStream lsg = LongStream.generate(() -> 9876543210L);
// LongStream obsahující čísla (řády) od 1 miliónu po 1 bilión.
LongStream lsi = LongStream.iterate(1_000_000, num -> num * 10).limit(9);

Dalším způsobem je použít statickou metodu of() a do ní uvést výčet hodnot.

IntStream intStream = IntStream.of(1, 3, 5, 7);
DoubleStream doubleStream = DoubleStream.of(0.52, 15.24, 7.879);
LongStream longStream = LongStream.of(14_854, 4_854);

Stream je možné vytvořit taktéž z pole pomocí statické metody java.util.Arrays.stream().

int[] intArray = { 1, 2, 3, 4};
double[] doubleArray = {12.548, 0.99, 103.1};
long[] longArray = {12, 778_755_441_784L, 1_221};

// IntSteam s elementy 1, 2, 3, 4.
IntStream isFromArray = Arrays.stream(intArray);
// DoubleStream s elementy 0.99 a 103.1.
DoubleStream dsFromArray = Arrays.stream(doubleArray, 1, 3);
// LongStream s elementem 1221.
LongStream lsFromArray = Arrays.stream(longArray, 2, 3);

IntStream a LongStream mají navíc k dispozici statické metody range() a rangeClosed().

// IntSteam s elementy 0, 1, 2, 3, 4.
IntStream isFromRange = IntStream.range(0, 5);
// LongStream s elementy 100, 101, 102, 103.
LongStream lsFromRange = LongStream.rangeClosed(100, 103);

Zdroj: HORSTMANN, Cay S. Java SE 8 for the really impatient. Upper Saddle River, NJ: Addison-Wesley, 2014, xv, 215 pages. ISBN 0321927761.

Java Optional efektivně

Klíčem k efektivnímu využití Optional je používání metod, které dokáží s Optional pracovat. Tyto metody buď zpracují hodnotu zabalenou v Optional, nebo, v případě, že není přítomna, vytvoří náhradní hodnotu (objekt).

Metoda Optional.ifPresent() jako parametr bere funkci java.util.function.Function a v případě, že Optional obsahuje hodnotu (referenci na objekt), je tato hodnota (reference) předána funkci. Pokud hodnotu neobsahuje, nic se nestane.

Stream<Integer> is1 = Stream.of(5, 7, 1, 8, 2);
Optional<Integer> max = is1.max(Integer::compareTo);
Stream<Integer> is2 = Stream.empty();
Optional<Integer> min = is2.min(Integer::compareTo);

Výpis výsledků do konzole (vypíše 8):

max.ifPresent((i) -> System.out.println(i));
min.ifPresent((i) -> System.out.println(i));

Přidání výsledků do listu:

List<Integer> results = new ArrayList<>();
max.ifPresent((i) -> results.add(i));
min.ifPresent((i) -> results.add(i));

Metoda Optional.ifPresent() zpracovává hodnotu v případě, že je přítomna. Dalším způsobem je použít metody, které vytvoří hodnotu (nebo něco provedou) v případě, že hodnota není přítomna. Pro tento případ se hodí metody optionalVariable.orElse(), optionalVariable.orElseGet(), optionalVariable.orElseThrow().

Stream<String> ss1 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> firstWithL = ss1.filter((word) -> word.startsWith("l")).findFirst();
Stream<String> ss2 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> anyWithQ = ss2.filter((word) -> word.startsWith("q")).findAny();

String withL = firstWithL.orElse("");
String withQ1 = anyWithQ.orElse("");

System.out.println(withL);
System.out.println(withQ1);

try {
	String withQ2 = anyWithQ.orElseThrow(Exception::new);
} catch (Exception e) {
	e.printStackTrace();
}

Výpis:

lokomotiva

java.lang.Exception
	at java.util.Optional.orElseThrow(...)

Metoda Optional.orElse() vrátí v případě, že Optional obsahuje null, náhradní hodnotu zadanou jako parametr. Pokud Optional obsahuje referenci, vrátí objekt. Metoda Optional.orElseThrow() vyhodí danou výjimku (pokud Optional obsahuje null).


Zdroj: HORSTMANN, Cay S. Java SE 8 for the really impatient. Upper Saddle River, NJ: Addison-Wesley, 2014, xv, 215 pages. ISBN 0321927761.

Proč používat Java Optional?

Není příliš velký rozdíl mezi:

Optional<T> op = ...;
op.get().someMethod();

vs

T val = ...;
val.someMethod();

Pokud se chceme vyhnout výjimce:

if (op.isPresent()) {
    T val = op.get();
    val.someMethod();
}

vs

if (val != null) {
    val.someMethod();
}

Pokud programátor Optional nepoužívá, ušetří dokonce pár znaků při psaní (nemusí volat get()). Proč tedy používat Optional?

Předchozí příspěvek na téma Optional se týkal třídy Optional z knihovny Guava. Tento příspěvek se věnuje třídě java.util.Optional, která je dostupná od Javy 8. Důvody pro jejich použití jsou ale obdobné. Guava Optional se používá v případě, že máte starší verzi Javy. V případě, že používáte Javu 8, knihovnu Guava nepotřebujete. 

Důvodem pro používání Optional je to, že null může mít více významů. Null může být prázdná hodnota, může představovat chybu, může to být též neinicializovaná proměnná. Pokud ale máme Optional<T> (Optional vlastně zabaluje hodnotu T), tak buď máme referenci na objekt typu T (Optional.isPresent() == true) a nebo referenci nemáme (Optional.isPresent() == false). V případě, že referenci nemáme, odpovídalo by to případu, kdy null vyjadřuje prázdnou hodnotu.

Další výhodou Optional je to, že bychom si při použití Optional měli uvědomit, že se jedná o hodnotu, která nepovinná (proto název optional) a podle toho se k ní chovat. U běžné proměnné nás nemusí napadnout, že je třeba provést kontrolu na to, zda třeba není null. To je důvod, proč některé metody vrací Optional. Dávají tím najevo, že nemusí vrátit žádnou hodnotu. Je to bezpečnější způsob práce než s null. Optional například vrací některé metody pracující s Stream (java.util.stream.Stream).

Stream<Integer> is1 = Stream.of(5, 7, 1, 8, 2);
Optional<Integer> max = is1.max(Integer::compareTo);

Stream<Integer> is2 = Stream.empty();
Optional<Integer> min = is2.min(Integer::compareTo);

Stream<String> ss1 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> firstWithL = ss1.filter((word) -> word.startsWith("l")).findFirst();

Stream<String> ss2 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> anyWithQ = ss2.filter((word) -> word.startsWith("q")).findAny();

Stream<String> ss3 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> shortest = ss3.min((word1, word2) -> word1.length() - word2.length());

Metody min(), max(), findAny(), findFirst() vrací Optional, protože Stream, který dostanou může být prázdný a tím pádem žádnou hodnotu nemusí najít (viz is2), nebo žádná taková hodnota nevyhovuje podmínkám (viz ss2.filter() na slova začínající na q -> žádné takové tam není).

Nekonečný Stream v Javě

Java 8 představila Stream, což je posloupnost hodnot, nad kterou je možné provádět operace. Je možné vytvořit i nekonečný Stream.

Stream<String> stringStream = Stream.generate(() -> UUID.randomUUID().toString());
Stream<String> constantStream = Stream.generate(() -> "word");
Stream<Double> doubleStream = Stream.generate(Math::random);
Stream<Integer> integerStream = Stream.iterate(0, (i) -> i + 1);
Stream<BigDecimal> bigDecimalStream = Stream.iterate(BigDecimal.ZERO, (bd) -> bd.add(BigDecimal.ONE));

Metoda Stream.generate() požaduje parametr typu Supplier<T>, což je funkční rozhraní obsahující metodu T get(). Metoda Stream.iterate() požaduje parametr typu T seed (počáteční element) a UnaryOperator<T>, což je funkční rozhraní dědící z funkčního rozhraní Function (místo typů <R, T> používá <T, T>, metoda apply(T) tedy nevrací typ R ale T).

Pokud nám stačí jen několik prvků streamu, můžeme použít operaci limit(n), která vezme n prvních prvků streamu.

System.out.println(Arrays.toString(stringStream.limit(5).toArray()));
System.out.println(Arrays.toString(constantStream.limit(5).toArray()));
System.out.println(Arrays.toString(doubleStream.limit(5).toArray()));
System.out.println(Arrays.toString(integerStream.limit(5).toArray()));
System.out.println(Arrays.toString(bigDecimalStream.limit(5).toArray()));

Výstup:

[cb2f9562-e468-4409-bd61-cc7f153adbc9, 45582575-fda8-48a4-9ecd-bd8e9ca87f42, 6338c4d5-7b67-4fff-b1ec-6a86e2bacec8, 9ba3c6b0-700a-4e78-b633-e6a8da9fb946, 04951054-8c43-43a6-a177-aeb2da7c7320]
[word, word, word, word, word]
[0.9250003903085224, 0.912794982359122, 0.8636553602757563, 0.5809828517176208, 0.002178508953346925]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]

Pozor na datový typ (na přetečení rozsahu), který je použit v nekonečném proudu.

Stream<Integer> is = Stream.iterate(1, (i) -> i * 10).limit(15);
Stream<BigDecimal> bds = Stream.iterate(BigDecimal.ONE, (bd) -> bd.multiply(new BigDecimal("10"))).limit(15);

System.out.println(Arrays.toString(is.toArray()));
System.out.println(Arrays.toString(bds.toArray()));

Výstup:

[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 1410065408, 1215752192, -727379968, 1316134912, 276447232]
[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000]

Rozdíl mezi lambda a anonymní třídou

Lambda výraz je jiný způsob zápisu anonymních tříd v Javě.

Lambda

Arrays.sort(pole, (String first, String second) -> {
	return Integer.compare(first.length(), second.length());
});

Anonymní třída

Arrays.sort(pole, new Comparator() {
	@Override
	public int compare(String first, String second) {
		return Integer.compare(first.length(), second.length());
	}
});

Lambda výraz je možné použít proto, že Comparator je funkční rozhraní. To znamená, že toto rozhraní má jedinou abstraktní metodu. Pro tato rozhraní se používá anotace @FunctionalInterface. Tato anotace není povinná a pouze kontroluje, zda dané rozhraní splňuje podmínky funkčního rozhraní.

Problém s lambda v Eclipse

Pokud používáte pro vývoj v Javě Eclipse IDE a chcete vyzkoušet psaní lambda výrazů, což je novinka v Javě 8, může se stát, že Eclipse vám validní lambda kód označí jako chybu a to i přesto, že Java 8 máte nainstalovanou a přidanou do build path. Je totiž ještě nutné nastavit compiler compliance level: Window -> Preferences -> Java -> Compiler -> vybrat „Compiler compliance level“ 1.8 -> potvrdit.

Eclipe_ide_compliance_level

Pokud ani toto nepomohlo zkontrolujte nastavení projektu: pravým na projekt -> Properties -> Java Compiler -> vybrat „Compiler compliance level“ 1.8 -> potvrdit.