Cómo filtrar una colección utilizando la API de Stream en Java

JavaBeginner
Practicar Ahora

Introducción

La API de Stream de Java ofrece una forma poderosa y versátil de trabajar con colecciones de datos. En este tutorial, exploraremos cómo aprovechar la API de Stream para filtrar colecciones, lo que te permitirá extraer de manera eficiente los datos que necesitas. Ya seas un principiante en Java o un desarrollador experimentado, esta guía te proporcionará el conocimiento y las habilidades necesarias para optimizar tus tareas de procesamiento de datos.

Introducción a la API de Stream de Java

La API de Stream de Java es una característica poderosa introducida en Java 8 que te permite procesar colecciones de datos de manera declarativa y funcional. Los streams ofrecen una forma de realizar diversas operaciones en los datos, como filtrado, mapeo, ordenación y reducción, sin necesidad de una iteración explícita o un estado mutable.

Comprendiendo los Streams

Un stream es una secuencia de elementos que admite diversas operaciones para realizar cálculos en esos elementos. Los streams se pueden crear a partir de diversas fuentes de datos, como colecciones, matrices o incluso recursos de E/S. Los streams proporcionan una API fluida que te permite encadenar múltiples operaciones, lo que hace que tu código sea más expresivo y legible.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
stream.filter(n -> n % 2 == 0)
    .map(n -> n * 2)
    .forEach(System.out::println);

En el ejemplo anterior, creamos un stream a partir de una lista de enteros, filtramos los números pares, los mapeamos a sus valores duplicados y luego imprimimos los resultados.

Ventajas de usar Streams

  1. Programación Declarativa: Los streams te permiten expresar tu intención de manera más declarativa, centrándote en lo que quieres lograr en lugar de cómo lograrlo.
  2. Paralelismo: Los streams se pueden paralelizar fácilmente, lo que te permite aprovechar los procesadores multinúcleo y mejorar el rendimiento.
  3. Perezosidad (Laziness): Los streams son perezosos, lo que significa que las operaciones solo se realizan cuando se necesita el resultado final, lo que puede mejorar la eficiencia.
  4. API Fluida: La API de stream proporciona una forma fluida y expresiva de encadenar múltiples operaciones, lo que hace que tu código sea más legible y mantenible.

Explorando las Operaciones de Stream

Los streams ofrecen una amplia gama de operaciones que puedes utilizar para manipular tus datos. Algunas de las operaciones más comunes incluyen:

  • Filtrado (Filtering): Seleccionar elementos basados en un predicado dado.
  • Mapeo (Mapping): Transformar elementos utilizando una función.
  • Ordenación (Sorting): Ordenar elementos basados en un comparador.
  • Reducción (Reducing): Combinar elementos en un solo resultado.
  • Recopilación (Collecting): Recopilar los resultados en una nueva estructura de datos.

En las siguientes secciones, profundizaremos en el uso específico de la API de Stream para filtrar colecciones.

Filtrado de Colecciones con Streams

El filtrado es una de las operaciones más comunes realizadas en colecciones utilizando la API de Stream de Java. El filtrado te permite seleccionar un subconjunto de elementos de una colección basado en un predicado dado.

Aplicando Filtros

El método filter() se utiliza para aplicar un filtro a un stream. Este método toma una expresión lambda o una referencia a método como argumento, que representa la condición de filtrado.

List<String> names = Arrays.asList("John", "Jane", "Bob", "Alice");
Stream<String> filteredNames = names.stream()
                                   .filter(name -> name.startsWith("J"));
filteredNames.forEach(System.out::println);

En el ejemplo anterior, creamos un stream a partir de una lista de nombres y luego usamos el método filter() para seleccionar solo los nombres que empiecen con la letra "J".

Encadenando Múltiples Filtros

Puedes encadenar múltiples llamadas a filter() para aplicar múltiples condiciones de filtrado a un stream.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> filteredNumbers = numbers.stream()
                                        .filter(n -> n % 2 == 0)
                                        .filter(n -> n > 5);
filteredNumbers.forEach(System.out::println);

En este ejemplo, primero filtramos los números para seleccionar solo los números pares, y luego aplicamos un segundo filtro para seleccionar solo los números pares mayores que 5.

