FK~

Moje odkazy

Ostatní odkazy

Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
There Is No Cloud …just other people's computers.
Sane software manifesto / Manifest příčetného softwaru

Java 8: coalesce – hledáme neNULLové hodnoty

vydáno: 11. 8. 2014 20:54, aktualizováno: 12. 8. 2014 10:22

Jednou z věcí, která mi v Javě vždycky trochu chyběla, je funkce coalesce(). Možná ji znáte z PostgreSQL. Funkce přijímá na vstupu více hodnot a vrací první z nich, která není NULL. A hodnotu NULL vrací pouze v případě, že jsou všechny vstupní hodnoty NULL. Hodí se to třeba při práci s mezipamětí nebo pro nastavování výchozích hodnot v případě, že chybí specifické.

Logo OpenJDK (průhledné 1)

Naivní implementace v Javě vypadá takhle:

public static <T> T coalesce(T... hodnoty) {
    for (T hodnota : hodnoty) {
        if (hodnota != null) {
            return hodnota;
        }
    }
    return null;
}

Proč naivní? Protože kruciální vlastností funkce coalesce() je to, že vyhodnocuje jen nezbytně nutné výrazy. Pokud je tedy na prvním místě výraz s nenulovou hodnotou, výrazy na dalších místech se už nevyhodnocují (tzn. pokud tam budou třeba metody, nebudou se volat). Zatímco v Javě se standardně vyhodnotí výrazy všech parametrů a pak se teprve provádí kód metody.

Příklad: na prvním místě budete mít lokální proměnnou, na druhém metodu, která něco spočítá (což stojí víc procesorového času než přístup k proměnné), na třetím hledání dat na místním disku (což je ještě dražší) a na čtvrtém stahování dat ze sítě (což je úplně nejdražší).

Při použití té výše uvedené implementace by se data pokaždé pracně získala ze všech zdrojů, přestože bychom je měli v té lokální proměnné – což by bylo dost na nic. Takže se to nakonec řeší nějakou posloupnosí IFů nebo třeba pomocí nějaké třídy a postupného volání metod na jejích instancích.

Java 8 přináší ale nové možnosti, které nám umožňují tuhle úlohu řešit elegantněji a mít v Javě skutečnou coalesce() funkci.

import java.util.function.Supplier;

public class Coalesce {

    public static <T> T coalesce(Supplier<T>... dodavatel) {
        for (Supplier<T> d : dodavatel) {
            if (d != null) {
                T hodnota = d.get(); // až tady se provede skutečné vyhodnocení
                if (hodnota != null) {
                    return hodnota;
                }
            }
        }
        return null;
    }
}

(pokud vám rozhraní Supplier nic neříká, přečtěte si předchozí článek: Java 8: novinky jazyka)

Pro jednodušší práci s obyčejnými proměnnými si napíšeme metodu, která nám z její hodnoty udělá funkci (dodavatele):

public static <T> Supplier<T> h(T value) {
    return () -> value;
}

Příklad použití:

import static cz.frantovo.priklady.java8.coalesce.Coalesce.coalesce;
import static cz.frantovo.priklady.java8.coalesce.Coalesce.h;

public class Demo {

    public static void main(String[] args) {

        Long hodnota = null;

        Long x;

        x = coalesce(h(hodnota), Demo::getČas, System::currentTimeMillis);

        System.out.println(x);

    }

    private static Long getČas() {
        // tady můžeme něco vypočítat
        // něco náročnějšího, než jen přečíst proměnnou
        return null;
    }

}

Pokud máme hodnotu v proměnné, nastavíme x na tuto hodnotu a zbytek neřešíme. Pokud ne, zkusíme hodnotu vypočítat v metodě getČas(). A pokud by ani to nešlo (null), nastavíme hodnotu na aktuální systémový čas.

Na rozdíl od původní implementace se vyhodnocují jen nezbytně nutné výrazy a přitom je použití stejně jednoduché (jen musíme hodnoty z proměnných obalit něčím jako h() nebo zapsat jako lambda výraz).

Na druhou stranu, oproti klasickému přístupu s posloupností IFů zde dochází k vytváření nových instancí (funkcí) a k procházení pole v cyklu.

Můžeme volat i metody objektů – akorát místo např. date.getTime() předáme odkaz na tuto metodu, aby se zavolala, až když je to potřeba:

Date datum = new Date();

x = coalesce(datum::getTime, h(123L));

Nebo použijeme lambda výraz a zjistíme hodnotu v něm, aniž bychom funkcionalitu museli separovat do nějaké metody:

Date datum = new Date();

x = coalesce(datum::getTime, () -> {
    // nemusíme se odkazovat na existující metodu
    // můžeme zjištění hodnoty provést v lambda výrazu
    return 123L;
});

Dalším vylepšením může být odchytávání výjimek – uvnitř coalesce() budeme během vyhodnocování jednotlivých výrazů odchytávat výjimky a pokud k nějaké dojde, budeme se chovat, jako by hodnota byla null, a přistoupíme v tichosti k dalšímu výrazu. Na konci můžeme vrátit buď hodnotu, nebo objekt, který bude obalovat jak hodnotu, tak průběžně nachytané výjimky.

Také můžeme upravit coalesce() tak, aby její první parametr byl přímo daný generický typ – aby nebylo potřeba hodnotu z proměnné obalovat instancí Supplier<T>.

Odkazy a zdroje:

Témata: [softwarové inženýrství] [Java]

Komentáře čtenářů

Tento článek zatím nikdo nekomentoval

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a špatným trollům

Náhled komentáře