Java Stream Filter y Predicate: Guía Completa con Ejemplos
Aprende a usar filter() y Predicate en Java Streams, cómo combinar condiciones con and(), or() y negate(), con ejemplos de código reales.
El método filter() de la API de Streams permite seleccionar únicamente los elementos que cumplen una condición determinada, definida a través de un Predicate. Es una de las operaciones intermedias más usadas junto a map().
¿Qué es filter() en un Stream?
filter() recibe un Predicate y devuelve un nuevo Stream que contiene solo los elementos para los que ese predicado devuelve true. Es una operación intermedia: no se ejecuta hasta que aparece una operación terminal en el pipeline.
¿Qué es un Predicate en Java?
Un Predicate<T> es una interfaz funcional que representa una función que recibe un objeto de tipo T y devuelve un boolean. Es, literalmente, una condición encapsulada como objeto.
Predicate<Integer> esPar = n -> n % 2 == 0;
System.out.println(esPar.test(4)); // true
Sintaxis básica de filter() con lambda
List<Integer> numeros = List.of(1, 2, 3, 4, 5, 6);
List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// pares -> [2, 4, 6]
Combinar Predicates: and(), or(), negate()
Predicate<Integer> esPar = n -> n % 2 == 0;
Predicate<Integer> esMayorQueDiez = n -> n > 10;
Predicate<Integer> parYMayorQueDiez = esPar.and(esMayorQueDiez);
Predicate<Integer> parONegativo = esPar.or(n -> n < 0);
Predicate<Integer> esImpar = esPar.negate();
Estos métodos permiten construir condiciones complejas combinando predicados simples y reutilizables, en lugar de escribir expresiones lambda largas y difíciles de leer.
Ejemplo práctico: filtrar una lista de objetos
record Empleado(String nombre, String departamento, double salario) {}
List<Empleado> empleados = List.of(
new Empleado("Ana", "IT", 2800),
new Empleado("Luis", "Ventas", 2100),
new Empleado("Carla", "IT", 3200)
);
Predicate<Empleado> esDeIT = e -> e.departamento().equals("IT");
Predicate<Empleado> ganaMasDe3000 = e -> e.salario() > 3000;
List<Empleado> resultado = empleados.stream()
.filter(esDeIT.and(ganaMasDe3000))
.collect(Collectors.toList());
// resultado -> [Carla]
Reutilizar Predicates como variables
Declarar los Predicate como variables (o incluso como constantes en una clase de utilidades) mejora la legibilidad y permite reutilizar la misma condición en distintos puntos del código sin duplicar lógica.
public class EmpleadoPredicates {
public static final Predicate<Empleado> ES_DE_IT =
e -> e.departamento().equals("IT");
}
filter() combinado con map() y collect()
List<String> nombresITConSalarioAlto = empleados.stream()
.filter(esDeIT.and(ganaMasDe3000))
.map(Empleado::nombre)
.collect(Collectors.toList());
Este es el patrón más habitual en proyectos reales: filtrar según una o varias condiciones y transformar el resultado antes de recogerlo.
Errores comunes
- Escribir condiciones muy largas dentro de una sola lambda en lugar de descomponerlas en Predicates reutilizables y legibles.
- Olvidar que
filter()no modifica la colección original, sino que produce un nuevo Stream filtrado. - Encadenar múltiples
.filter()seguidos cuando se podría combinar todo en un únicoPredicatecon.and(), afectando ligeramente a la legibilidad (aunque no al resultado).
Preguntas frecuentes
¿Puedo combinar más de dos Predicates?
Sí, and(), or() y negate() se pueden encadenar tantas veces como sea necesario.
¿filter() modifica la lista original? No, siempre devuelve un nuevo Stream; la colección de origen permanece intacta.
¿Cuál es la diferencia entre usar varios .filter() seguidos o un solo Predicate combinado?
El resultado final es el mismo, pero un único Predicate combinado con .and() suele ser más legible y reutilizable que encadenar múltiples llamadas a filter().
Siguiente paso
Dominar filter() y Predicate es el complemento natural a los conceptos generales de la API de Streams en Java.
Newsletter Semanal de Java
Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.
🔒 Sin spam. Cancela cuando quieras.