Combinando Filtros con Otras Operaciones

El filtrado se puede combinar con otras operaciones de stream, como mapeo, ordenación y reducción, para crear transformaciones de datos más complejas.

List<Person> persons = Arrays.asList(
    new Person("John", 30),
    new Person("Jane", 25),
    new Person("Bob", 35),
    new Person("Alice", 28)
);

List<String> youngAdultNames = persons.stream()
                                     .filter(p -> p.getAge() >= 18 && p.getAge() < 30)
                                     .map(Person::getName)
                                     .collect(Collectors.toList());

System.out.println(youngAdultNames);

En este ejemplo, primero filtramos la lista de objetos Person para seleccionar solo los jóvenes adultos (entre 18 y 30 años), luego mapeamos la propiedad Name de cada objeto Person y, finalmente, recopilamos los resultados en una nueva lista.

Al entender el poder del filtrado con la API de Stream de Java, puedes manipular y procesar eficazmente tus colecciones de datos. En la siguiente sección, exploraremos más ejemplos prácticos de aplicación de filtros.

Aplicación de Filtros: Ejemplos Prácticos

Ahora que tienes una sólida comprensión de los conceptos básicos del filtrado con la API de Stream de Java, exploremos algunos ejemplos prácticos para consolidar tu conocimiento.

Filtrado de Cadenas

Supongamos que tienes una lista de cadenas y quieres seleccionar solo aquellas que tienen una longitud específica o que empiecen con un determinado carácter.

List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");

// Filtrar palabras con longitud mayor a 5 caracteres
List<String> longWords = words.stream()
                              .filter(word -> word.length() > 5)
                              .collect(Collectors.toList());
System.out.println(longWords); // [banana, cherry, elderberry]

// Filtrar palabras que empiecen con 'a'
List<String> wordsStartingWithA = words.stream()
                                      .filter(word -> word.startsWith("a"))
                                      .collect(Collectors.toList());
System.out.println(wordsStartingWithA); // [apple, date]

Filtrado de Números

También puedes usar la API de Stream para filtrar datos numéricos, como enteros o dobles.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Filtrar números pares
List<Integer> evenNumbers = numbers.stream()
                                  .filter(n -> n % 2 == 0)
                                  .collect(Collectors.toList());
System.out.println(evenNumbers); // [2, 4, 6, 8, 10]

// Filtrar números mayores a 5
List<Integer> numbersGreaterThan5 = numbers.stream()
                                          .filter(n -> n > 5)
                                          .collect(Collectors.toList());
System.out.println(numbersGreaterThan5); // [6, 7, 8, 9, 10]

Filtrado de Objetos Personalizados

También puedes aplicar filtros a colecciones de objetos personalizados, como objetos Person.

class Person {
    private String name;
    private int age;

    // Getters, setters, and constructor
}

List<Person> persons = Arrays.asList(
    new Person("John", 30),
    new Person("Jane", 25),
    new Person("Bob", 35),
    new Person("Alice", 28)
);

// Filtrar personas mayores de 30 años
List<Person> personsOlderThan30 = persons.stream()
                                        .filter(p -> p.getAge() > 30)
                                        .collect(Collectors.toList());
System.out.println(personsOlderThan30); // [Person(name=Bob, age=35)]

// Filtrar personas con nombres que empiecen con 'J'
List<Person> personsWithNameStartingWithJ = persons.stream()
                                                  .filter(p -> p.getName().startsWith("J"))
                                                  .collect(Collectors.toList());
System.out.println(personsWithNameStartingWithJ); // [Person(name=John, age=0), Person(name=Jane, age=25)]

Al explorar estos ejemplos prácticos, ahora deberías tener una mejor comprensión de cómo usar eficazmente el método filter() para manipular tus colecciones en Java.

Resumen

Al final de este tutorial, tendrás una sólida comprensión de cómo usar la API de Stream de Java para filtrar colecciones. Aprenderás sobre los diversos métodos de filtrado disponibles, como filter(), distinct() y limit(), y cómo aplicarlos a tus casos de uso específicos. Con el conocimiento adquirido, podrás escribir código Java más eficiente y mantenible, optimizar tus flujos de trabajo de procesamiento de datos y aprovechar todo el potencial de la API de Stream.