Java 8 - nové DateTime API

Java Objektově orientované programování Java 8 - nové DateTime API

V tomto a následujících článcích se seznámíme s novým DateTime API přidaným do Javy ve verzi 1.8.

Proč nové API?

Určitě alespoň základně znáte třídy, které umožňovaly práci s časem do teď (pokud ne, je tu o nich článek). Jejich systém byl ale poměrně špatně navržen. Tyto třídy nesdílejí společnou filosofii a práce s nimi je neohrabaná. Navíc většina z nich není thread-safe.

Java SE 8 tedy přichází s novou ucelenou knihovnou v podobě balíčku java.time, která je navržena pro kompletní nahrazení starých tříd. A i když některé věci bych navrhl jinak, toto nahrazení je velice důstojné.

Hlavní myšlenky

Plynulé rozhraní

Všechny třídy se snaží podporovat plynulé rozhraní. Vše je ulehčeno skutečností, že naprostá většina metod nevrací null, čili my tuto hodnotu nemusíme testovat. Celá knihovna také nemá moc ráda veřejné konstruktory a upřednostňuje tovární metody. Typický způsob vytváření instance času:

LocalTime time = LocalTime.now().minusHours(4).withMinute(23).withSecond(42);

(O třídě LocalTime jsem vám ještě neřekl nic a přesto už určitě víte, co předchozí kód dělá.)

Immutable třídy

Hodnoty většiny tříd jsou finální (konstantní). To nám může často ulehčit práci a celou knihovnu to činí thread-safe.

Rozšiřitelnost

Knihovna je opravdu rozšiřitelná, kde to jen jde. Můžete definovat vlastní způsoby manipulace s časem nebo celé nové kalendářní systémy.

Jednoduchost v chování

Celý návrh je velmi dobře propracovaný až intuitivní. Stejně tak chování metod v neobvyklých situacích. Zde se těžko uvádí příklad, přesvědčit se budete muset sami dále.

Stejně jako např. v Collection frameworku Javy se i zde velmi dbalo na společné pojmenovávání metod. Např. na většině tříd reprezentující nějaký datum nebo čas najdete metodu now(), která (překvapivě) vrátí aktuální datum/čas.

Většina hlavních metod se poté sdružuje do skupin s podobným významem, v nichž se název každé z metod řídí stejným pravidlem (tak by to samozřejmě mělo být vždy, v praxi ale rozhodně není). Např. metoda začínající slovem with vrátí kopii původního objektu s jednou vlastností změněnou. Podle začátků názvů metod můžeme vytvořit jejich následující hlavní skupiny (moje definice jsou trochu krkolomné, ale vše bude ukázáno na příkladech):

  • of – statické metody vytvářející instanci data/času z jednotlivě předaných parametrů
  • from – statické metody převádějící instanci z jednoho typu na jinou (např. DateTime na Time)
  • parse – statické metody parsující string na instanci
  • format – metody na instanci převádějící ji na text
  • get – vrací jednu vlastnost (ne nepodobné klasickým getterům)
  • is – testují stav instance (opět gettery); lze využít i speciálního mechanismu tzv. queries, o kterém bude také řeč
  • with - vrací kopii původní instance s jednou vlastností změněnou
  • plus – vrací kopii původní instance s jednou částí času/data zvětšenou
  • minus - vrací kopii původní instance s jednou částí času/data zmenšenou
  • to – zkonvertuje instanci na jiný typ
  • at – zkombinuje instanci s další instancí (výsledek bude mít některé vlastnosti z instance první, některé z té druhé)

LocalTime a LocalDate

Tyto dvě třídy tvoří pro uživatele knihovny úplný základ. Jedná se o kontejnery pro čas nebo datum (hodiny + minuty + sekundy nebo roky + měsíce + dny). Jak již bylo řečeno, i tyto třídy mají konstantní hodnoty. Na obou také najdeme zmíněnou metodu now().

Jedna ze základních metod je tovární metoda of(int, int, int), které předáme rok, měsíc a den v měsíci (resp. hodiny, minuty a sekundy) a ona nám vrátí instanci s danými vlastnostmi. Existuje také několik přetížení a dalších verzí této metody (např. ofSecondOfDay(long) nebo OfNanoOfDay(long)), viz oficiální dokumentace.

Metody těchto tříd mají opravdu rozmanité užití, čili pokud pochopíte alespoň většinu z nich, pochopíte to důležité z celého API. Pojďme na to jít podobným způsobem. Vždy si o něčem řekneme a poté to ukážeme na konkrétní metodě tříd LocalTime/Local­Date.

LocalDateTime

Jak název napovídá, třída LocalDateTime v sobě kombinuje LocalDate a LocalTime. Obsahuje tedy rok, měsíc, den, hodinu, minutu a sekundu. LocalTime můžeme „rozšířit“ na LocalDateTime metodou atDate(LocalDa­te):

LocalTime time = LocalTime.now();
System.out.println(time);
LocalDate date = LocalDate.now();
System.out.println(date);
LocalDateTime datetime = time.atDate(date);
System.out.println(datetime);

Výstup:

16:42:46.250
2015-06-14
2015-06-14T16:42:46.250

Analogicky LocalDate můžeme rozšířit metodou atTime(LocalTi­me).

Rozhraní TemporalAccessor

