Java: Vysoce nakažlivé streamy

Napsat blog o streamech v Javě mě napadlo před nedávnem. Čas tlačil a streamy se ukázaly být hodně užitečným pomocníkem.

Pojďme se podívat na dvě vzorové úlohy a porovnejme jejich implementaci postaru a s použitím streamů. Úlohy vycházejí z reálné praxe, nicméně zdrojáky jsem (doufám úspěšně) anonymizoval. Efektivitu a časovou náročnost při spuštění ponechme stranou, měření jsem neprováděl. Porovnejme jednotlivé implementace z hlediska čitelnosti zdrojáku a také z hlediska filosofie programování (nebo spíše programátorské praxe, filozofování přenecháme lidem zabývajícím se měkkými obory).

Výběr z enumu podle textu

Mějme enum, kde prvky kromě svého názvu obsahují i uživatelsky čitelný text. Implementujme metodu, která vyhledá prvek enumu s daným textem, a pokud ho nenajde, vrátí null.

Nejprve postaru:

public MyEnum getByText(String textToFind) {
	for(MyEnum e : MyEnum.values()) {
		if(e.getText().equals(textToFind)) {
			return e;
		}
	}
	return null;
}

A pomocí streamu (plus nějaká ta lambda a šikovně použitý Optional):

public MyEnum getByText(String textToFind) {
	return Arrays.stream(MyEnum.values())
			.filter(e -> e.getText().equals(textToFind))
			.findFirst()
			.orElse(null);
}

Jaký je rozdíl mezi implementacemi? Zkusme si oba zdrojáky přečíst.

Implementaci postaru můžeme číst takto:

  1. Procházej všechny položky enumu.
  2. Když najdeš prvek obsahující hledaný text, vrať ho.
  3. Pokud takový prvek nenajdeš, vrať null.

Pokud jste poznali klasické sekvenční vyhledávání, poznali jste správně, a určitě se shodneme na tom, že i průměrně zdatný programátor by ho měl umět vysypat z rukávu. Zdroják tedy odpovídá na otázku: jak vyhledat daný prvek?

Implementaci streamem můžeme číst takto:

  1. Vyber všechny prvky obsahující hledaný text.
  2. Vezmi první z nich
  3. A pokud takový prvek není, vrať null.

Tady zdroják odpovídá na otázku: co chceš dělat? Říká tedy jediné: prostě hledej. A budeme věřit autorům knihoven JRE, že vyhledávání implementovali efektivně.

Seznam toolbarů ze seznamu funkcí

Pojďme se podívat na složitější úlohu. Mějme GUI, kde nabízíme uživateli nejrůznější funkce. Tyto funkce mají svá tlačítka pro obsluhu. Toolbary, které zobrazujeme v GUI, mohou obsahovat tlačítka pro jednu nebo více funkcí. Seznam funkcí konfigurujeme per uživatel, toolbary jsou předem definovány, mají předem dané pořadí a vytváříme je dle potřeby. Máme-li tedy seznam funkcí, které toolbary musíme vytvořit?

Tentokrát začneme implementací streamem:

public List<Toolbar> getToolbars(List<Function> functions) {
	return functions.stream()
			.map(f -> f.getToolbar())
			.distinct()
			.sorted()
			.collect(Collectors.toList());
}

A jak by se to mohlo implementovat postaru:

public List<Toolbar> getToolbars(List<Function> functions) {
	Set<Toolbar> barSet = new TreeSet<>();
	for(Function f : functions) {
		barSet.add(f.getToolbar());
	}
	return new ArrayList<>(barSet);
}

Kupodivu implementace postaru není zase o tolik složitější než implementace streamem, pokud využijeme TreeSet pro implementaci metod distinct() a sorted(). Opět ale řešíme otázku jak a dokážu si představit, že čtenáři zdrojáku zpočátku vyvstane v hlavě i otázka proč. Proč autor použil ten TreeSet?

Závěr

Když porovnáme implementace obou úloh postaru a pomocí streamů, dojdeme k závěru, že hlavní rozdíl spočívá především v přístupu k programování a ve způsobu přemýšlení. Absence otázky "jak" přemýšlení výrazně zjednodušila. Odpadla potřeba řešit implementační detaily a mohl jsem se soustředit na aplikační logiku jako takovou. A troufám si tvrdit, že i výsledný zdroják je čitelnější a přehlednější. Streamy proto považuji jednoznačně za přínosné.

Tagy: Java, Programování

Tento web bude tebe a tvůj počítač krmit piškotkami, jelikož a protože je to slušný web a jako takový ví, že je potřeba návštěvu řádně pohostit, aby se u nás cítila dobře. Užíváním tohoto webu potvrzuješ, že netrpíš mentální anorexií, nedržíš žádnou obskurní dietu a že můžeš piškotki do sebe cpát kdykoli a v jakémkoli množství. Více informací...