1 2 3 4 5 6 7 8 9 10 11 12

Programování » 27. 8. 2014 » Lambda stopky

Pomocí lambda výrazů se dají v Java 8 implementovat i docela elegantní stopky. Budeme vycházet z funkčního rozhraní Runnable, které předáme do speciální statické metody. Tato metoda "obalí" volání metody run dotazem na aktuální systémový čas (v milisekundách) a vypočítá rozdíl mezi koncovým a počátečním časem. Tento rozdíl vypíše do konzole. Největší výhodou, kterou nám zde Java 8 přinesla, je bezpochyby možnost "vytvořit" instanci Runnable při volání metody jako lambda výraz nebo použít referenci na nějakou již existující metodu.

Nejprve si tedy vytvoříme knihovní třídu Stopwatch s jedinou metodou:

  1. public final class Stopwatch {
  2.     public static void measure(final Runnable task) {
  3.         final long startTime = System.currentTimeMillis();
  4.         task.run();
  5.         final long endTime = System.currentTimeMillis();
  6.         final long duration = endTime - startTime;
  7.         System.out.println(String.format("Task duration: %d ms", duration));
  8.     }
  9. }

Stopky potom můžeme používat rozličnými způsoby. Nejkratší je asi zápis pomocí reference na metodu. Pokud chceme měřit čas spuštění nějaké metody na dané instanci, použijeme tento zápis:

  1. Stopwatch.measure(new MargheritaPizza()::bake);

Pokud chceme měřit čas trvání statické metody, existuje tento způsob:

  1. Stopwatch.measure(MyUtilityClass::doSomething);

Jakmile má měřená metoda argumenty, už je zápis o něco delší:

  1. Stopwatch.measure(() -> Calculator.factorial(10));

Programování » 26. 8. 2014 » Co přinesly default metody v Java 8

Před verzí Java 8 bylo možné v rozhraních specifikovat pouze hlavičky metod a implementující třídy musely těla dodatečně specifikovat samy. Často to vedlo k typické situaci, kdy se vytvořilo nějaké (často rozsáhlejší) rozhraní a nějaká základní abstraktní třída (abstract base class), která toto rozhraní částečně implementovala. Koncové třídy implementující rozhraní potom mohly dědit od základní třídy a implementovat jen to, v čem se od obecného případu skutečně odlišují.

Pro ilustraci mějme například tato rozhraní:

  1. public interface Meal {
  2.     void make();
  3. }
  1. public interface BakedMeal extends Meal {
  2.     void prepareForBaking();
  3.     void bake();
  4. }
  1. public interface Pizza extends BakedMeal {
  2.     void prepareDough();
  3.     void prepareBase();
  4.     void prepareTopping();
  5. }

A tyto základní třídy:

  1. public abstract class AbstractBakedMeal implements BakedMeal {
  2.     @Override
  3.     public void make() {
  4.         prepareForBaking();
  5.         bake();
  6.     }
  7.  
  8.     @Override
  9.     public void bake() {
  10.         System.out.println("putting into oven...");
  11.     }
  12. }
  1. public abstract class AbstractPizza extends AbstractBakedMeal implements Pizza {
  2.     @Override
  3.     public void prepareForBaking() {
  4.         prepareDough();
  5.         prepareBase();
  6.         prepareTopping();
  7.     }
  8.  
  9.     @Override
  10.     public void prepareDough() {
  11.         System.out.println("preparing pizza dough...");
  12.     }
  13. }

Typický potomek potom může vypadat nějak takto:

  1. public class MargheritaPizza extends AbstractPizza {
  2.     @Override
  3.     public void prepareBase() {
  4.         System.out.println("preparing sugo from tomatoes...");
  5.     }
  6.  
  7.     @Override
  8.     public void prepareTopping() {
  9.         System.out.println("adding olive oil, mozzarella and basil...");
  10.     }
  11. }

Potomek využívá v maximální míře společný kód ze základních tříd a implementuje jen to, co ho činí specifickým - v případě naší pizzy je to tedy její příchuť. Základní suroviny a rámcový pracovní postup zůstává stejný. Také tvorbu nových druhů pizzy máme usnadněnou.

