Java
Tutorial Java: Transformación
Aprende a usar las operaciones distinct, sorted, limit, skip y peek en Streams de Java para procesar datos de forma eficiente y funcional.
Aprende Java y certifícatedistinct()
La operación distinct() es una operación intermedia en los Streams de Java que permite eliminar elementos duplicados de una secuencia. Esta operación es especialmente útil cuando trabajamos con colecciones que pueden contener valores repetidos y necesitamos obtener solo valores únicos.
Cuando aplicamos distinct()
a un Stream, este método crea un nuevo Stream que contiene los elementos del Stream original pero sin duplicados. Para determinar si dos elementos son iguales, distinct()
utiliza el método equals()
de los objetos, por lo que es importante que este método esté correctamente implementado en las clases de los objetos que procesamos.
Funcionamiento básico
La sintaxis para usar distinct()
es muy sencilla:
Stream<T> distinct()
Veamos un ejemplo simple con números enteros:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctExample {
public static void main(String[] args) {
List<Integer> numeros = Arrays.asList(1, 2, 3, 2, 1, 4, 5, 4, 6);
List<Integer> numerosUnicos = numeros.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Lista original: " + numeros);
System.out.println("Lista sin duplicados: " + numerosUnicos);
}
}
La salida de este programa sería:
Lista original: [1, 2, 3, 2, 1, 4, 5, 4, 6]
Lista sin duplicados: [1, 2, 3, 4, 5, 6]
Como podemos observar, distinct()
ha eliminado los números duplicados, dejando solo una ocurrencia de cada valor.
Uso con objetos personalizados
Cuando trabajamos con objetos personalizados, es fundamental asegurarnos de que los métodos equals()
y hashCode()
estén correctamente implementados para que distinct()
funcione adecuadamente:
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
class Persona {
private String nombre;
private int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Persona persona = (Persona) o;
return edad == persona.edad && Objects.equals(nombre, persona.nombre);
}
@Override
public int hashCode() {
return Objects.hash(nombre, edad);
}
@Override
public String toString() {
return "Persona{" + "nombre='" + nombre + "', edad=" + edad + '}';
}
}
public class DistinctObjectExample {
public static void main(String[] args) {
List<Persona> personas = Arrays.asList(
new Persona("Ana", 25),
new Persona("Carlos", 30),
new Persona("Ana", 25), // Duplicado
new Persona("Luis", 28),
new Persona("Carlos", 30) // Duplicado
);
List<Persona> personasUnicas = personas.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Lista original: " + personas.size() + " personas");
personas.forEach(System.out::println);
System.out.println("\nLista sin duplicados: " + personasUnicas.size() + " personas");
personasUnicas.forEach(System.out::println);
}
}
En este ejemplo, dos objetos Persona
se consideran iguales si tienen el mismo nombre y edad. La salida mostraría:
Lista original: 5 personas
Persona{nombre='Ana', edad=25}
Persona{nombre='Carlos', edad=30}
Persona{nombre='Ana', edad=25}
Persona{nombre='Luis', edad=28}
Persona{nombre='Carlos', edad=30}
Lista sin duplicados: 3 personas
Persona{nombre='Ana', edad=25}
Persona{nombre='Carlos', edad=30}
Persona{nombre='Luis', edad=28}
Casos de uso prácticos
Filtrado de valores únicos en una base de datos
Uno de los usos más comunes de distinct()
es cuando necesitamos obtener valores únicos de una colección que representa datos de una base de datos:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctCategoriesExample {
public static void main(String[] args) {
// Simulación de productos con categorías
List<Producto> productos = Arrays.asList(
new Producto("Laptop", "Electrónica"),
new Producto("Smartphone", "Electrónica"),
new Producto("Camiseta", "Ropa"),
new Producto("Pantalón", "Ropa"),
new Producto("Tablet", "Electrónica")
);
// Obtener categorías únicas
List<String> categoriasUnicas = productos.stream()
.map(Producto::getCategoria)
.distinct()
.collect(Collectors.toList());
System.out.println("Categorías disponibles: " + categoriasUnicas);
}
static class Producto {
private String nombre;
private String categoria;
public Producto(String nombre, String categoria) {
this.nombre = nombre;
this.categoria = categoria;
}
public String getNombre() {
return nombre;
}
public String getCategoria() {
return categoria;
}
}
}
La salida sería:
Categorías disponibles: [Electrónica, Ropa]
Eliminación de palabras duplicadas en un texto
Otro caso de uso común es procesar texto para obtener palabras únicas:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctWordsExample {
public static void main(String[] args) {
String texto = "Java es un lenguaje de programación. Java es versátil y potente.";
// Dividir el texto en palabras y eliminar duplicados
List<String> palabrasUnicas = Arrays.stream(texto.toLowerCase().split("\\W+"))
.filter(palabra -> !palabra.isEmpty())
.distinct()
.collect(Collectors.toList());
System.out.println("Palabras únicas: " + palabrasUnicas);
}
}
La salida sería:
Palabras únicas: [java, es, un, lenguaje, de, programación, versátil, y, potente]
Consideraciones de rendimiento
Es importante tener en cuenta que distinct()
puede tener un impacto en el rendimiento, especialmente con grandes volúmenes de datos, ya que necesita mantener un registro de los elementos ya vistos. Para colecciones muy grandes, podría ser más eficiente usar otras estructuras como Set
directamente:
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DistinctPerformanceExample {
public static void main(String[] args) {
List<Integer> numerosGrandes = Arrays.asList(1, 2, 3, 2, 1, 4, 5, 4, 6);
// Usando distinct()
long tiempoInicio = System.nanoTime();
long cantidadDistinct = numerosGrandes.stream()
.distinct()
.count();
long tiempoFin = System.nanoTime();
System.out.println("Tiempo con distinct(): " + (tiempoFin - tiempoInicio) + " ns");
// Usando HashSet
tiempoInicio = System.nanoTime();
Set<Integer> conjuntoNumeros = new HashSet<>(numerosGrandes);
long cantidadSet = conjuntoNumeros.size();
tiempoFin = System.nanoTime();
System.out.println("Tiempo con HashSet: " + (tiempoFin - tiempoInicio) + " ns");
System.out.println("Cantidad de elementos únicos: " + cantidadDistinct);
}
}
Combinación con otras operaciones
La operación distinct()
se puede combinar fácilmente con otras operaciones de Stream para crear flujos de procesamiento más complejos:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctCombinedExample {
public static void main(String[] args) {
List<String> palabras = Arrays.asList("Java", "Python", "C++", "Java", "JavaScript", "Python", "Kotlin");
// Obtener palabras únicas que empiezan con 'J' y ordenarlas
List<String> resultado = palabras.stream()
.filter(p -> p.startsWith("J"))
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("Palabras que empiezan con J (sin duplicados): " + resultado);
}
}
La salida sería:
Palabras que empiezan con J (sin duplicados): [Java, JavaScript]
La operación distinct()
es una herramienta fundamental en la programación funcional con Java que nos permite trabajar con conjuntos de datos únicos de manera elegante y expresiva, mejorando la legibilidad y mantenibilidad de nuestro código.
sorted()
La operación sorted() es una operación intermedia en los Streams de Java que permite ordenar los elementos de un flujo según un criterio específico. Esta funcionalidad es esencial cuando necesitamos procesar datos en un orden determinado antes de realizar operaciones posteriores.
En su forma más básica, sorted()
ordena los elementos utilizando su orden natural (implementado a través de la interfaz Comparable
). También existe una sobrecarga que acepta un Comparator
para definir criterios de ordenación personalizados.
Ordenación natural con sorted()
Para elementos que implementan la interfaz Comparable
, podemos usar sorted()
sin argumentos:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortedExample {
public static void main(String[] args) {
List<Integer> numeros = Arrays.asList(5, 3, 8, 1, 9, 4, 7);
List<Integer> numerosOrdenados = numeros.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Lista original: " + numeros);
System.out.println("Lista ordenada: " + numerosOrdenados);
}
}
La salida mostrará:
Lista original: [5, 3, 8, 1, 9, 4, 7]
Lista ordenada: [1, 3, 4, 5, 7, 8, 9]
Este método funciona con cualquier tipo que implemente Comparable
, como String
, Integer
, LocalDate
, etc.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortedStringsExample {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Carlos", "Ana", "Zoe", "Miguel", "Elena");
List<String> nombresOrdenados = nombres.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Nombres ordenados alfabéticamente: " + nombresOrdenados);
}
}
Resultado:
Nombres ordenados alfabéticamente: [Ana, Carlos, Elena, Miguel, Zoe]
Ordenación personalizada con Comparator
Para criterios de ordenación más complejos o para tipos que no implementan Comparable
, podemos usar la versión sobrecargada de sorted()
que acepta un Comparator
:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortedComparatorExample {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Carlos", "Ana", "Zoe", "Miguel", "Elena");
// Ordenar por longitud de cadena (de menor a mayor)
List<String> porLongitud = nombres.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
System.out.println("Ordenados por longitud: " + porLongitud);
// Ordenar por longitud de cadena (de mayor a menor)
List<String> porLongitudInversa = nombres.stream()
.sorted(Comparator.comparing(String::length).reversed())
.collect(Collectors.toList());
System.out.println("Ordenados por longitud inversa: " + porLongitudInversa);
}
}
Salida:
Ordenados por longitud: [Ana, Zoe, Elena, Carlos, Miguel]
Ordenados por longitud inversa: [Carlos, Miguel, Elena, Ana, Zoe]
Ordenación de objetos personalizados
Para objetos personalizados, es común definir criterios de ordenación específicos:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Producto {
private String nombre;
private double precio;
private int stock;
public Producto(String nombre, double precio, int stock) {
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
public int getStock() { return stock; }
@Override
public String toString() {
return nombre + " ($" + precio + ", stock: " + stock + ")";
}
}
public class SortedProductsExample {
public static void main(String[] args) {
List<Producto> productos = Arrays.asList(
new Producto("Laptop", 999.99, 10),
new Producto("Smartphone", 599.99, 15),
new Producto("Tablet", 299.99, 5),
new Producto("Auriculares", 89.99, 30),
new Producto("Monitor", 249.99, 8)
);
// Ordenar por precio (de menor a mayor)
List<Producto> porPrecio = productos.stream()
.sorted(Comparator.comparing(Producto::getPrecio))
.collect(Collectors.toList());
System.out.println("Productos ordenados por precio:");
porPrecio.forEach(System.out::println);
// Ordenar por stock (de mayor a menor)
List<Producto> porStock = productos.stream()
.sorted(Comparator.comparing(Producto::getStock).reversed())
.collect(Collectors.toList());
System.out.println("\nProductos ordenados por stock (mayor a menor):");
porStock.forEach(System.out::println);
}
}
Ordenación con múltiples criterios
También podemos combinar varios criterios de ordenación utilizando los métodos thenComparing()
:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Empleado {
private String departamento;
private String nombre;
private int edad;
public Empleado(String departamento, String nombre, int edad) {
this.departamento = departamento;
this.nombre = nombre;
this.edad = edad;
}
public String getDepartamento() { return departamento; }
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
@Override
public String toString() {
return departamento + " - " + nombre + " (" + edad + " años)";
}
}
public class MultiCriteriaSortExample {
public static void main(String[] args) {
List<Empleado> empleados = Arrays.asList(
new Empleado("IT", "Carlos", 35),
new Empleado("RRHH", "Ana", 28),
new Empleado("IT", "Miguel", 42),
new Empleado("Marketing", "Elena", 28),
new Empleado("RRHH", "Luis", 35),
new Empleado("Marketing", "Sara", 31)
);
// Ordenar por departamento, luego por edad y finalmente por nombre
List<Empleado> ordenados = empleados.stream()
.sorted(
Comparator.comparing(Empleado::getDepartamento)
.thenComparing(Empleado::getEdad)
.thenComparing(Empleado::getNombre)
)
.collect(Collectors.toList());
System.out.println("Empleados ordenados por departamento, edad y nombre:");
ordenados.forEach(System.out::println);
}
}
Salida:
Empleados ordenados por departamento, edad y nombre:
IT - Carlos (35 años)
IT - Miguel (42 años)
Marketing - Elena (28 años)
Marketing - Sara (31 años)
RRHH - Ana (28 años)
RRHH - Luis (35 años)
Combinación con otras operaciones de Stream
La operación sorted()
se integra perfectamente con otras operaciones de Stream para crear flujos de procesamiento más complejos:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortedCombinedExample {
public static void main(String[] args) {
List<String> palabras = Arrays.asList(
"Java", "Python", "JavaScript", "C#", "Kotlin", "Swift", "Go", "Rust"
);
// Filtrar palabras con más de 4 letras, ordenarlas por longitud y luego alfabéticamente
List<String> resultado = palabras.stream()
.filter(p -> p.length() > 4)
.sorted(
Comparator.comparing(String::length)
.thenComparing(String::toString)
)
.collect(Collectors.toList());
System.out.println("Palabras filtradas y ordenadas: " + resultado);
}
}
Salida:
Palabras filtradas y ordenadas: [Kotlin, Python, Swift, JavaScript]
Consideraciones de rendimiento
Es importante tener en cuenta que la operación sorted()
requiere cargar todos los elementos en memoria para realizar la ordenación, lo que puede afectar al rendimiento con grandes volúmenes de datos. Además, no es una operación paralela eficiente, ya que requiere reunir todos los elementos para ordenarlos.
import java.util.stream.Stream;
public class SortedPerformanceExample {
public static void main(String[] args) {
// Crear un stream de 10 millones de números aleatorios
long inicio = System.currentTimeMillis();
long count = Stream.generate(Math::random)
.limit(1_000_000)
.sorted()
.count();
long fin = System.currentTimeMillis();
System.out.println("Tiempo para ordenar 1 millón de números: " + (fin - inicio) + " ms");
System.out.println("Cantidad de elementos: " + count);
}
}
Casos de uso prácticos
Análisis de datos
La ordenación es fundamental en el análisis de datos para identificar patrones, valores extremos o simplemente presentar información de manera organizada:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Venta {
private String producto;
private double importe;
private String region;
public Venta(String producto, double importe, String region) {
this.producto = producto;
this.importe = importe;
this.region = region;
}
public String getProducto() { return producto; }
public double getImporte() { return importe; }
public String getRegion() { return region; }
}
public class DataAnalysisExample {
public static void main(String[] args) {
List<Venta> ventas = Arrays.asList(
new Venta("Laptop", 1200, "Norte"),
new Venta("Smartphone", 800, "Sur"),
new Venta("Tablet", 500, "Este"),
new Venta("Laptop", 1100, "Oeste"),
new Venta("Smartphone", 750, "Norte"),
new Venta("Monitor", 300, "Sur")
);
// Agrupar por producto y ordenar por importe total
Map<String, Double> ventasPorProducto = ventas.stream()
.collect(Collectors.groupingBy(
Venta::getProducto,
Collectors.summingDouble(Venta::getImporte)
));
// Ordenar el mapa por valor (importe total)
ventasPorProducto.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEach(entry -> System.out.println(
entry.getKey() + ": $" + entry.getValue()
));
}
}
Paginación de resultados
En aplicaciones web, es común necesitar ordenar y paginar resultados:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Producto {
private String nombre;
private double precio;
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
@Override
public String toString() {
return nombre + " ($" + precio + ")";
}
}
public class PaginationExample {
public static void main(String[] args) {
List<Producto> catalogo = Arrays.asList(
new Producto("Laptop", 999.99),
new Producto("Smartphone", 599.99),
new Producto("Tablet", 299.99),
new Producto("Auriculares", 89.99),
new Producto("Monitor", 249.99),
new Producto("Teclado", 49.99),
new Producto("Ratón", 29.99),
new Producto("Impresora", 199.99),
new Producto("Altavoces", 79.99),
new Producto("Webcam", 59.99)
);
// Parámetros de paginación
int pagina = 2; // Segunda página
int elementosPorPagina = 3;
// Ordenar por precio y obtener la página solicitada
List<Producto> resultadosPaginados = catalogo.stream()
.sorted(Comparator.comparing(Producto::getPrecio))
.skip((pagina - 1) * elementosPorPagina)
.limit(elementosPorPagina)
.collect(Collectors.toList());
System.out.println("Página " + pagina + " (ordenados por precio):");
resultadosPaginados.forEach(System.out::println);
}
}
La operación sorted()
es una herramienta fundamental en la programación funcional con Java que nos permite organizar datos de manera flexible y expresiva, facilitando su posterior procesamiento y análisis.
limit(), skip()
Las operaciones limit() y skip() son operaciones intermedias en los Streams de Java que permiten controlar la cantidad de elementos que fluyen a través de un Stream. Estas operaciones son fundamentales cuando necesitamos trabajar con subconjuntos específicos de datos o implementar paginación en nuestras aplicaciones.
Ambas operaciones son complementarias: mientras limit()
restringe el número máximo de elementos, skip()
omite un número determinado de elementos desde el inicio del Stream.
Funcionamiento básico de limit()
La operación limit()
toma un parámetro long
que especifica el número máximo de elementos que debe contener el Stream resultante:
Stream<T> limit(long maxSize)
Veamos un ejemplo sencillo:
import java.util.stream.Stream;
import java.util.List;
import java.util.stream.Collectors;
public class LimitExample {
public static void main(String[] args) {
// Crear un stream de números del 1 al 10
Stream<Integer> numeros = Stream.iterate(1, n -> n + 1).limit(10);
List<Integer> primerosCinco = numeros.limit(5)
.collect(Collectors.toList());
System.out.println("Primeros cinco números: " + primerosCinco);
}
}
La salida sería:
Primeros cinco números: [1, 2, 3, 4, 5]
En este ejemplo, primero creamos un Stream infinito de números enteros comenzando desde 1, pero lo limitamos a 10 elementos. Luego aplicamos limit(5)
para obtener solo los primeros 5 elementos.
Funcionamiento básico de skip()
La operación skip()
también toma un parámetro long
que indica cuántos elementos se deben omitir desde el inicio del Stream:
Stream<T> skip(long n)
Veamos un ejemplo:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SkipExample {
public static void main(String[] args) {
List<String> frutas = Arrays.asList("Manzana", "Banana", "Cereza", "Durazno", "Fresa");
List<String> frutasSalteadas = frutas.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println("Frutas originales: " + frutas);
System.out.println("Frutas después de saltar 2: " + frutasSalteadas);
}
}
La salida sería:
Frutas originales: [Manzana, Banana, Cereza, Durazno, Fresa]
Frutas después de saltar 2: [Cereza, Durazno, Fresa]
En este caso, skip(2)
omite los dos primeros elementos ("Manzana" y "Banana") y devuelve un Stream con los elementos restantes.
Combinando limit() y skip()
Una de las aplicaciones más comunes de estas operaciones es cuando se utilizan juntas para implementar paginación:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PaginationExample {
public static void main(String[] args) {
List<String> elementos = Arrays.asList(
"Elemento 1", "Elemento 2", "Elemento 3", "Elemento 4",
"Elemento 5", "Elemento 6", "Elemento 7", "Elemento 8",
"Elemento 9", "Elemento 10", "Elemento 11", "Elemento 12"
);
int paginaActual = 2; // Segunda página
int elementosPorPagina = 4;
List<String> paginaResultado = elementos.stream()
.skip((paginaActual - 1) * elementosPorPagina)
.limit(elementosPorPagina)
.collect(Collectors.toList());
System.out.println("Página " + paginaActual + ": " + paginaResultado);
}
}
La salida sería:
Página 2: [Elemento 5, Elemento 6, Elemento 7, Elemento 8]
Este patrón es extremadamente útil en aplicaciones web donde necesitamos mostrar resultados paginados.
Casos de uso prácticos
Procesamiento de grandes conjuntos de datos
Cuando trabajamos con grandes volúmenes de datos, podemos usar limit()
para procesar solo una muestra:
import java.util.stream.Stream;
import java.util.Random;
public class BigDataSampleExample {
public static void main(String[] args) {
Random random = new Random();
// Simular un stream de datos muy grande
Stream<Integer> datosGrandes = Stream.generate(() -> random.nextInt(1000));
// Procesar solo una muestra de 100 elementos
double promedio = datosGrandes.limit(100)
.mapToInt(Integer::intValue)
.average()
.orElse(0);
System.out.println("Promedio de la muestra: " + promedio);
}
}
Implementación de "Top N" resultados
Podemos combinar limit()
con otras operaciones como sorted()
para obtener los N mejores resultados:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Producto {
private String nombre;
private double precio;
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
@Override
public String toString() {
return nombre + " ($" + precio + ")";
}
}
public class TopNExample {
public static void main(String[] args) {
List<Producto> productos = Arrays.asList(
new Producto("Laptop", 999.99),
new Producto("Smartphone", 599.99),
new Producto("Tablet", 299.99),
new Producto("Auriculares", 89.99),
new Producto("Monitor", 249.99)
);
// Obtener los 3 productos más baratos
List<Producto> top3MasBaratos = productos.stream()
.sorted(Comparator.comparing(Producto::getPrecio))
.limit(3)
.collect(Collectors.toList());
System.out.println("Top 3 productos más baratos:");
top3MasBaratos.forEach(System.out::println);
}
}
La salida sería:
Top 3 productos más baratos:
Auriculares ($89.99)
Monitor ($249.99)
Tablet ($299.99)
Omitir elementos no deseados
La operación skip()
es útil cuando queremos omitir ciertos elementos, como encabezados en archivos CSV:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CsvProcessingExample {
public static void main(String[] args) {
// Simular líneas de un archivo CSV
List<String> lineasCsv = Arrays.asList(
"id,nombre,edad", // Encabezado
"1,Ana,28",
"2,Carlos,35",
"3,Elena,42",
"4,Miguel,31"
);
// Procesar el CSV omitiendo la línea de encabezado
List<String> datosProcesados = lineasCsv.stream()
.skip(1) // Omitir encabezado
.map(linea -> {
String[] partes = linea.split(",");
return "Usuario: " + partes[1] + " (" + partes[2] + " años)";
})
.collect(Collectors.toList());
datosProcesados.forEach(System.out::println);
}
}
La salida sería:
Usuario: Ana (28 años)
Usuario: Carlos (35 años)
Usuario: Elena (42 años)
Usuario: Miguel (31 años)
Consideraciones de rendimiento
Tanto limit()
como skip()
son operaciones de corto circuito, lo que significa que pueden optimizar el procesamiento al evitar operaciones innecesarias:
import java.util.stream.IntStream;
public class ShortCircuitExample {
public static void main(String[] args) {
long inicio = System.currentTimeMillis();
// Sin limit(), esto procesaría todos los números hasta 1.000.000
int suma = IntStream.rangeClosed(1, 1_000_000)
.map(n -> {
// Simulamos una operación costosa
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return n * 2;
})
.limit(10) // Solo necesitamos 10 elementos
.sum();
long fin = System.currentTimeMillis();
System.out.println("Suma de los primeros 10 números procesados: " + suma);
System.out.println("Tiempo de ejecución: " + (fin - inicio) + " ms");
}
}
En este ejemplo, aunque el Stream contiene un millón de elementos, la operación limit(10)
hace que solo se procesen los primeros 10, lo que mejora significativamente el rendimiento.
Comportamiento con Streams paralelos
Cuando trabajamos con Streams paralelos, es importante tener en cuenta que limit()
y skip()
pueden afectar la paralelización:
import java.util.stream.IntStream;
import java.util.List;
import java.util.stream.Collectors;
public class ParallelStreamExample {
public static void main(String[] args) {
// Con un stream secuencial, los resultados son predecibles
List<Integer> resultadoSecuencial = IntStream.range(0, 20)
.boxed()
.skip(5)
.limit(10)
.collect(Collectors.toList());
System.out.println("Resultado secuencial: " + resultadoSecuencial);
// Con un stream paralelo, el orden puede variar
List<Integer> resultadoParalelo = IntStream.range(0, 20)
.parallel()
.boxed()
.skip(5)
.limit(10)
.collect(Collectors.toList());
System.out.println("Resultado paralelo: " + resultadoParalelo);
}
}
En Streams paralelos, skip()
y limit()
pueden no comportarse exactamente como esperamos en términos de qué elementos específicos se incluyen, aunque el número total de elementos será correcto.
Aplicaciones en el mundo real
Análisis de datos por lotes
En análisis de datos, a menudo procesamos grandes conjuntos por lotes:
import java.util.stream.Stream;
import java.util.function.Consumer;
public class BatchProcessingExample {
public static void main(String[] args) {
// Simulamos un stream de datos grande
Stream<Integer> datosCompletos = Stream.iterate(1, n -> n + 1).limit(1000);
// Tamaño del lote
int tamañoLote = 100;
// Procesamos por lotes de 100 elementos
for (int i = 0; i < 10; i++) {
int loteActual = i + 1;
Consumer<Integer> procesador = dato -> {
// Aquí iría la lógica de procesamiento
// Solo imprimimos para el ejemplo
if (dato % 50 == 0) {
System.out.println("Procesando dato: " + dato);
}
};
System.out.println("Procesando lote " + loteActual + "...");
datosCompletos
.skip(i * tamañoLote)
.limit(tamañoLote)
.forEach(procesador);
}
}
}
Implementación de API REST con paginación
En aplicaciones web, es común implementar endpoints que devuelven resultados paginados:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Usuario {
private int id;
private String nombre;
public Usuario(int id, String nombre) {
this.id = id;
this.nombre = nombre;
}
public int getId() { return id; }
public String getNombre() { return nombre; }
@Override
public String toString() {
return "Usuario{id=" + id + ", nombre='" + nombre + "'}";
}
}
public class RestApiPaginationExample {
public static void main(String[] args) {
// Simulamos una base de datos de usuarios
List<Usuario> todosLosUsuarios = Arrays.asList(
new Usuario(1, "Ana"),
new Usuario(2, "Carlos"),
new Usuario(3, "Elena"),
new Usuario(4, "Miguel"),
new Usuario(5, "Laura"),
new Usuario(6, "Pedro"),
new Usuario(7, "Sofía"),
new Usuario(8, "Javier")
);
// Parámetros de la solicitud
int pagina = 2;
int tamaño = 3;
// Procesamiento de la solicitud
List<Usuario> usuariosPaginados = todosLosUsuarios.stream()
.skip((pagina - 1) * tamaño)
.limit(tamaño)
.collect(Collectors.toList());
// Creamos la respuesta
Map<String, Object> respuesta = Map.of(
"pagina", pagina,
"tamaño", tamaño,
"total", todosLosUsuarios.size(),
"datos", usuariosPaginados
);
// Simulamos la respuesta JSON
System.out.println("Respuesta API:");
System.out.println(" pagina: " + respuesta.get("pagina"));
System.out.println(" tamaño: " + respuesta.get("tamaño"));
System.out.println(" total: " + respuesta.get("total"));
System.out.println(" datos: " + respuesta.get("datos"));
}
}
Las operaciones limit()
y skip()
son herramientas esenciales en la programación funcional con Java que nos permiten controlar con precisión el flujo de elementos en un Stream, facilitando la implementación de patrones comunes como paginación, muestreo y procesamiento por lotes.
peek()
La operación peek() es una operación intermedia en los Streams de Java que permite inspeccionar elementos mientras fluyen a través de un Stream, sin modificar el Stream en sí. Esta operación es especialmente útil para depuración y logging durante el procesamiento de datos.
A diferencia de otras operaciones intermedias como map()
o filter()
, peek()
no transforma los elementos ni altera el flujo del Stream; simplemente ejecuta una acción sobre cada elemento y luego lo pasa sin cambios al siguiente paso en la cadena de operaciones.
Sintaxis y funcionamiento básico
La operación peek()
recibe como parámetro un Consumer<T>
, que es una función que acepta un elemento y no devuelve ningún resultado:
Stream<T> peek(Consumer<? super T> action)
Veamos un ejemplo sencillo:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PeekBasicExample {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Ana", "Carlos", "Elena", "Miguel");
List<String> nombresProcesados = nombres.stream()
.peek(nombre -> System.out.println("Procesando: " + nombre))
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Resultado final: " + nombresProcesados);
}
}
La salida sería:
Procesando: Ana
Procesando: Carlos
Procesando: Elena
Procesando: Miguel
Resultado final: [ANA, CARLOS, ELENA, MIGUEL]
En este ejemplo, peek()
nos permite ver cada elemento antes de que sea transformado por la operación map()
.
Uso para depuración
Una de las aplicaciones más comunes de peek()
es la depuración de operaciones en cadena:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PeekDebuggingExample {
public static void main(String[] args) {
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> resultado = numeros.stream()
.peek(n -> System.out.println("Inicial: " + n))
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println("Después de filter: " + n))
.map(n -> n * n)
.peek(n -> System.out.println("Después de map: " + n))
.collect(Collectors.toList());
System.out.println("Resultado final: " + resultado);
}
}
La salida mostraría el flujo completo de transformaciones:
Inicial: 1
Inicial: 2
Después de filter: 2
Después de map: 4
Inicial: 3
Inicial: 4
Después de filter: 4
Después de map: 16
...
Resultado final: [4, 16, 36, 64, 100]
Este tipo de depuración es invaluable para entender cómo se procesan los datos en cada etapa del Stream.
Verificación de efectos secundarios
También podemos usar peek()
para verificar efectos secundarios en objetos mutables:
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Contador {
private int valor = 0;
public void incrementar() {
valor++;
}
public int getValor() {
return valor;
}
}
public class PeekSideEffectExample {
public static void main(String[] args) {
List<String> palabras = Arrays.asList("Java", "es", "un", "lenguaje", "de", "programación");
Contador contador = new Contador();
List<String> palabrasLargas = palabras.stream()
.filter(p -> p.length() > 3)
.peek(p -> {
System.out.println("Palabra larga encontrada: " + p);
contador.incrementar();
})
.collect(Collectors.toList());
System.out.println("Palabras largas: " + palabrasLargas);
System.out.println("Total de palabras largas: " + contador.getValor());
}
}
Aunque este ejemplo funciona, es importante recordar que modificar estado externo desde un Stream (como incrementar un contador) no es una práctica recomendada en programación funcional pura. Para contar elementos, sería mejor usar operaciones terminales como count()
.
Monitoreo de rendimiento
Otra aplicación útil de peek()
es el monitoreo de rendimiento:
import java.util.stream.IntStream;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
public class PeekPerformanceMonitoringExample {
public static void main(String[] args) {
IntStream.rangeClosed(1, 5)
.peek(n -> System.out.println("Iniciando procesamiento de: " + n + " a las " + LocalTime.now()))
.map(n -> {
// Simulamos una operación que toma tiempo
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return n * 2;
})
.peek(n -> System.out.println("Finalizando procesamiento con resultado: " + n + " a las " + LocalTime.now()))
.forEach(n -> {});
}
}
Este código nos permite ver cuánto tiempo toma procesar cada elemento.
Consideraciones importantes
Evaluación perezosa
Es crucial entender que peek()
, como todas las operaciones intermedias, está sujeto a la evaluación perezosa. Esto significa que no se ejecutará a menos que haya una operación terminal al final de la cadena:
import java.util.Arrays;
import java.util.List;
public class PeekLazyEvaluationExample {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Ana", "Carlos", "Elena");
// Este peek() nunca se ejecutará porque no hay operación terminal
nombres.stream()
.peek(nombre -> System.out.println("Este mensaje no se mostrará: " + nombre))
.map(String::toUpperCase);
System.out.println("Fin del programa");
}
}
La salida sería simplemente:
Fin del programa
Para que peek()
se ejecute, necesitamos añadir una operación terminal:
nombres.stream()
.peek(nombre -> System.out.println("Ahora sí se mostrará: " + nombre))
.map(String::toUpperCase)
.count(); // Operación terminal
No confundir con forEach()
Es importante no confundir peek()
con forEach()
. Mientras que peek()
es una operación intermedia que devuelve un Stream, forEach()
es una operación terminal que consume el Stream:
import java.util.Arrays;
import java.util.List;
public class PeekVsForEachExample {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Ana", "Carlos", "Elena");
// peek() con operación terminal
System.out.println("Usando peek():");
nombres.stream()
.peek(nombre -> System.out.println(" Procesando: " + nombre))
.count();
// forEach() como operación terminal
System.out.println("Usando forEach():");
nombres.stream()
.forEach(nombre -> System.out.println(" Procesando: " + nombre));
// Esto NO compilará porque forEach() no devuelve un Stream
// nombres.stream()
// .forEach(nombre -> System.out.println("Procesando: " + nombre))
// .count();
}
}
Casos de uso prácticos
Logging en sistemas de producción
En sistemas de producción, peek()
puede ser útil para registrar información sin interferir con el flujo de datos:
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class PeekLoggingExample {
private static final Logger logger = Logger.getLogger(PeekLoggingExample.class.getName());
public static void main(String[] args) {
List<String> datos = Arrays.asList("dato1", "dato2", "error", "dato3", "fallo", "dato4");
List<String> procesados = datos.stream()
.peek(dato -> {
if (dato.equals("error") || dato.equals("fallo")) {
logger.warning("Encontrado dato problemático: " + dato);
} else {
logger.info("Procesando dato normal: " + dato);
}
})
.filter(dato -> !dato.equals("error") && !dato.equals("fallo"))
.collect(Collectors.toList());
System.out.println("Datos procesados correctamente: " + procesados);
}
}
Validación de datos
Podemos usar peek()
para validar datos antes de procesarlos:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class DatoInvalidoException extends RuntimeException {
public DatoInvalidoException(String mensaje) {
super(mensaje);
}
}
public class PeekValidationExample {
public static void main(String[] args) {
List<String> entradas = Arrays.asList("123", "456", "abc", "789", "xyz");
try {
List<Integer> numeros = entradas.stream()
.peek(entrada -> {
try {
Integer.parseInt(entrada);
} catch (NumberFormatException e) {
throw new DatoInvalidoException("Valor no numérico: " + entrada);
}
})
.map(Integer::parseInt)
.collect(Collectors.toList());
System.out.println("Números procesados: " + numeros);
} catch (DatoInvalidoException e) {
System.err.println("Error de validación: " + e.getMessage());
}
}
}
Análisis estadístico durante el procesamiento
Podemos usar peek()
para recopilar estadísticas mientras procesamos datos:
import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
public class PeekStatisticsExample {
public static void main(String[] args) {
List<Double> valores = Arrays.asList(10.5, 20.3, 30.8, 15.2, 25.7);
DoubleSummaryStatistics stats = new DoubleSummaryStatistics();
List<Double> valoresNormalizados = valores.stream()
.peek(stats::accept) // Recopilamos estadísticas
.map(valor -> valor / stats.getAverage()) // Normalizamos por el promedio
.collect(Collectors.toList());
System.out.println("Estadísticas: " + stats);
System.out.println("Valores normalizados: " + valoresNormalizados);
}
}
La operación peek()
es una herramienta valiosa en la programación funcional con Java que nos permite observar y depurar el flujo de datos en un Stream sin alterarlo, facilitando la comprensión y el mantenimiento de nuestro código.
peek()
La operación peek() es una herramienta intermedia en los Streams de Java que permite examinar elementos durante su procesamiento sin alterar el flujo de datos. A diferencia de otras operaciones como map()
que transforman elementos, peek()
simplemente observa cada elemento, ejecuta una acción sobre él y lo pasa intacto al siguiente paso en la cadena de operaciones.
Esta operación recibe como parámetro un Consumer<T>
, que es una función que acepta un elemento y no devuelve ningún resultado:
Stream<T> peek(Consumer<? super T> action)
Visualización del flujo de datos
El uso más común de peek()
es para visualizar el estado de los elementos mientras atraviesan un pipeline de Stream:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PeekVisualizationExample {
public static void main(String[] args) {
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> resultado = numeros.stream()
.peek(n -> System.out.println("Valor original: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("Después de multiplicar: " + n))
.filter(n -> n > 5)
.peek(n -> System.out.println("Después de filtrar: " + n))
.collect(Collectors.toList());
System.out.println("Resultado final: " + resultado);
}
}
Este código mostrará cada transformación que sufren los elementos, facilitando la comprensión del flujo de datos:
Valor original: 1
Después de multiplicar: 2
Valor original: 2
Después de multiplicar: 4
Valor original: 3
Después de multiplicar: 6
Después de filtrar: 6
Valor original: 4
Después de multiplicar: 8
Después de filtrar: 8
Valor original: 5
Después de multiplicar: 10
Después de filtrar: 10
Resultado final: [6, 8, 10]
Depuración de operaciones complejas
Cuando trabajamos con operaciones encadenadas complejas, peek()
nos ayuda a identificar dónde pueden estar ocurriendo problemas:
import java.util.stream.Stream;
import java.util.List;
import java.util.stream.Collectors;
public class PeekDebuggingComplexExample {
public static void main(String[] args) {
try {
List<Integer> resultado = Stream.of("5", "10", "15", "abc", "20")
.peek(s -> System.out.println("Procesando string: " + s))
.map(s -> {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
System.err.println("Error al convertir: " + s);
throw e;
}
})
.peek(n -> System.out.println("Número convertido: " + n))
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println("Número par encontrado: " + n))
.collect(Collectors.toList());
System.out.println("Resultado: " + resultado);
} catch (Exception e) {
System.err.println("Excepción capturada: " + e.getMessage());
}
}
}
Este ejemplo muestra cómo peek()
puede ayudarnos a identificar exactamente dónde ocurre un error en el procesamiento.
Medición de rendimiento
Podemos usar peek()
para medir el tiempo que toma cada etapa del procesamiento:
import java.util.stream.Stream;
import java.time.Instant;
import java.time.Duration;
import java.util.function.Function;
public class PeekPerformanceMeasurementExample {
public static void main(String[] args) {
// Función para medir tiempo
Function<String, Function<Object, Object>> medirTiempo = etapa -> elemento -> {
Instant inicio = Instant.now();
System.out.println("Iniciando " + etapa + " para: " + elemento);
// Simulamos procesamiento
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Duration duracion = Duration.between(inicio, Instant.now());
System.out.println("Completado " + etapa + " en: " + duracion.toMillis() + "ms");
return elemento;
};
Stream.of(1, 2, 3, 4, 5)
.peek(e -> medirTiempo.apply("etapa 1").apply(e))
.map(n -> n * 2)
.peek(e -> medirTiempo.apply("etapa 2").apply(e))
.forEach(n -> {});
}
}
Evaluación perezosa y peek()
Es fundamental entender que peek()
, como todas las operaciones intermedias, está sujeto a la evaluación perezosa. Esto significa que el código dentro de peek()
no se ejecutará hasta que se encuentre una operación terminal:
import java.util.Arrays;
import java.util.List;
public class PeekLazyExample {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Ana", "Carlos", "Elena");
System.out.println("Antes del stream");
// Este peek() no se ejecutará porque no hay operación terminal
nombres.stream()
.peek(nombre -> System.out.println("Procesando: " + nombre));
System.out.println("Después del stream (sin operación terminal)");
// Ahora añadimos una operación terminal
nombres.stream()
.peek(nombre -> System.out.println("Ahora sí procesando: " + nombre))
.count();
System.out.println("Después del stream (con operación terminal)");
}
}
La salida mostrará que el primer peek()
nunca se ejecuta:
Antes del stream
Después del stream (sin operación terminal)
Ahora sí procesando: Ana
Ahora sí procesando: Carlos
Ahora sí procesando: Elena
Después del stream (con operación terminal)
Diferencia entre peek() y forEach()
Es importante distinguir entre peek()
(operación intermedia) y forEach()
(operación terminal):
import java.util.Arrays;
import java.util.List;
public class PeekVsForEachExample {
public static void main(String[] args) {
List<String> frutas = Arrays.asList("Manzana", "Banana", "Cereza");
// peek() permite continuar el procesamiento
long conteo = frutas.stream()
.peek(fruta -> System.out.println("Peek: " + fruta))
.count();
System.out.println("Conteo: " + conteo);
// forEach() termina el stream
frutas.stream()
.forEach(fruta -> System.out.println("ForEach: " + fruta));
// Esto NO compilará:
// frutas.stream()
// .forEach(fruta -> System.out.println(fruta))
// .count();
}
}
Casos de uso prácticos
Auditoría de operaciones
peek()
es ideal para implementar auditoría sin interferir con la lógica principal:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
public class PeekAuditExample {
public static void main(String[] args) {
List<String> transacciones = Arrays.asList("TX001", "TX002", "TX003");
List<String> procesadas = transacciones.stream()
.peek(tx -> registrarAuditoria("INICIO", tx))
.map(tx -> "Procesado: " + tx)
.peek(tx -> registrarAuditoria("FIN", tx))
.collect(Collectors.toList());
System.out.println("Transacciones procesadas: " + procesadas);
}
private static void registrarAuditoria(String etapa, String datos) {
System.out.println("[" + LocalDateTime.now() + "] " + etapa + ": " + datos);
}
}
Validación de datos en tiempo real
Podemos usar peek()
para validar datos mientras se procesan:
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
public class PeekValidationExample {
public static void main(String[] args) {
List<String> datos = Arrays.asList("100", "200", "abc", "300");
List<String> errores = new ArrayList<>();
List<Integer> validos = datos.stream()
.peek(valor -> {
try {
Integer.parseInt(valor);
} catch (NumberFormatException e) {
errores.add("Valor inválido: " + valor);
}
})
.filter(valor -> {
try {
Integer.parseInt(valor);
return true;
} catch (NumberFormatException e) {
return false;
}
})
.map(Integer::parseInt)
.collect(Collectors.toList());
System.out.println("Valores procesados: " + validos);
System.out.println("Errores encontrados: " + errores);
}
}
Monitoreo de recursos
peek()
puede ayudarnos a monitorear el uso de recursos durante el procesamiento:
import java.util.stream.Stream;
import java.util.concurrent.atomic.AtomicLong;
public class PeekResourceMonitoringExample {
public static void main(String[] args) {
AtomicLong memoriaUsada = new AtomicLong(0);
Stream.of("dato1", "dato2", "dato3", "dato4", "dato5")
.peek(dato -> {
long memoriaActual = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
memoriaUsada.set(memoriaActual);
System.out.println("Procesando: " + dato + " - Memoria usada: " + memoriaActual / 1024 + "KB");
})
.map(String::toUpperCase)
.forEach(dato -> {});
System.out.println("Memoria final usada: " + memoriaUsada.get() / 1024 + "KB");
}
}
Mejores prácticas al usar peek()
- Usar peek() solo para observación: No modificar el estado de objetos externos dentro de
peek()
, ya que esto va contra los principios de la programación funcional.
// Evitar esto:
AtomicInteger contador = new AtomicInteger();
stream.peek(e -> contador.incrementAndGet())...
// Preferir esto:
long contador = stream.count();
- Mantener las operaciones ligeras: Las acciones dentro de
peek()
deben ser rápidas para no afectar el rendimiento.
// Evitar operaciones pesadas
stream.peek(e -> realizarOperacionCostosa(e))...
- Desactivar peek() en producción: Considera implementar un mecanismo para desactivar las operaciones de
peek()
en entornos de producción.
public static <T> Consumer<T> debugPeek(Consumer<T> action) {
return DEBUG_ENABLED ? action : e -> {};
}
// Uso
stream.peek(debugPeek(e -> System.out.println(e)))...
- Combinar con logging estructurado: Para sistemas en producción, integra
peek()
con sistemas de logging adecuados.
import java.util.logging.Logger;
public class PeekLoggingBestPractice {
private static final Logger logger = Logger.getLogger(PeekLoggingBestPractice.class.getName());
public static void main(String[] args) {
Stream.of(1, 2, 3)
.peek(n -> {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Procesando: " + n);
}
})
.forEach(n -> {});
}
}
La operación peek()
es una herramienta valiosa que, cuando se usa correctamente, puede mejorar significativamente la observabilidad y depuración de nuestras aplicaciones basadas en Streams, sin interferir con la lógica principal del procesamiento de datos.
Otros ejercicios de programación de Java
Evalúa tus conocimientos de esta lección Transformación con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Streams: match
Gestión de errores y excepciones
CRUD en Java de modelo Customer sobre un ArrayList
Clases abstractas
Listas
Métodos de la clase String
Streams: reduce()
API java.nio 2
Polimorfismo
Pattern Matching
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Inferencia de tipos con var
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
Tipos de variables
Streams: collect()
Operadores aritméticos
Arrays y matrices
Clases y objetos
Interfaz funcional Consumer
CRUD en Java de modelo Customer sobre un HashMap
Interfaces
Enumeraciones Enums
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Sobrecarga de métodos
CRUD de productos en Java
Clases sealed
Creación de Streams
Records
Encapsulación
Streams: min max
Herencia
Métodos avanzados de la clase String
Funciones
Polimorfismo de tiempo de compilación
Reto sintaxis Java
Conjuntos
Estructuras de control
Recursión
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
Operadores
Variables
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Mapas
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Tipos de datos
Clases abstractas
Instalación
Funciones
Excepciones
Estructuras de control
Herencia de clases
La clase Scanner
Generics
Streams: map()
Funciones y encapsulamiento
Todas las lecciones de Java
Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Java
Introducción Y Entorno
Configuración De Entorno Java
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Recursión
Sintaxis
Arrays Y Matrices
Sintaxis
Excepciones
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Sobrecarga De Métodos
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
La Clase Scanner
Programación Orientada A Objetos
Métodos De La Clase String
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Records
Programación Orientada A Objetos
Pattern Matching
Programación Orientada A Objetos
Inferencia De Tipos Con Var
Programación Orientada A Objetos
Enumeraciones Enums
Programación Orientada A Objetos
Generics
Programación Orientada A Objetos
Clases Sealed
Programación Orientada A Objetos
Listas
Framework Collections
Conjuntos
Framework Collections
Mapas
Framework Collections
Funciones Lambda
Programación Funcional
Interfaz Funcional Consumer
Programación Funcional
Interfaz Funcional Predicate
Programación Funcional
Interfaz Funcional Supplier
Programación Funcional
Interfaz Funcional Function
Programación Funcional
Métodos Referenciados
Programación Funcional
Creación De Streams
Programación Funcional
Operaciones Intermedias Con Streams: Map()
Programación Funcional
Operaciones Intermedias Con Streams: Filter()
Programación Funcional
Operaciones Intermedias Con Streams: Distinct()
Programación Funcional
Operaciones Finales Con Streams: Collect()
Programación Funcional
Operaciones Finales Con Streams: Min Max
Programación Funcional
Operaciones Intermedias Con Streams: Flatmap()
Programación Funcional
Operaciones Intermedias Con Streams: Sorted()
Programación Funcional
Operaciones Finales Con Streams: Reduce()
Programación Funcional
Operaciones Finales Con Streams: Foreach()
Programación Funcional
Operaciones Finales Con Streams: Count()
Programación Funcional
Operaciones Finales Con Streams: Match
Programación Funcional
Api Optional
Programación Funcional
Transformación
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Mapeo
Programación Funcional
Streams Paralelos
Programación Funcional
Agrupación Y Partición
Programación Funcional
Filtrado Y Búsqueda
Programación Funcional
Api Java.nio 2
Entrada Y Salida Io
Fundamentos De Io
Entrada Y Salida Io
Leer Y Escribir Archivos
Entrada Y Salida Io
Httpclient Moderno
Entrada Y Salida Io
Clases De Nio2
Entrada Y Salida Io
Api Java.time
Api Java.time
Localtime
Api Java.time
Localdatetime
Api Java.time
Localdate
Api Java.time
Executorservice
Concurrencia
Virtual Threads (Project Loom)
Concurrencia
Future Y Completablefuture
Concurrencia
Spring Framework
Frameworks Para Java
Micronaut
Frameworks Para Java
Maven
Frameworks Para Java
Gradle
Frameworks Para Java
Lombok Para Java
Frameworks Para Java
Quarkus
Frameworks Para Java
Ecosistema Jakarta Ee De Java
Frameworks Para Java
Introducción A Junit 5
Testing
Certificados de superación de Java
Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el uso de distinct() para eliminar elementos duplicados en Streams.
- Aprender a ordenar elementos con sorted(), incluyendo ordenación natural y personalizada.
- Manejar la selección y omisión de elementos con limit() y skip(), aplicando paginación y muestreo.
- Utilizar peek() para inspeccionar y depurar el flujo de datos en Streams sin modificarlo.
- Reconocer consideraciones de rendimiento y buenas prácticas al usar estas operaciones en flujos de datos grandes.