Entidades de java.util.functional¶
Antes de crear una función de tipo lambda, conviene conocer las entidades básicas que componen esta manera de programar. Las principales entidades son interfaces con un único método que debe implementar el programador y que estas implementaciones pueden hacerse llegar como argumentos de métodos de otras muchas clases del API de Java
Las implementaciones de estas interfaces son del tipo, consume un valor y retorna otro tipo de valor, o produce un valor sin argumentos o produce un valor dados dos argumentos. A éstas se les llama unidades funcionales porque componen una lógica interna que a priori el consumidor de esta lógica no conoce, pero de la que sí se conoce su interfaz y por tanto la manera de relacionarse con el resto de los objetos, o lo que es lo mismo la manera de ser invocada
Las interfaces funcionales más importantes contenidas en java.util.functional son:
- Predicate: Se utiliza en expresiones lambda para comprobar si una condición dada es verdadera o falsa
- Supplier: esta función se debe utilizar cuando se necesiten generar objetos sin requerir argumentos. Por ejemplo para realizar una inicialización perezosa.
- Consumer esta en cambio es el opuesto de Supplier ya que consume, acepta como argumento el tipo T sin devolver ningún valor de retorno.
Function<T,R>
esta interfaz permite definir una función que acepta un parámetro de tipo T y devuelve un resultado del tipo R pudiendo aplicarle alguna transformación u operación.BiFunction<T,R,S>
esta interfaz permite definir una función que acepta dos parámetros de tipo T y R, devolviendo un resultado del tipo S. Normalmente serán operaciones de agregación u operadores binarios como la suma, resta, etc..
Predicate¶
Predicate la interfaz predicado debe devolver forzosamente un boolean dado un objeto de tipo T, normalmente utilizado para filtrar elementos de una colección.
Predicate es una interfaz funcional que se encuentra en el paquete java.util.function
. Mejora la capacidad de administración del código, ayuda a realizar pruebas unitarias por separado.
Se utiliza en expresiones lambda para comprobar si una condición dada es verdadera o falsa.
En lugar de pasar un booleano con valor true or false, pasas una referencia de tipo Predicate para determinar de manera dinámica si una condición dada es verdadera, o falsa.
Esta es la estructura de la interfaz Predicate
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Como vemos la interfaz Predicate
utiliza los genéricos para poder decirle que tipo concreto vamos a utilizar.
Ejemplo:
public class EjemploPredicate {
public static void main(String[] args) {
Predicate<String> checker = a -> a.startsWith("M");
System.out.println(checker.test("Miguel"));
}
}
Hemos creado un objeto Predicate
de tipo String
. Le damos cuerpo al método test
con una expresión lambda que se le pasa un argumento y devuelve un booleano.
Métodos Predicate¶
La interfaz Predicate
contiene algunos métodos como:
isEqual(Object targetRef)
: Devuelve un predicado que prueba si dos argumentos son iguales.and(Predicate other)
: Devuelve un predicado compuesto que representa un AND lógico de este predicado y otro.or(Predicate other)
: Devuelve un predicado compuesto que representa un OR lógico de este predicado y otro.negate()
: Devuelve un predicado que representa la negación lógica de este predicado.
Ejemplo:
Predicate<Integer> greaterThan10 = i -> i > 10;
Predicate<Integer> lessThan20 = i -> i < 20;
//si es mayor de 10 y menor de 20->true
Predicate<Integer> andPredicate=greaterThan10.and(lessThan20);
System.out.println(andPredicate.test(15));//true
//negate, niega el resultado del predicado
System.out.println(andPredicate.negate().test(15));//false
//isEquals, Devuelve un predicado que prueba si dos argumentos son iguales //según Objects.equals(Object, Object).
Predicate<String> compara = Predicate.isEqual("hola");
System.out.println(compara.test("java"));//false
System.out.println(compara.test("hola"));//true
Nota
Debido al uso extendido de Predicate
se han añadido las interfaces funcionales IntPredicate
cuando queremos trabajar con predicados de tipo entero, DoublePredicate
y LongPredicate
. También tenemos la interfaz BiPredicate
que es un caso especial de Predicate
y recibe dos parámetros en vez de uno.
IntPredicate predicate = (x) -> {
if (x == 12345) {
return true;
}
return false;
};
System.out.println(predicate.test(12345));
//creamos otro predicado que niega el anterior
IntPredicate intPredicate1 = predicate.negate();
System.out.println(intPredicate1.test(12345));
BiPredicate<String, Integer> filtroLongitud = (x, y) -> {
return x.length() == y;
};
boolean result = filtroLongitud.test("java", 10);
System.out.println(result); // false
Supplier¶
Supplier
es otra interfaz funcional dentro del paquete java.util.function
que nos provee del método abstracto **get**
, sin argumentos que devuelve un tipo de dato.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Esta interfaz también se utiliza con expresiones lambda que no tienen parámetros pero devuelven un resultado:
Random random = new Random();
//devuelve un número aleatorio entre 1..6
Supplier<Integer> dados = () -> random.nextInt(6)+1;
System.out.println("Jugada 1: " +dados.get());
System.out.println("Jugada 2: " +dados.get());
Al igual que ocurría en los predicados con los Supplier
también disponemos de las clases IntSupplier
, DoubleSupplier
, LongSupplier
y BooleanSupplier
.
Consumer¶
Consumer
es otra interfaz funcional dentro del paquete java.util.function
que provee un método que recibe un solo parámetro de tipo genérico y no devuelve nada.
public interface Consumer<T> {
void accept(T t);
}
La expresión lambda asignada a un objeto de tipo Consumer
se usa para definir su método **accept(T t)**
que eventualmente aplica la operación dada en su argumento. Los Consumer
son útiles cuando no necesitan devolver ningún valor, ya que se espera que operen a través de efectos secundarios.
Existen también las interfaces IntConsumer
, LongConsumer
y DoubleConsumer
.
Consumer<Integer> doble = (x) -> System.out.println(x*2);
doble.accept(5);
default void forEach(Consumer<? super T> action)
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("java");
list.add("lambda");
list.add("test");
list.forEach(
//n es el elemento actual de la lista
(n)->System.out.println(n)
);
List<Integer> list2=new ArrayList<Integer>();
list2.add(1);
list2.add((2));
list2.add(3);
list2.forEach(n->System.out.println(n%2==0?"Es par":"Es impar"));
}
BiConsumer
es un caso especial de las expresiones Consumer
, son aquellas que reciben dos valores como parámetro y no devuelven resultado.
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
Ejemplo:
BiConsumer<Integer, String> biConsumer = (x, s) -> System.out.println(x + s);
biConsumer.accept(3, " puntos");