Hlavní nevýhodou této situace je asi to, že jsou koncové konkrétní třídy přinuceny být navždy v jedné hierarchii (např. MargheritaPizza -- AbstractPizza -- AbstractBakedMeal), protože vícenásobná dědičnost v Javě neexistuje. To je určité omezení, které snižuje přepoužitelnost kódu. Stejně tak vytváříme vlastně "dvě" hierarchie - jednu hierarchii rozhraní a jednu hierarchii odpovídajících základních tříd. Není vždy jednoduché tyto skupiny napasovat na sebe a prakticky se tak až zdvojnásobuje počet typů, které je nutné v programu udržovat.

Od Java verze 8 jsou v rozhraních dostupné tzv. default metody, což jsou metody včetně těla, které se dědí do všech implementujících tříd. Tato nová funkcionalita slouží nejen k tomu, aby bylo možné stávající rozhraní obohatit o obecné implementace bez nutnosti měnit implementující třídy, ale zbaví nás ve většině případů zmíněného omezení tvořit hierarchii základních tříd. Místo základních tříd může totiž implementace obsahovat již samo rozhraní v default metodách.

Podívejme se, jak by se náš pizza příklad změnil s použitím default metod:

  1. public interface Meal {
  2.     void make();
  3. }
  1. public interface BakedMeal extends Meal {
  2.     @Override
  3.     default void make() {
  4.         prepareForBaking();
  5.         bake();
  6.     }
  7.  
  8.     void prepareForBaking();
  9.  
  10.     default void bake() {
  11.         System.out.println("putting into oven...");
  12.     }
  13. }
  1. public interface Pizza extends BakedMeal {
  2.     @Override
  3.     public default void prepareForBaking() {
  4.         prepareDough();
  5.         prepareBase();
  6.         prepareTopping();
  7.     }
  8.  
  9.     default void prepareDough() {
  10.         System.out.println("preparing pizza dough...");
  11.     }
  12.  
  13.     void prepareBase();
  14.     void prepareTopping();
  15. }
  1. public class MargheritaPizza implements Pizza {
  2.     @Override
  3.     public void prepareBase() {
  4.         System.out.println("preparing sugo from tomatoes...");
  5.     }
  6.  
  7.     @Override
  8.     public void prepareTopping() {
  9.         System.out.println("adding olive oil, mozzarella and basil...");
  10.     }
  11. }

Vidíte ten hlavní rozdíl? Koncová třída MargheritaPizza už od ničeho nedědí a naprosto chybí jakákoliv základní třída. To je velké zjednodušení.

Související pojmy:

Programování » 22. 8. 2014 » Přepravky

Přepravky jsou jednoduché třídy, jejichž jediným účelem je zapouzdřit související skupinu proměnných a tu pak přesouvat z jednoho místa programu na druhé (předávají se jako parametry metod). Je to snad i nějaký návrhový vzor, tuším že DTO nebo* Crate. Zpravidla se implementují jako skupina privátních proměnných a k nim pomocí IDE vygenerované settery a gettery.

Už delší dobu si pohrávám s myšlenkou, že možná není úplně špatné přepravky implementovat jako množinu veřejných proměnných, tedy bez setterů a getterů. Objektový purista hned namítne, že tím porušuji pravidlo zapouzdření - ale jak mohu mluvit o zapouzdření v objektu, ve kterém jsou stejně všechny settery a gettery veřejné a mohu tedy proměnné měnit dle své libovůle tak jako tak? Co tam tedy zapouzdřuji?

Naopak, veřejnými proměnnými dávám jasně najevo, že jde o přepravku a nemá se do ní nikdy implementovat žádná logika. V neposlední řadě je to také stručnější a přepravky nesnižuje pokrytí unit testy.

Kde by to na druhou stranu mohlo vadit? Teoreticky by se to mohlo nelíbit nějakým mapovacím frameworkům, které přes introspekci hledají settery a gettery a něco pomocí nich nastavují.

  1. public class PointCrate {
  2.   private int x;
  3.   private int y;
  4.  
  5.   public int getX(){ return x; }
  6.   public int getY(){ return y; }
  7.  
  8.   public void setX(int nX) { x = nX; }
  9.   public void setY(int nY) { y = nY; }
  10. }

Posuďte sami, není tohle v mnoha situacích lepší?

  1. public class PointCrate {
  2.   public int x;
  3.   public int y;
  4. }

1 2 3 4 5 6 7 8 9 10 11 12