FP = Funktionale Programmierung
- Erfordert Grundlagen - "Motivation" später - Jetzt nur Teaser - Wir erhalten das MysteriumGrundbaustein: Kapselung dieses was
Das Command Pattern
- Wie eben im Vortrag von Thomas gehört - Command Pettern kapselt Verhalten hinter Interface - Z.B. zur Übergabe von Aufgaben an einen Manager - Asynchrone Queue, Undo/Redo-Manager@FunctionalInterface public interface Command { void execute(Object param); }Annotation erstmal ignorieren
public class MyCommand implements Command { void execute(Object param) { // Do something meaningful. } } executor.doSomeAction(new MyCommand());
executor.doSomeAction(new Command() { void execute(Object param) { // Do something meaningful. } });
executor.doSomeAction(param -> { // Do something meaningful. });Nebenbei auch Lambdas erklärt, und das mit Absicht.
List<String> teamSchadow = Arrays.asList("Dominik", "Sandro", "Jochen", ...);
teamSchadow.forEach((String name) -> { System.out.println(name) });
teamSchadow.forEach(name -> System.out.println(name));
teamSchadow.forEach(System.out::println);
Consumer<String> myPrintLn = name -> System.out.println(name); teamSchadow.forEach(myPrintLn);- Automatische Typinferenz - Keine geschweiften Klammern bei Einzeilern - Methodenreferenz (yey, function pointers!), wenn Parameter 1:1 übernommen wird
... und jetzt nochmal für Pragmatiker, bitte!
Beispiel: Summiere Preise über 20 EUR, ermäßigt um 10 %
Collection<BigDecimal> prices = MyApi.getPrices(); BigDecimal price; BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; int i = 0; while (i < prices.size()) { price = prices.get(i); if (price.compareTo(BigDecimal.valueOf(20)) > 0) { totalOfDiscountedPrices = totalOfDiscountedPrices .add(price.multiply(BigDecimal.valueOf(0.9))); } i++ } System.out.println("Result: " + totalOfDiscountedPrices);
Collection<BigDecimal> prices = MyApi.getPrices(); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for (BigDecimal price : prices) { if (price.compareTo(BigDecimal.valueOf(20)) > 0) { totalOfDiscountedPrices = totalOfDiscountedPrices .add(price.multiply(BigDecimal.valueOf(0.9))); } } System.out.println("Result: " + totalOfDiscountedPrices);Erlaubt kompaktere Syntax, Funktions- und vor allem Denkweise ist aber gleich.
final BigDecimal totalOfDiscountedPrices = MyApi .getPrices().stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Result: " + totalOfDiscountedPrices);- WIE unter der Haube gekapselt in Verben (filter, map, reduce) - WAS auf Einzelelement-Basis definiert
⇒ wartbarer
- geschlossener Scope in Java nicht umgesetzt, siehe später - NatSpr + intuitiver (gewöhnungsbedürftig, wie OO auch) - Fehleranfälligkeit: weniger Code, (keine Variablenmuation)Java Boardmittel & die "Grundverben"
Stream<T> Collection<T>::stream()
collection.stream() .someFpVerb(command);
Stream<R> Stream<T>::map(Function<? super T, ? extends R> mapper)
teamSchadow.stream() .map((String name) -> { return name.toUpperCase(); }); // "DOMINIK", "SANDRO", "JOCHEN", ...
Stream<T> Stream<T>::filter(Predicate<? super T> predicate)
teamSchadow.stream() .filter(name -> name.startsWith("D")) .map(String::toUpperCase); // "DOMINIK", ...
T Stream<T>::reduce(T identity, BinaryOperator<T> accumulator)
teamSchadow.stream() .reduce("", (collector, name) -> { return collector + ", " + name; }); // "Dominik, Sandro, Jochen, ..."
In java.util.stream
streamParallel() statt stream(), so einfach geht das.
WIE unter der Haube, Threading ist auch so ein WIEDie Einfachheit der Anpassung (und damit einhergehend die Folienanzahl) wird der Mächtigkeit kaum gerecht.
Daher strecke ich auf drei Folien :-)
Bibliotheken, Frameworks & Sprachen
... und ein paar andere
Functional Java (viel Conveniece)
ReactiveX is a combination of the best ideas from the Observer pattern, the Iterator pattern, and functional programming.
Eigenen Code funktional zugänglich machen
Beispiele für Entwicklung mit FP
public static int totalAssetValues(final List<Asset> assets, final Predicate<Asset> assetSelector) { return assets.stream() .filter(assetSelector) .mapToInt(Asset::getValue) .sum(); } System.out.println("Total of assets: " + totalAssetValues(assets, asset -> true)); System.out.println("Total of bonds: " + totalAssetValues(assets, asset -> asset.getType() == AssetType.BOND)); System.out.println("Total of stocks: " + totalAssetValues(assets, asset -> asset.getType() == AssetType.STOCK));
Macht auch testen einfacher (z. B. asset -> throw new EnumConstantNotPresentException("on purpose");)
- Dependency hier assetSelector - Siehe auch Punkt oben "FIs als Parameter akzeptieren"public class Mailer { public void from(final String address) { /*... */ } public void to(final String address) { /*... */ } public void subject(final String line) { /*... */ } public void body(final String message) { /*... */ } public void send() { privateValidate(); privateSend(); } }
Mailer mailer = new Mailer(); mailer.from("build@agiledeveloper.com"); mailer.to("venkats@agiledeveloper.com"); mailer.subject("build notification"); mailer.body("...your code sucks..."); mailer.send();
public class FluentMailer { private FluentMailer() {} public FluentMailer from(final String address) { /*... */; return this; } public FluentMailer to(final String address) { /*... */; return this; } public FluentMailer subject(final String line) { /*... */; return this; } public FluentMailer body(final String message) { /*... */; return this; } public static void send(final Consumer<FluentMailer> mailerConsumer) { final FluentMailer mailer = new FluentMailer(); mailerConsumer.accept(mailer); mailer.privateValidate(); mailer.privateSend(); } }
FluentMailer.send(mailer -> mailer .from("build@agiledeveloper.com") .to("venkats@agiledeveloper.com") .subject("build notification") .body("...much better..."));- Fluent (return this) - send() aktzeptiert FI - send() managed mailer
lightBoolean && heavy1() && heavy2()
myMethod(lightBoolean, heavy1(), heavy2());
Lambdas werden "vor Ort" ausgewertet, aber nicht aufgerufen
Supplier<Boolean> lightLambda1 = () -> heavy1(); Supplier<Boolean> lightLambda2 = () -> heavy2(); myMethod(lightBoolean, lightLambda1, lightLambda2);
List<String> names = Arrays.asList("Brad", "Kate", "Kim", "Jack", "Joe", "Mike", "Susan", "George", "Robert", "Julia", "Parker", "Benson"); final String firstNameWith3Letters = names.stream() .filter(name -> length(name) == 3) .map(name -> toUpper(name)) .findFirst() .get();
Was wäre Log-Ausgabe der Lambdas?
getting length for "Brad" getting length for "Kate" getting length for "Kim" converting "Kim" to uppercase KIM
Nicht 100%-ig umgesetzt bzw. überhaupt umsetzbar
⇒
Aber FP ist in, daher muss Java es können...
Nichtsdestotrotz sehr hilfreich für den Alltag!