O programování 07 - Funkce jako argument aneb od factory k lambdě v Javě
V minulém díle jsem ukázal, jak lze v Javě implementovat čtyři varianty jednoho algoritmu s využitím veškerého aparátu OOP a návrhových vzorů, konkrétně vzorů strategy a factory. Výsledná implementace je sice Javovsky sofistikovaná ale zastírá podstatu, totiž to, že ve skutečnosti chci předat do metody funkci jako parametr. Pokud se podívám na příklad s factory trochu s nadhledem, tak v něm vlastně předáváme instanci třídy, která danou funkcionalitu implementuje a v rámci factory ji specifikuji názvem.
public static void main(String[] args) {
LoopRunner runner = new LoopRunner(LoopFactory.get("foreach"));
runner.run();
}
Dalším krokem je předat do konstruktoru anonymní třídu implementující danou funkcionalitu:
public static void main(String[] args) {
LoopRunner runner = new LoopRunner(new ILoopMethod() {
@Override
public void testedMethod(List<Integer> inputList, List<Integer> outputList) {
for (Integer integer : inputList) {
outputList.add(++integer);
}
}
});
runner.run();
}
Tento zápis lze přepsat s využitím funkcionálních možností následujícím způsobem:
public static void main(String[] args) {
LoopRunner runner = new LoopRunner((List<Integer> inputList, List<Integer> outputList) -> {
for (Integer integer : inputList) {
outputList.add(++integer);
}
});
runner.run();
}
Tím jsem sice dosáhl toho, že předávám funkci (metodu) jako parametr, ale pořád mi to nepřipadá úplně intuitivní a přehledné. Navíc (mi) není na první pohled zřejmé, jak je zařízeno to, že předávaná implementace implementuje interface ILoopMethod. V předchozím příkladě je ILoopMethod explicitně zmíněno v konstruktoru, ale ve funkcionálním případě zcela chybí. Jak to?
Ono to funguje (jenom) díky tomu, interface ILoopMethod má (v tomto případě náhodou) jen jednu metodu a jde tedy o funkční interface zavedený v Java 8, což je opět přiohnutí syntaxe v tom smyslu, že interface s jednou metodou může mít speciální význam.
Každopádně je zajímavé, že jsem se k funkcionálnímu přístupu dostal vlastně domyšlením návrhového vzoru strategy a factory do důsledků, což mimo jiné potvrzuje můj názor, že (některé) návrhové vzory nejsou nic jiného než způsob, jak obejít omezení jazyka.
Mimochodem, použití funkce jako parametru je využití command patternu, viz zde a zde. Lambda v Java 8 je pak pouhým syntaktickým cukrem nad výše uvedeným, doplněný zmíněným funkčním interfacem, což sice dává docela smysl, ale pořád mi to přijde lehce nepřehledné.
Nicméně budu pokračovat s úpravami kódu. Nejdříve přepíšu volání metody run() tak, aby se předávala pouze funkce (method), byť stále budu využívat můj (uživatelsky definovaný) interface:
public static void main(String[] args) {
final ILoopMethod method = (List<Integer> inputList, List<Integer> outputList) -> {
for (Integer integer : inputList) {
outputList.add(++integer);
}
};
final LoopRunner runner = new LoopRunner(method);
runner.run();
}
U vlastního volání v rámci metody run() je použito:
method.testedMethod(inputList, outputList);
Následně využijeme interface Function definované v Java 8:
public static void main(String[] args) {
Function<List<Integer>, List<Integer>> method = (List<Integer> inputList) -> {
List<Integer> outputList = new ArrayList<>(inputList.size());
for (Integer integer : inputList) {
outputList.add(++integer);
}
return outputList;
};
final LoopRunner runner = new LoopRunner(method);
runner.run();
}
U vlastního volání v rámci metody run() je použito:
private void run() {
...
final List<Integer> outputList = method.apply(inputList);
...
}
Tento zápis mi už dává smysl a celkem se mi i líbí, i když pořád zůstávají "nějak" pojmenované metody místo řešení na úrovni syntaxe jazyka a "zneužití" patřičně oanotovaného interface. Chápu, že je to kompromis mezi využitím toho, co už bylo v jazyce a jeho implementaci k dispozici a potřebou zavést novou a potřebnou funkcionalitu. Ovšem řešit to knihovnou místo rysem jazyka je ... už jsem to psal, má to výhody i nevýhody.
- O programování 01 - Úvod
- O programování 02 - Efektivita funkcionálního přístupu v Java 8
- O programování 03 - Přehlednost funkcionálního zápisu v Java 8
- O programování 04 - Java Microbenchmark Harness
- O programování 05 - Další varianty Javovské implementace foreach
- O programování 06 - Návrhové vzory - síla i slabina Javy
- O programování 08 - Jak v Javě předat metodu, která bude mít různé parametry
- O programování 09 - Pokročilé využití streamů a funkcionálního přístupu v Java 8
- O programování 10 - Java a inspirace v Ruby
- O programování 11 - Minimalistické programy a Java
- O programování 12 - Paralelní implementace násobení matic v Java - úvod