Co to jsou Java Closures?

Opět se ve světě jazyka Java objevil fenomén zvaný Java Closures (také zvaný BGGA), který je plánován do Javy 7. Už při prvním uvedení se proti němu zvedla vlna nevole a zdálo se, že tato konstrukce přeci jen v Javě 7 vznikne. Kolem tohoto tématu bylo už napsána spousta textu. Navíc jsme se o tématu Java Closures docela vášnivě pobavili v našem týmu. Proto jsem se rozhodl, že k tomuto tématu upustím nějaké moudro i já…

Když jsem narazil na článek, který naznačuje, že Java Closures v Javě budou, napadlo mě, kde Java skončí. Kam se v Javě vytratila ona striktnost a objektovost? Ale konec stěžování si. Pojďmě si říct, co to vlastně ony closures jsou.

Definice říká, že se jedná o anonymní funkci, která není izolována od okolního světa tak, jak to známe běžně v Javě. Navíc má takováto funkce přístupné všechny proměnné, které jsou přístupné ve scope, ve kterém byla tato „funkce“ volána.

Tolik definice. Vlastní closures vznikly v jazyce Lisp a postupně se rozšířily do dalších jazyků – zejména funkcionálních. Kolega mi doporučil, abych se podíval na jazyk Ruby, který je prý dokonalý – uvidíme. V rámci studia Ruby se pokusím closures pochopit nejprve v jazyce, kde to již funguje – tedy právě v Ruby. A pak se podíváme na to, jak by se to dalo používat v Javě.

Začněme příkladem, který bude simulovat funkci unixové aplikace grep. V Ruby se to dá implementovat celkem snadno:

IO.foreach("test.txt") do |line| 
	if (line =~ /total: (d+)/)
		puts $1;
	end
end

Tento kód projde soubor test.txt a vypíše řádky, které ty, které odpovídají patternu. První příjemné překvapení v Ruby je to, jak některé věci jdou snadno. Otevření souboru a jeho proiterování po řádcích zajistí příkaz IO.foreach.

Pojďme dále. Zapouzdříme si toto volání do samostatné operace, která bude očekávat na vstupu název souboru a pattern, který budeme vyhledávat.

class File
	def File.grep(filename, pattern)
		IO.foreach(fileName) do |line|
			if matchData = pattern.match(line)
				yield matchData;
			end
		end
	end
end
	
File.grep("test.txt", /total: (d+)/) { |matchData| puts matchData[1]; }

Teď už to vypadá zajímavěji. Voláme funkci File.grep s tím, že definujeme funkci, která se má provést uvnitž volané funkce. Jak toto zajistit? Všimněte si příkazu yield. Ten zajistí předání proměnné matchData do closure a její vyvolání. Doufám, že jsem to popsal dostatečně srozumitelně.

Zastánci těchto konstrukcí říkají, že tyto konstrukce velmi zjednodušují vývoj. Velmi častým příkladem je konstrukce foreach. Argumentem je to, že v Javě se konstrukce foreach objevila teprve v Javě 5. Kdyby měla closures, nebylo by nutné foreach vůbec řešit. Když to srovnáme s Ruby, zjistíme, že tento jazyk tyto konstrukce vůbec neřeší. Nemusí, protože tyto věci jsou řešeny právě prostřednictvím closures.

Dalším pádným argumentem pro closures je použití patternu command v Javě, kdy dnes musíme v parametru metody předávat novou instanci objektu.

Tolik tedy výhody. Na první pohled se zdá, že closures jsou skvělou věcí. Jenže když se na to podíváme z jiného úhlu pohledu, nebudou se tyto konstrukce zdát tak dokonalé.

První věcí je, že se mění syntaxe jazyka. Podle mého názoru musí být pro změnu syntaxe jazyka opravdu pádný důvod s tím, že současné řešení je naprosto nedostatečné (například annotace). Změna syntaxe totiž přináší spoustu nepříjemností. Kromě toho, že ztrácíme zpětnou kompatibilitu, komplikujeme život programátorům tím, že se snižuje čitelnost tohoto kódu.

Další věcí je že tyto konstrukce patří dle mého názoru spíše do světa funkcionálních jazyků, boří striktnost objektového programování. Navíc se podle všeho Java bude chovat tak, že onu konstrukci closure převede do „staré konstrukce“. Díky tomu se stále častěji budeme setkávat s tím, že nám bude docházet PerGemSpace, protože pokud si programátoři closures oblíbí a začnou je masivně používat, budou vznikat mraky objektů.

Jak se v Javě closures budou používat? Pojďme se tedy podívat na jednoduchý příklad, který na příkladu CollectionUtils ukazuje použití closures v Javě.

public Collection getAdminList(Collection users) {
    return select(users, {User user =>
        user.isAdmin()
    });
}

/**
 * Vraci seznam objektu T z kolekce, kde closure vraci true.
 *
 * @param source kolekce objektu T nesmi byt null
 * @param predicate vraci true pro zadany objekt T, ktery ma byt ve vysledne kolekci
 * @return vraci seznam objektu T z kolekce, kde closure vraci true
 */
public static  Collection select(Collection source,
        {T=>Boolean} predicate) {
    Collection result = new ArrayList();
    for (T o : source) {
        if (predicate.invoke(o)) {
            result.add(o);
        }
    }
    return result;
}

A ještě jeden příklad použití. Bývá jím použití komparátoru jako closure. Díky tomu získáme možnost řadit si objekty v kolekci podle toho, jak potřebujeme.

public static void sortByTitle(Collection books) {
    sort(books, {Book book =>
        book.getTitle()
    });
}

/**
 * Sorts the given list of T objects, using the block to compare
 * individual elements of the list.
 *
 * @param l
 *            list to be sorted
 * @param block
 *            returns the value of a property of the given T object
 *            that should be used for sorting
 */
public static  void sort(List l,
        {T=>Comparable} block) {
    sort(T o1, T o2 : l) {
        block.invoke(o1).compareTo(block.invoke(o2));
    }
}

To, zda jsou closures dobrá nebo špatná věc, nechám na uvážení každého. Osobně s tímto konceptem nejsem smířen a nelíbí se mi. Na druhou stranu nepopírám, že rozumné použití closures může vést ke zjednodušení kódu aplikace. Otázkou zůstává, zda tyto změny syntaxe nepovedou k tomu, aby se z Javy stal jazyk typu PHP…

4 odpovědi na “Co to jsou Java Closures?”

  1. Diky za pochvalu – ten clanek stejne vznikl kvuli Tobe 🙂 Jsem zvedavej, jak ta implementace bude nakonec v Jave vyresena, protoze ted jsem se docetl, ze to ma byt jeste jinak …

  2. Nemyslim si, ze bych neco ukradl. Spis jsem pri koumani nad tim, jak ty closures vlastne funguji, prochazel spoustu zdroju. No a jednim z nich byl i tenhle web. Je otazkou, jestli pouziti ukazky kusu kodu z jinyho webu je kradez – ja si to nemyslim. Na druhou stranu jsem mohl uvest zdroj – priste se polepsim.

Napsat komentář