I nie chodzi o moduły a o nową ciekawą metodę Optional, która zwię się or i jest dostępna od Javy 9.
To skoro small talk mamy za sobą to dlaczego ona taka fajna?
Unia opcjonalnych typów w dynamicznych strukturach
Taka sytuacja. Masz jakiś kanał komunikacji i otrzymujesz przez ten kanał opcjonalną wartość jakiegoś typu. I teraz ten typ którego wartość jest opcjonalna może przyjąć jedną z na przykład 4 elementowego zbioru wartości gdzie każdy element może mieć różną liczbę różnych atrybutów. Na rysunku duże kółko to symbolika typu abstrakcyjnego a małe kółka to podtypy - i technicznie wszystko jest tak opcjonalne, iż ani nie wiadomo czy jakaś wartość będzie przekazana a jeżeli będzie to jaka to wartość będzie?
Gdy już mało mówiący opis abstrakcyjny mamy za sobą teraz będzie trochę konkretów w stylu retro.
Retro przykład
Żeby nie było, iż tylko nowe nowe nowe to zerknijmy zatem na przykład pamiętający czasy monitorów monochromatycznych
Poniżej kawałek jakiejś tam definicji jednego z elementów w jakiejś tam specyfikacji. Nieważne. istotny jest ten choice który sprawia, iż tam w tym typie może być w sumie cokolwiek.
Schema Definition <element name="X509Data" type="ds:X509DataType"/> <complexType name="X509DataType"> <sequence maxOccurs="unbounded"> <choice> <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/> <element name="X509SKI" type="base64Binary"/> <element name="X509SubjectName" type="string"/> <element name="X509Certificate" type="base64Binary"/> <element name="X509CRL" type="base64Binary"/> <any namespace="##other" processContents="lax"/> choice> sequence> complexType> <complexType name="X509IssuerSerialType"> <sequence> <element name="X509IssuerName" type="string"/> <element name="X509SerialNumber" type="integer"/> sequence> complexType>
Żródło : https://www.w3.org/TR/xmldsig-core/ <--- jakiś tam losowy standard sprzed 10 lat wybrany losowo na potrzeby artykułu.
No i są takie patenty w stylu JAX-WS, które to przewalą na kod javowy bardzo często mający więcej nulli niż rzeczywistych wartości. Ponieważ artykuł nie jest o xmlach i klasach javy wypełnionych po brzegi annotacjami to zrobimy wygodny skok/uproszczenie i dla potrzeb edukacyjnych "ekstrakcję" zasymilujemy na zwykłej takiej javowej mapie.
Bezpieczny i silnie typowany kod Javy 9
Także oto wspomniana mapka na potrzeby edukacyjne :
Mapm1 = Map.of("X509SKI", "dupa");
No i teraz najważniejsze. Chcemy wydobyć ten element kiedy tak naprawdę nie wiemy czy on tam jest, robimy to tak :
Optional<X509Data> result = Optional .ofNullable(m1.get("X509SKI")).map(X509Data.X509SKI::new)
No i fajnie ale to nie jedyna opcja w tej opcji - przecież możemy mieć "X509CLR" i teraz własnie wchodzi or
Optional<X509Data> result = Optional .ofNullable(m1.get("X509SKI")).<X509Data>map(X509Data.X509SKI::new) .or( () -> Optional.ofNullable(m1.get("X509CLR")).map(X509Data.X509CLR::new) )
- Obserwacja nr1 : Java nie ma czegoś takiego jak "pass by name" w skutek czego trzeba to zasymulować przekazaniem Supplier stąd to "()->" na początku
- Obserwacja nr2 : zauważcie to .
map : jeszcze nie pokazałem jak te typy sa modelowane ale generalnie X509Data to nad-typ i niestety wykrywanie typów w Javie kuleje jeszcze na tyle, iż trzeba mu tutaj pomóc i powiedzieć żeby trzymał się typu bazowego.
No i ostatecznie mamy :
Optional<X509Data> result = Optional .ofNullable(m1.get("X509SKI")).<X509Data>map(X509Data.X509SKI::new) .or( () -> Optional.ofNullable(m1.get("X509CLR")).map(X509Data.X509CLR::new) ).or( () -> Optional.ofNullable(m1.get("X509IssuerSerial")) .map(data -> new X509Data.X509IssuerSerial(data, 69)) );
Closed Algebraic Data Type
Wróćmy teraz do modelu danych. Mamy pewien abstrakcyjny typ z określonymi jego realizacjami X509SKI, X509CosTamInnego itd. Inne języki niż Java wspierają taki typ przy pomocy konstrukcji, która z jednej strony nie jest finalna w nad-klasie a z drugiej pozwala jedynie na zadeklarowanie jasno określonej listy pod typów. (więcej na ten temat : http://pawelwlodarski.blogspot.com/2017/02/closedclosed-principle.html) . W javie okazuję się jest też pewien "sposób" na takie rozwiązanie. Pod spodem zobacyzmy ograniczoną listę klasy wewnętrznych i klasę zewnętrzna z prywatnym konstruktorem -> czyli nic poza ta klasą nie stworzy nowej podklasy bo nie ma dostępu do konstruktora nadklasy - w teorii tak to własnie powinno działać.
//Java sealed type dla ubogich class X509Data { private X509Data() { } final static class X509SKI extends X509Data { final String value; X509SKI(String value) { this.value = value; } } final static class X509CLR extends X509Data { final String value; X509CLR(String value) { this.value = value; } } final static class X509IssuerSerial extends X509Data { final String name; final Integer number; X509IssuerSerial(String name, Integer number) { this.name = name; this.number = number; } } }
Wygodniej w Kotlinie
Ogólnie fajnie, iż java się rozwija i stara się nadganiać ale niektóre rzeczy wciąż wyglądają jak wiadro z gównem. Toteż uwagę mą przykuwa coraz popularniejszy Kotlin, który pod kontem ekspresji jest dla mnie gdzieś pomiędzy Javą a Scalą - i co dobre wydaje się być jeszcze przed granicą gdzie nie udostępnia potężnych ale i niebezpiecznych mechanizmów, którymi dzieci w korporacjach robią sobie krzywdę. A co najważniejsze dla naszego przykładu - ma natywne wsparcie dla rekordów danych oraz typów algebraicznych
sealed class X509Data data class X509SKI(val value:String):X509Data() data class X509CLR(val value:String):X509Data() data class X509IssuerSerial(val name:String,val number:Int):X509Data()
I mając rozwiązanie na największy ból obecnej javy czyli definicje małych klas rozjebane na dwa ekrany możemy przejść do naszego głównego przykładu
val m1=mapOf("X509CLR" to "dupa") val result:Optional<X509Data> = Optional .ofNullable(m1["X509SKI"]).map<X509Data>(::X509SKI) .or{ Optional.ofNullable(m1["X509CLR"]).map(::X509CLR)} .or{ Optional.ofNullable(m1["X509IssuerSerial"]).map { data -> X509IssuerSerial(data,69) } }
Okazuje się, iż Kotlin bardzo dobrze radzi sobie z konwersją pomiędzy swoimi lambdami a Javowymi SAM types. Trzeba tylko przywyknąć do tych klamerek takich i będzie ok.
Edukacyjna rola Scali
Operator Optional.or, który jest nowością w Javie 9 był od dawna dostępny w scali jako Option.orElse. Poniżej jego definicja z charakterystyczną strzałką "=>" przed argumentem. Ten zapis oznacza "call by name" czyli argument jest zwyczajnie ewaluowany leniwie. Ponieważ w Javie nie ma czegoś takiego to zwyczajnie trzeba było się tam spodziewać Supplier. No i tak znając jeden język można z marszu przejść do używania "nowinek" w innym języku.@inline final def orElse[B >: A](alternative: => Option[B]): Option[B] = if (isEmpty) alternative else this
Podsumowanie
Dzisiaj połączyliśmy technologię w miarę nowoczesną - Java 9 - która mimo wszystko trochę jest w plecy za resztą świata - z przykładem technologii tak starej, iż pewnie wasz dziadek używał kiedy w dyliżansie przemierzał dziki zachód. To co chciałbym byście wynieśli to jednak ilustracja zastosowania operatora na poziomie typów Optional. Bo to już nie takie pseudo "ifowanie" w stylu
Zaczytałem se jakiegoś syfa użyje ifPresent czy getOrElse i z Optionala mam kurwa zwykłego IFaTo co pojawia się w przypadku Optional.or to - algebra - tak samo jak algebra boolea dla true|false . Java zmierza w dobrym kierunku (byleby jakiemu geniuszowi nie przyszło do głowy jebnąć na Optional jakiejś annotacji @orrable która w runtime obrzyga całe typesafety, nie , prosze nie, nie , poprostu nie)