conferência internacional de desenvolvimento de software - … · 2015. 5. 27. · manipula...
TRANSCRIPT
Código funcional em Java: Superando o hype
Eder Ignatowicz @ederign
Lambda Expressions 101
Dora
Bento
<3
Jesse "Pinkman"
public Pug( String nome, String color, Integer size ) { this.nome = nome; this.color = color; this.weight = size; }
Pug dora = new Pug( "Dora", "abricot", 10 ); Pug bento = new Pug( "Bento", "abricot", 13 ); Pug jesse = new Pug( "Jesse", "black", 9 );
Requisito:
Filtrar todos os pugs de cor “abricot" da lista
Filtrar todos os pugs de cor “abricot" da lista
private static List<Pug> filterAbricotPugs( List<Pug> pugs ) { List<Pug> abricots = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getColor().equals( "abricot" ) ) { abricots.add( pug ); }
} return abricots; }
List<Pug> pugs = Arrays.asList( dora, bento, jesse );
List<Pug> abricot = filterAbricotPugs( pugs );
Pug{nome='Dora', color='abricot', weight=10} Pug{nome='Bento', color='abricot', weight=13}
Novo Requisito: :)
Filtrar todos os pugs de cor “black" da lista
private static List<Pug> filterAbricotPugs( List<Pug> pugs ) { List<Pug> abricots = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getColor().equals( "abricot" ) ) { abricots.add( pug ); }
} return abricots; }
private static List<Pug> filterBlackPugs( List<Pug> pugs ) { List<Pug> abricots = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getColor().equals( "black" ) ) { abricots.add( pug ); }
} return abricots; }
Filtrar todos os pugs de cor “black" da lista private static List<Pug> filterPugByColor( List<Pug> pugs, String color ) { List<Pug> coloured = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getColor().equals( color ) ) { coloured.add( pug ); }
} return coloured;
}
List<Pug> black = filterPugByColor( pugs, "black" );
black.forEach( System.out::println );
Pug{nome='Jesse', color='black', weight=9}
Novo Requisito: :)
Filtrar todos os pugs com sobrepeso (>10 kg)
Filtrar todos os pugs com sobrepeso (>10 kg)
private static List<Pug> filterPugBySize( List<Pug> pugs, int size ) { List<Pug> fat = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getWeight() > size ) { fat.add( pug ); }
} return fat; }
List<Pug> fat = filterPugBySize( pugs, 10 );
fat.forEach( System.out::println );
Pug{nome='Bento', color='abricot', weight=13}
private static List<Pug> filterPugs( List<Pug> pugs, String color, int weight, boolean flag ) { List<Pug> result = new ArrayList<>(); for ( Pug pug : pugs ) { if ( ( flag && pug.getColor().equals( color ) ) || ( !flag && pug.getWeight() > weight ) ) { result.add( pug ); }
} return result; }
Refatorador :D
List<Pug> result = filterPugs(pugs, "black", 0, true); List<Pug> result1 = filterPugs(pugs, "", 10, false);
Behavior Parametrization
Behavior Parametrization public interface PugPredicate { boolean test(Pug pug); }
class BlackPugPredicate implements PugPredicate{
@Override public boolean test( Pug pug ) { return pug.getColor().equals( "black" ); } }
class FatPugPredicate implements PugPredicate{
@Override public boolean test( Pug pug ) { return pug.getWeight()>10; } }
Predicate
List<Pug> black = filterPug( pugs, new BlackPugPredicate() );
List<Pug> fat = filterPug( pugs, new FatPugPredicate() );
fat.forEach( System.out::println );
Pug{nome='Bento', color='abricot', weight=13}
Problemas?
Classes anônimas <3
List<Pug> abricotsNotFat =
filterPug( pugs, new PugPredicate() { @Override public boolean test( Pug pug ) { return pug.getColor().equals( "abricot" ) && pug.getWeight() <= 10; } } );
abricotsNotFat.forEach( System.out::println );
Pug{nome='Dora', color='abricot', weight=10}
Expressões Lambda <3
(Pug p1,Pug p2) -> p1.getWeight().compareTo(p2.getWeight())
Parâmetros do
Lambda
"Arrow"
Corpo do
Lambda
class BlackPugPredicate implements PugPredicate{
@Override public boolean test( Pug pug ) { return "black".equals( pug.getColor() ) ); } }
blacks = filterPug( pugs, new BlackPugPredicate() );
blacks = filterPug( pugs,
(Pug pug) ->"black".equals( pug.getColor() ) );
Posso modificar mais um pouco?
public interface Predicate<T>{ boolean test(T t);}
public static <T> List<T> filter (List<T> list, Predicate<T> p){
List<T> result = new ArrayList<>(); for(T e: list){
if(p.test(e)){ result.add(e);
} }
return result;
}
List<Pug> blacks =
filter( pugs,
(Pug pug) ->"black".equals( pug.getColor() ) );
List<String> pares =
filter(numbers,
(Integer i) -> i % 2 == 0);
Predicate<Pug> fatPredicate = d -> d.getWeight() > 9; Predicate<Pug> abricotPredicate1 = d -> d.getColor().equalsIgnoreCase( "abricot" ); Predicate<Pug> abricotAndFat = abricotPredicate.and( fatPredicate );
@FunctionalInterface public interface Predicate<T>{ boolean test(T t); }
public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> results = new ArrayList<>(); for(T s: list){ if(p.test(s)){ results.add(s); } } return results; }
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
Predicate
@FunctionalInterface public interface Function<T, R>{ R apply(T t); } public static <T, R> List<R> map(List<T> list, Function<T, R> f) { List<R> result = new ArrayList<>(); for(T s: list){ result.add(f.apply(s)); } return result; }
List<Integer> l = map(Arrays.asList("qcon","em","sampa"),
(String s) -> s.length() ); // [4, 2, 5]
Function
Etc. etc. etc.
BiConsumer BiFunction
BinaryOperator BiPredicate
BooleanSupplier Consumer
DoubleBinaryOperator DoubleConsumer DoubleFunction DoublePredicate DoubleSupplier
DoubleToIntFunction DoubleToLongFunction DoubleUnaryOperator
Function IntBinaryOperator
IntConsumer IntFunction IntPredicate IntSupplier
IntToDoubleFunction IntToLongFunction IntUnaryOperator
LongBinaryOperator LongConsumer LongFunction LongPredicate LongSupplier
LongToDoubleFunction LongToIntFunction
LongUnaryOperator ObjDoubleConsumer
ObjIntConsumer ObjLongConsumer
Predicate Supplier
ToDoubleBiFunction ToDoubleFunction
ToIntBiFunction ToIntFunction
ToLongBiFunction ToLongFunction UnaryOperator
(Pug p1,Pug p2) -> "black".equals( pug.getColor() )
Parâmetros do
Lambda
"Arrow"
Corpo do
Lambda
Código funcional em Java: Superando o hype
Eder Ignatowicz
Streams API
Streams: Manipula coleções de forma declarativa
Quais são os nomes dos Pugs com peso maior do que 9 quilos?
Streams: Manipula coleções de forma declarativa
SELECT nome FROM pugs WHERE weight < 9 order by peso.
Em JavaList<Pug> gordinhos = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getWeight() > 9 ) { gordinhos.add( pug ); } } Collections.sort( gordinhos, new Comparator<Pug>() { @Override public int compare( Pug p1, Pug p2 ) { return Integer.compare( p1.getWeight(), p2.getWeight() ); } });
List<String> nomeGordinhos = new ArrayList<>(); for ( Pug pug : gordinhos ) { nomeGordinhos.add( pug.getNome() ); }
Java 8Streams API
Em JavaList<String> gordinhosNome =
pugs.stream()
.filter( d -> d.getWeight() > 9 )
.sorted( comparing( Pug::getWeight ) )
.map( Pug::getNome )
.collect( toList() );
Seleciona > 9 kg
Ordena por peso
Extrai o nomeArmazena em uma lista
Em JavaList<Pug> gordinhos = new ArrayList<>(); for ( Pug pug : pugs ) { if ( pug.getWeight() > 9 ) { gordinhos.add( pug ); } } Collections.sort( gordinhos, new Comparator<Pug>() { @Override public int compare( Pug p1, Pug p2 ) { return Integer.compare( p1.getWeight(), p2.getWeight() ); } });
List<String> nomeGordinhos = new ArrayList<>(); for ( Pug pug : gordinhos ) { nomeGordinhos.add( pug.getNome() ); }
Em JavaList<String> gordinhosNome =
pugs.parallelStream()
.filter( d -> d.getWeight() > 9 )
.sorted( comparing( Pug::getWeight ) )
.map( Pug::getNome )
.collect( toList() );
Seleciona > 9 kg
Ordena por peso
Extrai o nomeArmazena em uma lista
(
Parallel streams não são mágica!
)
Stream Pipelines
filterpugs -> —> sorted —> map —> collect
lambda lambda lambda
Código: Declarativo
Componentizável Paralelizável
Streams API
Sequência de elementos de uma fonte que suporta operações de processamento em seus dados
Streams
Sequência de elementos de uma fonte que suporta operações de processamento em seus dados
Streams
Sequência de elementos de uma fonte que suporta operações de processamento em seus dados
Streams
Sequência de elementos de uma fonte que suporta operações de
processamento em seus dados
Streams
Streams
Uma fonte de dados para a query Uma cadeia de operações intermediárias
(pipeline) Uma operação terminal que gera o resultado
Vamos a prática
public Venda( Vendedor vendedor, int ano, int valor ) { this.vendedor = vendedor; this.ano = ano; this.valor = valor; }
public Vendedor( String nome, String cidade ) { this.nome = nome; this.cidade = cidade; }
Vendedor eder = new Vendedor("Eder", "Campinas"); Vendedor pedro = new Vendedor("Pedro", "Apucarana"); Vendedor luciano = new Vendedor("Luciano", "Piracicaba"); Vendedor junior = new Vendedor("Junior", "Londrina");
List<Venda> transactions = Arrays.asList( new Venda( eder, 2014, 100 ), new Venda( eder, 2013, 200 ), new Venda( pedro, 2014, 300 ), new Venda( luciano, 2012, 500 ), new Venda( luciano, 2012, 400 ), new Venda( junior, 2012, 500 ));
Quais são as vendas que fizemos em 2014? Ordenadas?
List<Venda> vendas2014 = transactions .stream()
List<Venda> vendas2014 = transactions .stream() .filter( venda -> venda.getAno() == 2014 )
List<Venda> vendas2014 = transactions .stream() .filter( venda -> venda.getAno() == 2014 ) .sorted( comparing( Venda::getValor ) )
List<Venda> vendas2014 = transactions .stream() .filter( venda -> venda.getAno() == 2014 ) .sorted( comparing( Venda::getValor ) ) .collect( toList() );
List<Venda> vendas2014 = transactions .stream() .filter( venda -> venda.getAno() == 2014 ) .sorted( comparing( Venda::getValor ) ) .collect( toList() );
vendas2014.forEach(System.out::println);
Venda{vendedor=Vendedor{nome='Eder', cidade='Campinas'}, ano=2014, valor=100}
Venda{vendedor=Vendedor{nome='Pedro', cidade='Apucarana'}, ano=2014, valor=300}
Em que cidades temos vendedores?
List<String> cidadesAtendidas = vendas.stream() .map( venda -> venda.getVendedor().getCidade() ) .distinct() .collect( toList() );
Campinas Apucarana Piracicaba Londrina
Qual foi a maior venda?
Optional<Integer> maiorVenda = vendas.stream() .map(Venda::getValor) .reduce( Integer::max );
maiorVenda.ifPresent( i -> System.out.println(i));
500
Total de vendas?
Optional<Integer> totalVendas = vendas.stream() .map(Venda::getValor) .reduce( Integer::sum );
totalVendas.ifPresent( i -> System.out.println(i));
2000
Quais são as vendas de cada vendedor? Ordenadas?
Map<Vendedor, List<Venda>> vendedorPorVendas = vendas.stream() .sorted( comparing( Venda::getValor ) ) .collect( groupingBy( Venda::getVendedor ) );
System.out.println(vendedorPorVendas);
{Vendedor{nome='Junior', cidade='Londrina'}=[Venda{vendedor=Vendedor{nome='Junior', cidade='Londrina'}, ano=2012, valor=500}],
Vendedor{nome='Pedro', cidade='Apucarana'}=[Venda{vendedor=Vendedor{nome='Pedro', cidade='Apucarana'}, ano=2014, valor=300}],
Vendedor{nome='Luciano', cidade='Piracicaba'}=[Venda{vendedor=Vendedor{nome='Luciano', cidade='Piracicaba'}, ano=2012, valor=400}, Venda{vendedor=Vendedor{nome='Luciano', cidade='Piracicaba'}, ano=2012, valor=500}],
Vendedor{nome='Eder', cidade='Campinas'}=[Venda{vendedor=Vendedor{nome='Eder', cidade='Campinas'}, ano=2014, valor=100}, Venda{vendedor=Vendedor{nome='Eder', cidade='Campinas'}, ano=2013, valor=200}]}
Streams são lazy
Stream Pipelines
filterpugs -> —> map —> collect
lambda lambda
Stream Pipelines
intermediatepugs -> —> —> findFirst
lambda lambda
intermediate
Lazy Streams
List<Dog> dogs = Arrays.asList( new Dog( "Dora", true, 10, Dog.RACA.PUG ), new Dog( "Bento", true, 13, Dog.RACA.PUG ), new Dog( "Rex", false, 8, Dog.RACA.SRD ), new Dog( "Teté", false, 6, Dog.RACA.SRD ), new Dog( "Banzé", true, 7, Dog.RACA.SRD ), new Dog( "Rin-Tin-Tin", false, 15, Dog.RACA.PASTOR ) );
Qual o nome do primeiro SRD que pesa mais do que 5kg?
String nomePrimeiroSRDMaiorDoQue5Kg = dogs.stream() .filter( dog -> { System.out.println( "filter - " + dog.getNome() ); return dog.getRaca().equals( Dog.RACA.SRD) && dog.getPeso() > 5; }) .map( dog -> { System.out.println( "map - " + dog.getNome() ); return dog.getNome(); }) .findFirst() .get();
Eager Streams
Dora Bento
Rex Teté
Banzé Rin Tin tin
—>SRD e
peso> 5
filter map
Rex Teté
Banzé —> Nome
"Rex" “Teté"
“Banzé" —> —> Nome
findFirst
“Rex" —>—>
Lazy Stream
Dora Bento
Rex Teté
Banzé Rin Tin tin
—>SRD e
peso> 5
filter map
Rex—> Nome “Rex"—> —> first()
findFirst
“Rex" —>—>
String nomePrimeiroSRDMaiorDoQue5Kg = dogs.stream() .filter( dog -> { System.out.println( "filter - " + dog.getNome() ); return dog.getRaca().equals( Dog.RACA.SRD) && dog.getPeso() > 5; }) .map( dog -> { System.out.println( "map - " + dog.getNome() ); return dog.getNome(); }) .findFirst() .get();
filter - Dora filter - Bento filter - Rex map - Rex
Rex
List<Dog> dogs = Arrays.asList( new Dog( "Dora", true, 10, Dog.RACA.PUG ), new Dog( "Bento", true, 13, Dog.RACA.PUG ), new Dog( "Rex", false, 8, Dog.RACA.SRD ), new Dog( "Teté", false, 6, Dog.RACA.SRD ), new Dog( "Banzé", true, 7, Dog.RACA.SRD ), new Dog( "Rin-Tin-Tin", false, 15, Dog.RACA.PASTOR ) );
Streams "infinitos"
Criar uma lista de números primos infinita
public static boolean isPrime( final int number ) { return number > 1 && IntStream.rangeClosed( 2, (int) Math.sqrt( number ) ) .noneMatch( divisor -> number % divisor == 0 ); }
public static int primeAfter( final int number ) { if ( isPrime( number + 1 ) ) { return number + 1; } else { return primeAfter( number + 1 ); } }
Coleções "infinitas"
public static List<Integer> primes(final int fromNumber, final int count) { return Stream.iterate(primeAfter(fromNumber - 1), Primes::primeAfter) .limit(count) .collect(Collectors.<Integer>toList()); }
public static List<Integer> primes(final int fromNumber, final int count) { return Stream.iterate(primeAfter(fromNumber - 1), Primes::primeAfter) .limit(count) .collect(Collectors.<Integer>toList()); }
System.out.println("10 primos do 1: " + primes( 1, 10 )); System.out.println("10 primos do 1000: " + primes( 1000, 10 ));
10 primos do 1: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] 10 primos do 1000: [1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061]
Recursões
Fibonacci
public static Long fib( int n ) { if ( n < 2 ) { return new Long( n ); } else { return fib( n - 1 ) + fib( n - 2 ); } }
Memoization
Pure FunctionsIn computer programming, a function may be described as a pure function if both these statements about the function hold:
1-) The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change as program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices (usually—see below.
2-) Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices (usually—see below)
Manual
Integer doubleValue(Integer x) { return x * 2; }
Integer doubleValue(Integer x) { if (cache.containsKey(x)) { return cache.get(x); } else { Integer result = x * 2; cache.put(x, result) ; return result; } }
private Map<Integer, Integer> cache = new ConcurrentHashMap<>();
public Integer fib(int n) { if (n == 0 || n == 1) return n;
Integer result = cache.get( n );
if (result == null) { synchronized (cache) { result = cache.get(n);
if (result == null) { result = fib(n - 2) + fib(n - 1); cache.put(n, result); } } }
return result; }
Java 8 to the rescue
public class Fibonacci {
private Map<Integer, Integer> cache = new ConcurrentHashMap<>();
public Long fib(int n) { if (n == 0 || n == 1) return n;
return cache.computeIfAbsent(n, (k) -> fib(k - 2) + fib(k - 1)); }
}
DEMO
Automática
Function<Integer, Integer> f = x -> x * 2;
Function<Integer, Integer> g = Memoizer.memoize(f);
public class Memoizer {
public static <T, R> Function<T, R> memoize( Function<T, R> fn ) { Map<T, R> map = new ConcurrentHashMap<T, R>(); return ( t ) -> map.computeIfAbsent( t, fn ); }
}
static Integer longCalculation(Integer x) { try { Thread.sleep(1_000); } catch (InterruptedException ignored) { } return x * 2; }
static Function<Integer, Integer> loooongCalc = Memoizer::longCalculation;
static Function<Integer, Integer> loooongCalcMemo = Memoizer.memoize(loooongCalc);
loooongCalcMemo.apply( 30 ); 1005ms
loooongCalcMemo.apply( 30 ); 0 ms
static Function<Integer, Long> fibonacci = Fibonacci::calc;
static Function<Integer, Long> memoFib = Memoizer.memoize( fibonacci );
memoFib.apply( 30 ); 832040 56ms
memoFib.apply( 30 ); 832040 0 ms
E agora?
Programar funcional em Java é uma mudança de paradigma
Java é multi-paradigma
Imperativo, OO e Funcional
Escolha o melhor deles para o seu problema
Java 8, Lambdas e Streams trouxeram uma nova vida
para o Java
DIVIRTA-SE!
Obrigado!!!
@ederign