TemporalAccessor je cokoli, co nese nějakou informaci o čase a umí ji poskytnout (pokud ji umí i přepsat, mělo by implementovat rozhraní Temporal, viz dále), čili např. LocalTime, LocalDateTime nebo LocalDate. Tyto třídy tedy rozhraní implementují. To nám nabízí skvělou možnost zobecňování a celé API toho využívá. Příkladem nechť je metoda from (TemporalAccessor) na třídě LocalTime a LocalDate, která převede instanci na jiný typ (např. LocalDateTime na LocalDate). Např. následující kód:

LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);
LocalDate date = LocalDate.from(dateTime);
System.out.println(date);

Vypíše toto:

2015-06-14T16:33:47.028
2015-06-14

Vše má ale své meze. Ani nové API není např. tak chytré, aby převedlo LocalTime na LocalDate. Tento kód:

LocalTime time = LocalTime.now();
System.out.println(time);
LocalDate date = LocalDate.from(time);
System.out.println(date);

Tedy vyvolá výjimku:

java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: 16:37:06.189 of type java.time.LocalTime

Rozhraní Temporal

Rozhraní Temporal rozšiřuje již zmíněné rozhraní TemporalAccessor a k jeho metodám přidává vlastní pro upravování času (TemporalAccessor dovoloval pouze čtení). Ve skutečnosti třídy jako LocalTime a LocalDate implementují toto rozhraní.

Jedny z metod tohoto rozhraní začínají slovy minus a plus. Jak název napovídá, slouží k úpravě (zvětšení nebo zmenšení) vlastností instance. Např. třída LocalTime definuje metody jako minusMinutes, minusHours, plusSeconds atd. příjímající počet přičtených/odeč­tených jednotek času. Obecné „znění“ metody tak, jak je definovaná v rozhraní Temporal je:

default Temporal minus(TemporalAmount amount)

a

default Temporal minus(long amountToSubtract, TemporalUnit unit)

Analogicky existují metody plus. Pokud umíte alespoň trochu anglicky, je vám význam obou metod hned jasný. Pojďme se ale přeci jenom podrobněji podívat na rozhraní TemporalAmount a TemporalUnit.

TemporalAmount je určité „množství“ času – např. 5 hodin, 4 minuty nebo 4 roky a 2 měsíce.

TemporalUnit je rozhraní pro určitou jednotku času, např. dny nebo roky. Nejdůležitější implementace je výčtový typ ChronoUnit, který všechny tyto jednotky definuje.

Rozhraní TemporalAdjuster

Toto rozhraní specifikuje nějakou určitou změnu hodnot instance, která implementuje rozhraní Temporal (= která dovoluje hodnoty vůbec měnit). Např. nějaký TemporalAdjuster může měnit den v jakékoli instanci na pátek, nebo třeba přidat 5 minut. Jedná se o funkcionální rozhraní s metodou:

Temporal adjustInto(Temporal temporal);

Jak je vidět, metoda přebírá instanci a instanci zase odevzdává. Našim cílem bude hodnoty v instanci náležitě změnit. Např. adjuster pro zmíněné posunutí času o 5 minut vpřed by vypadal takto:

TemporalAdjuster adjuster = temporal -> LocalTime.from(temporal).plusMinutes(5);

Zabalujeme instanci do LocalTime, přidáváme 5 minut a vracíme. Jak prosté. Funkci adjusteru pak můžeme využít pomocí metody with(TemporalAd­juster) definované v rozhraní Temporal. Pokud je 17:35, pak následující kód:

LocalTime time = LocalTime.now().with(adjuster);
System.out.println(time);

vypíše:

17:40:03.103

Čili metoda with mění podle daného pravidla obsah instance. Existuje ještě jedno přetížení této metody:

Temporal with(TemporalField field, long newValue);

To jednoduše nastaví hodnotu nějaké části času na danou hodnotu. O rozhraní TemporalField ještě nebyla řeč. Může se snadno splést s rozhraním TemporalUnit, ale není to to samé. Zatímco TemporalUnit představuje např. den, rok nebo minutu, TemporalField představuje např. den v měsíci, den v týdnu nebo minutu dne. Je to logické – když přidáváte 1 den, je jedno, jestli je to den v týdnu nebo v měsíci. Pokud ale nastavujete den na třeba hodnotu 4, vědět to musíte. Analogicky k výčtovému typu ChronoUnit existuje ChronoField.

Nyní známe základy práce s novým DateTime API, příště se koukneme na pokročilejší věci.


 

  Aktivity (2)

Článek pro vás napsal Matěj Kripner
Avatar
Autor se převážně věnuje programování a dalším věcem, které považuje za důležité.

Jak se ti líbí článek?
Celkem (12 hlasů) :
4.54.54.54.54.5


 



 

 

Komentáře

Avatar
MrPabloz
Člen
Avatar
MrPabloz:

Jak tak vidím, snažili se skopírovat javovský joda time, ale mají do toho ještě daleko, tudíž si myslím že joda time bude stále používanější :)

Odpovědět  -1 16.6.2015 18:14
Harmonie těla a duše, to je to, oč se snažím! :)
Avatar
roman64
Redaktor
Avatar
roman64:

Calendar, Date ani joda time neznám neb se Javu teprve učím. Pro řešení běžných programátorských zadání mi třída LocalDate připadá jako dostačující.
Na své zdi mám odkazy na řešení Cvičení k lekci 11. (Java OOP)

Odpovědět  -1 25.6.2015 18:22
osvícený člověk se učí celý život, hlupákovi stačí hodina
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.