Queremos iterar sobre una colección de partidos de una competición y quedarnos sólo con los partidos que enfrentan a dos equipos concretos.
null en algún paso de la iteración?Guava es una aitigua biblioteca open source de Google que proporciona una amplia gama de utilidades para Java, incluyendo colecciones, cachés, primitivas, concurrencia, etc.
Iteración:
com.google.common.base.Predicate (inspiraron la clase java.util.function.Predicate de Java) —Guava define Predicate::apply() y Java define Predicate::test()filter a los Iterators, que recibe un Predicate como criterio de filtradonull en la programación fluentimport java.util.Iterator;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
Iterator<Match> matches = repository.findMatches(); // puede ser null
final Predicate<Match> condition = new Predicate<>() {
final Team team1 = new Team("Cadiz CF");
final Team team2 = new Team("RC Betis");
@Override
public boolean apply(Match match) {
return match.getLocalTeam().equals(team1)
&& match.getVisitingTeam().equals(team2);
}
};
Iterator<Match> matchesByTeam =
Iterators.filter(matches, condition); //
Excepción si matches == null
while (matchesByTeam.hasNext()) {
System.out.println(matchesByTeam.next());
}
Supongamos que matches se obtiene de una API heredada que puede devolver null. Entonces al hacer...
Iterator<Match> matchesByTeam = Iterators.filter(matches, condition);
null.matches es null, se lanzará una excepción NullPointerException al intentar iterar sobre matchesByTeam.import java.util.Iterator;
import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
class MatchByTeamsPredicate implements Predicate<Match> {
private final Team localTeam;
private final Team visitingTeam;
MatchByTeamsPredicate(Team localTeam, Team visitingTeam) {
this.localTeam = localTeam;
this.visitingTeam = visitingTeam;
}
@Override
public boolean test(Match match) {
return match.getLocalTeam().equals(localTeam)
&& match.getVisitingTeam().equals(visitingTeam);
}
}
Stream<Match> toStream(Iterator<Match> iterator) {
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, 0),
false
);
}
void main() {
Iterator<Match> matches = repository.findMatches(); // puede ser null
Predicate<Match> condition =
new MatchByTeamsPredicate(
new Team("Cadiz CF"),
new Team("RC Betis")
);
Stream.ofNullable(matches)
.flatMap(this::toStream)
.filter(condition)
.forEach(System.out::println);
}
Programación nullen los iterables:
FluentIterable para encadenar varios IterableFluentIterable por los Predicate o por el uso de StreamSupportStream.ofNullable()Lectura recomendada: From Guava's FluentIterable via StreamSupport to Java 8 Streams
Colecciones
ImmutableList, ImmutableSet, ImmutableMap... en GuavaList.of(), Set.of(), Map.of()... en Java 8Comprobar que un mismo partido (fixture) no está repetido ni se enfrenta un equipo contra sí mismo en un grupo de la competición...
import java.util.List;
import java.util.function.Predicate;
class FixturePredicate implements Predicate<Match> {
private final Team localTeam;
private final Team visitingTeam;
public FixturePredicate(Team local, Team visiting) {
this.localTeam = local; this.visitingTeam = visiting;
}
@Override
public boolean test(Match match) {
return match.getLocalTeam().equals(localTeam)
&& match.getVisitingTeam().equals(visitingTeam);
}
}
private Predicate<Match> fixture(Team localTeam, Team visitingTeam) {
return new FixturePredicate(localTeam, visitingTeam);
}
private void checkMatchesInGroup(List<Match> matchesInGroup) {
for (Match match : matchesInGroup) {
Team t1 = match.getLocalTeam();
Team t2 = match.getVisitingTeam();
assertNotSame(t1, t2);
List<Match> firstLeg = matchesInGroup.stream()
.filter(fixture(t1, t2))
.toList();
assertTrue(firstLeg.size() == 1);
List<Match> secondLeg = matchesInGroup.stream()
.filter(fixture(t2, t1))
.toList();
assertTrue(secondLeg.size() == 0);
}
}
lambdaUna expresión lambda es una función anónima (con o sin parámetros) que es llamada sin necesidad de asociarle un nombre explícito.
Sirven para pasarlas como argumento a funciones de orden superior, momento en el cual los parámetros de la función anónima toman un valor en el contexto de ejecución de la función contenedora que la recibe y ejecuta.
Por tanto, las funciones anónimas permiten acceder a variables (no locales) definidas en el ámbito de la contenedora.
Mecanismos de los lenguajes para implementar funciones anónimas:
Sintaxis
( argumentos ) -> expresión
Ejemplos
(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
Blocks
{ | argumentos | expresión }
Lambdas
->(argumentos) { expresión }
Ejemplos
[1, 2, 3].each { |num| puts num * 2 }
# 2 4 6
times_two = ->(x) { x * 2 }
times_two.call(10)
# 20
Sintaxis
[capture](parameters) -> return_type { body }
capture = entorno de referencia
[] – Sin variables externas definidas. Erróneo intentar utilizar cualquier variable externa
[x, &y] – x se captura por copia, y por referencia
[&] – Toda variable externa utilizada es capturada implícitamente por referencia
[=] – Toda variable externa utilizada es capturadas implícitamente por copia
[&, x] – x se captura explícitamente por copia; el resto, por referencia
[=, &z] – z se captura explícitamente por referencia; el resto, por copia
Clausura = función o referencia a función junto con un entorno de referencia
Tabla que guarda una referencia a cada una de las variables no locales (libres) de la función
std::vector<int> some_list; // assume that contains something
int total = 0;
for (int i=0;i<5;++i) some_list.push_back(i);
std::for_each(
begin(some_list),
end(some_list),
[&total](int x) { total += x; }
);
// Calcula la suma total de los elementos de la lista.
/*
La variable total se guarda como parte de la clausura de la
función lambda. Como es una referencia a la variable total de la
pila de ejecución, puede cambiar su valor.
*/
[capture](parameters) -> return_type { body }
[capture][=]) o por referencia ([&])body
mutable después de los (parameters): permite que body modifique los objetos capturados por copiaLecturas recomendadas: Lambdas en C++
Tutoriales recomendado:
Una expresión lambda en Java puede capturar (o no)...
final, pero cuyo valor no es modificado)...del ámbito contenedor.
En Java, una expresión lambda y una inner class tienen un propósito similar, pero son diferentes en el ámbito (scope) de definición de las variables locales.
Con una inner class se crea un nuevo ámbito para la clase
this dentro de una clase anónima para hacer referencia a su instanciaLas expresiones lambda trabajan con el ámbito contenedor
this hace referencia a una instancia de la clase contenedoraEn el ejemplo siguiente, ¿qué valor devuelve scopeExperiment()?
@FunctionalInterface
public interface ClaseFuncional{
String method(String string);
}
private String variable = "Valor de la contenedora";
public String scopeExperiment() {
ClaseFuncional unaInnerClass = new ClaseFuncional() {
String variable = "Valor de la inner class";
@Override
public String method(String string) {
return this.variable;
/* Con o sin this, no hay lambdas ni variables libres */
}
};
String resultadoInnerClass = unaInnerClass.method("");
return "resultadoInnerClass = " + resultadoInnerClass;
}
El valor devuelto será:
resultadoInnerClass = Valor de la inner class
En el ejemplo siguiente, ¿qué valor devuelve scopeExperiment()?
@FunctionalInterface
public interface ClaseFuncional{
String method(String string);
}
private String variable = "Valor de la contenedora";
public String scopeExperiment() {
ClaseFuncional unaLambda = parametro -> {
String variable= "Valor de la lambda";
return this.variable;
/* Con this, la clausura de la variable libre se produce
con el valor de ClaseFuncional::variable */
};
String resultadoLambda = unaLambda.method("");
return "resultadoLambda = " + resultadoLambda;
}
El valor será:
resultadoLambda = Valor de la contenedora
Sintaxis do ... end
some_list = [ 10, 20, 30 ]
some_list.map do |i|
i += 1
end
Sintaxis { ... }
some_list = [ 10, 20, 30 ]
some_list.map { |i| i += 1 }
El método map itera y aplica un bloque repetitivamente a cada elemento de una colección (representado por el parámetro i)
Sin bloques:
class SongList
def with_title(title)
for i in 0...@songs.length
return @songs[i] if title == @songs[i].name
end
return nil
end
end
Con bloques do ... end:
class SongList
def with_title(title)
@songs.find do |song|
title == song.name
end
end
end
Con bloques { ... }:
class SongList
def with_title(title)
@songs.find { |song| title == song.name }
end
end
El método find itera y aplica el test del bloque a cada elemento song de la colección.
Ejemplo: fibonacci
def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
fib_up_to(200) {|f| print f, " " }
#Salida => 1 1 2 3 5 8 13 21 34 55 89 144
Ejecución con yield:
yieldyieldEjemplos de yield:
def three_times
yield
yield
yield
end
three_times { puts "Hello" }
class Array
def find
for i in 0...size
value = self[i]
return value if yield(value)
end
return nil
end
end
Ejemplos: iterar con bloques
Iterar sobre un array con each
Array#each: recibe un array y aplica el bloque a cada item, sin modificar el array ni crear un nuevo objeto; devuelve el mismo array.
[ 1, 3, 5, 7, 9 ].each {|i| printf i, " " }
#Salida => 1 3 5 7 9
Array a = [ 1, 2, 3, 4 ]
a.each {|i| puts i*2 }
#Salida => 2 4 6 8
#Devuelve => [1, 2, 3, 4]
a
#Devuelve => [1, 2, 3, 4]
Iterar sobre un fichero con each
File#each: recibe el contenido de un fichero de texto y aplica el bloque a cada línea.
f = File.open("testfile")
f.each do |line|
puts line
end
f.close
f = File.open("testfile")
f.each {|line| puts line}
f.close
Iterar sobre un array con collect
Array#collect: aplica el bloque a todos los items y devuelve el nuevo array modificado; hace lo mismo que Array#map
["H", "A", "L"].collect {|x| x.succ }
# Salida => [''I'', ''B'', ''M'']
Array a = [ 1, 2, 3, 4 ]
a.collect {|i| puts i*2}
#Salida => 2 4 6 8
#Devuelve => [nil, nil, nil, nil]
a.collect {|i| i.succ}
#Devuelve => [2, 3, 4, 5]
a
#Devuelve => [1, 2, 3, 4]
En Ruby, una función anónima o lambda es simplemente un tipo especial de objeto Proc
Definición de procs/lambdas:
# sin argumentos:
say_something = -> { puts "This is a lambda" }
say_something = lambda { puts "This is a lambda" }
say_otherwise = Proc.new { puts "This is a proc" }
# con argumentos:
times_two = ->(x) { x * 2 }
Varias formas de llamar a la lambda (es preferible call)
say_something = -> { puts "This is a lambda" }
say_something.call
say_something.()
say_something[]
say_otherwise = Proc.new { puts "This is a proc" }
say_otherwise.call
times_two = ->(x) { x * 2 }
times_two.call(10)
Los proc no se preocupan de los argumentos:
t = Proc.new { |x,y| puts "I don't care about args!" }
t.call #Salida: I don't care about args!
t.call(10) #Salida: I don't care about args!
t.call(10,10) #Salida: I don't care about args!
t.call(10,10) #Salida: I don't care about args!
s = ->(x,y) { puts "I care about args" }
s.call # ArgumentError: wrong number of arguments (given 0, expected 2)
s.call(10) # ArgumentError: wrong number of arguments (given 1, expected 2)
s.call(10,10) # Salida: I care about args
Los proc retornan del método actual; los lambda retornan de la función anónima:
# funciona:
my_lambda = -> { return 1 }
puts "Lambda result: #{my_lambda.call}"
# eleva una exceción:
my_proc = Proc.new { return 1 }
puts "Proc result: #{my_proc.call}"
Si el proc está dentro de un método, la llamada a return es equivalente a retornar de ese método:
def call_proc
puts "Before proc"
my_proc = Proc.new { return 2 }
my_proc.call
puts "After proc"
end
puts call_proc
# Prints "Before proc" but not "After proc"
def call_lambda
puts "Before lambda"
my_lambda = lambda { return 2 }
my_lambda.call
puts "After lambda"
end
puts call_lambda
# Prints "Before lambda" and "After lambda"
Diferencias entre Proc y lambda:
-> {} y los procs con Proc.new {}Proc retornan del método actual, las lambdas retornan de la propia función lambdaProc no se preocupan del número correcto de argumentos, las lambdas elevan una excepciónyieldProc) se pasa como una especie de parámetro no declaradoEjemplos de paso de bloques:
Llamada a un bloque sin parámetros
def run_it
puts("Before the yield")
yield
puts("After the yield")
end
run_it do
puts('Hello')
puts('Coming to you from inside the block')
end
# Salida =>
# Before the yield
# Hello
# Coming to you from inside the block
# After the yield
Cualquier método puede recibir un bloque como parámetro implícito, pero no lo ejecuta si no hace yield:
def run_it
end
run_it do
puts('Hello')
end
# => No genera salida
Con yield:
def run_it
yield if block_given?
end
run_it do
puts('Hello')
end
# Salida =>
# Hello
Llamada a un bloque con parámetros:
def run_it_with_parameter
puts('Before the yield')
yield(24)
puts('After the yield')
end
run_it_with_parameter do |x|
puts('Hello from inside the proc')
puts("The value of x is #{x}")
end
# Salida =>
# Before the yield
# Hello from inside the proc
# The value of x is 24
# After the yield
Hacer explícito el bloque pasado como parámetro usando ampersand: explicitamos que se espera que el método reciba un parámetro de tipo bloque
def run_it_with_parameter(&block)
puts('Before the call')
block.call(24)
puts('After the call')
end
Convertir un Proc o un lambda en un bloque pasado como parámetro:
my_proc = Proc.new {|x| puts("The value of x is #{x}")}
run_it_with_parameter(&my_proc)
my_lambda = lambda {|x| puts("The value of x is #{x}")}
run_it_with_parameter(&my_lambda)
# Salida (en ambos casos) =>
# Before the call
# The value of x is 24
# After the call
Lecturas recomendadas
Un stream representa una secuencia de elementos que soportan diferentes tipos de operaciones para realizar cálculos sobre ellos
Las operaciones sobre un stream pueden ser intermediarias o terminales
[Probar en paiza.io]
void main() {
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
}
Las operaciones que se aplican sobre un stream aceptan algún parámetro en forma de:
Las operaciones aplicadas no pueden modificar el estado del stream original
En el ejemplo anterior, se puede observar que:
filter, map y sorted son operaciones intermediariasforEach es una operación terminalmyList añadiendo o eliminando elementosvoid main() {
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
myList
.stream()
.reduce( (a,b) -> a + " " + b )
.ifPresent(System.out::println);
}
Reutilizamos el caso de filtrado de partidos que vimos en el apartado de Predicados, pero implementando fixture() como una factoría con una expresión lambda, en lugar de una clase concreta:
import java.util.List;
import java.util.function.Predicate;
private Predicate<Match> fixture(Team local, Team visiting) {
return match -> match.getLocalTeam().equals(local)
&& match.getVisitingTeam().equals(visiting);
}
La lambda captura los parámetros local y visiting y devuelve un Predicate<Match> funcional. No hace falta clase auxiliar; la lógica de filtrado está integrada en la expresión lambda.
Ahora usamos la factoría en un stream para validar que en una competición no hay partidos repetidos ni un equipo enfrentado a sí mismo:
private void checkMatchesInGroup(List<Match> matchesInGroup) {
for (Match match : matchesInGroup) {
Team t1 = match.getLocalTeam();
Team t2 = match.getVisitingTeam();
assertNotSame(t1, t2); // no juega contra sí mismo
List<Match> firstLeg = matchesInGroup.stream()
.filter(fixture(t1, t2))
.toList();
assertTrue(firstLeg.size() == 1);
List<Match> secondLeg = matchesInGroup.stream()
.filter(fixture(t2, t1))
.toList();
assertTrue(secondLeg.size() == 0);
}
}
El pipeline de streams no cambia: sigue siendo stream().filter(...).toList(). Lo que cambia es que ahora el criterio de filtrado (fixture(...)) devuelve una lambda inline en lugar de una instancia de una clase que implementa Predicate.
--- ### Retrollamadas (_callbacks_) - Un __callback__ o retrollamada es un fragmento de código ejecutable que se pasa como argumento. - Hacen falta interfaces funcionales para poder definir retrollamadas --- #### Implementaciones en C/C++ - Puntero a función: ```c int (*f)(void) ``` - Con puntero asociado a datos: ```c void (*f)(void *data) ``` - _functor_ en C++ - clase que define `operator()` - es una clase y por tanto pueden contener un estado
--- __Ejemplo: Lambda que captura variables locales `final`__ ```java final BigDecimal bd = new BigDecimal(1); final BigDecimal x = new BigDecimal(2); Function<BigDecimal, BigDecimal> func = (a) -> bd.multiply(a).add(x); for (int j = 0; j < 999999999; j++) { func.apply(new BigDecimal(j)); } ``` --- __Ejemplo: Lambda que captura variables locales no declaradas `final` pero cuyo valor no es modificado__ ```java BigDecimal bd = new BigDecimal(1); BigDecimal x = new BigDecimal(2); // Se puede consultar x pero no se podría cambiar el valor de x: Function<BigDecimal, BigDecimal> func = (a) -> bd.multiply(a).add(x); // x debe ser final o efectivamente final: // (a) -> { bd.multiply(a).add(x); x = new BigDecimal(0); return bd; }; for (int j = 0; j < 999999999; j++) { func.apply(new BigDecimal(j)); } ``` --- __Ejemplo: Lambda que captura variables de instancia de la clase contenedora__ ```java public class LambdaInstanceCapturing implements Runnable { private BigDecimal bd = new BigDecimal(1); @Override public void run() { Function<BigDecimal, BigDecimal> func = (a) -> bd.multiply(a); for (int j = 0; j < 999999999; j++) { func.apply(new BigDecimal(j)); } } } ```