Java
Tutorial Java: Interfaz funcional Supplier
Aprende la interfaz funcional Supplier en Java 8 para generar valores bajo demanda con ejemplos prácticos y patrones funcionales esenciales.
Aprende Java y certifícateDefinición y propósito de Supplier
La interfaz funcional Supplier<T>
es uno de los componentes fundamentales de la API de programación funcional introducida en Java 8. Esta interfaz representa una operación que no acepta argumentos pero produce un resultado de tipo T
. Su principal característica es que genera valores sin necesidad de recibir datos de entrada, actuando como una fábrica de objetos o un proveedor de valores.
La firma de la interfaz es simple y directa:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
El propósito principal de Supplier
es encapsular la lógica de creación o generación de valores, permitiendo:
- Diferir la creación de objetos hasta que realmente sean necesarios
- Separar la lógica de generación de valores de su consumo
- Proporcionar valores dinámicos que pueden cambiar en cada invocación
- Abstraer la fuente de donde provienen los datos
Esta interfaz resulta especialmente útil en escenarios donde:
- Necesitamos inicialización perezosa (lazy initialization) de objetos
- Queremos generar valores aleatorios o secuenciales
- Debemos proporcionar valores por defecto cuando faltan datos
- Implementamos fábricas de objetos de forma funcional
- Requerimos cálculos costosos que solo deben ejecutarse cuando sea necesario
Veamos un ejemplo básico de cómo se puede implementar un Supplier
:
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
// Supplier que genera un saludo
Supplier<String> greetingSupplier = () -> "¡Hola, programador funcional!";
// Obtener el valor generado por el Supplier
String greeting = greetingSupplier.get();
System.out.println(greeting);
// Supplier que genera un número aleatorio entre 1 y 100
Supplier<Integer> randomNumberSupplier = () -> (int) (Math.random() * 100) + 1;
// Generar y mostrar 5 números aleatorios
for (int i = 0; i < 5; i++) {
System.out.println("Número aleatorio: " + randomNumberSupplier.get());
}
}
}
A diferencia de otras interfaces funcionales como Consumer
o Function
, que procesan datos de entrada, Supplier
se centra exclusivamente en la generación de valores. Esta característica lo convierte en un componente ideal para implementar patrones como:
- Factory Method: para crear instancias de objetos
- Memoization: para almacenar en caché resultados de operaciones costosas
- Lazy Evaluation: para diferir cálculos hasta que sean necesarios
Un caso de uso común es proporcionar valores por defecto en operaciones que podrían no producir resultados:
import java.util.Optional;
import java.util.function.Supplier;
public class DefaultValueExample {
public static void main(String[] args) {
// Simulamos buscar un usuario que no existe
Optional<String> emptyResult = Optional.empty();
// Utilizamos Supplier para proporcionar un valor por defecto
Supplier<String> defaultValueSupplier = () -> {
System.out.println("Generando valor por defecto...");
return "Usuario Invitado";
};
// El Supplier solo se ejecuta si emptyResult está vacío
String username = emptyResult.orElseGet(defaultValueSupplier);
System.out.println("Nombre de usuario: " + username);
}
}
En este ejemplo, el Supplier
solo se invoca cuando es necesario (cuando emptyResult
está vacío), lo que demuestra la evaluación perezosa que proporciona esta interfaz.
La interfaz Supplier
también es ampliamente utilizada en la API de Streams para generar secuencias infinitas:
import java.util.stream.Stream;
import java.util.function.Supplier;
public class InfiniteStreamExample {
public static void main(String[] args) {
// Supplier que genera números secuenciales
Supplier<Integer> sequentialNumberSupplier = new Supplier<>() {
private int value = 0;
@Override
public Integer get() {
return value++;
}
};
// Crear un stream infinito y limitar a 10 elementos
Stream.generate(sequentialNumberSupplier)
.limit(10)
.forEach(System.out::println);
}
}
En resumen, la interfaz Supplier
es una herramienta versátil para la generación de valores en Java, que permite implementar patrones de diseño funcionales y mejorar la eficiencia de nuestras aplicaciones mediante la evaluación perezosa y la separación de responsabilidades.
El método get()
El corazón de la interfaz Supplier<T>
es su único método abstracto: get(). Este método no acepta parámetros y devuelve un valor del tipo genérico T
especificado al implementar la interfaz. Su simplicidad es precisamente lo que hace a Supplier
tan versátil y fácil de utilizar en diferentes contextos de programación funcional.
La firma del método es extremadamente sencilla:
T get();
Donde T
representa cualquier tipo de dato que queramos que nuestro Supplier
genere, desde tipos primitivos hasta objetos complejos. Esta simplicidad permite implementar el método de diversas formas:
- Mediante expresiones lambda:
Supplier<Double> randomValue = () -> Math.random();
- Mediante referencias a métodos:
Supplier<LocalDate> today = LocalDate::now;
- Mediante clases anónimas:
Supplier<UUID> idGenerator = new Supplier<>() {
@Override
public UUID get() {
return UUID.randomUUID();
}
};
Características principales del método get()
El método get()
presenta varias características importantes que debemos tener en cuenta:
- No recibe parámetros: A diferencia de otras interfaces funcionales como
Function
oConsumer
, el métodoget()
no acepta ningún argumento. - Siempre devuelve un valor: Cada invocación debe producir un resultado del tipo especificado.
- Puede ser invocado múltiples veces: El método puede llamarse repetidamente para obtener valores.
- Puede devolver valores diferentes en cada invocación: No hay requisito de que el valor retornado sea siempre el mismo.
- No tiene efectos secundarios garantizados: Aunque puede tenerlos, idealmente debería centrarse solo en la generación de valores.
Patrones comunes de uso del método get()
El método get()
se utiliza en varios patrones de programación funcional:
1. Generación de valores aleatorios
import java.util.Random;
import java.util.function.Supplier;
public class RandomGeneratorExample {
public static void main(String[] args) {
Random random = new Random();
// Supplier para generar números aleatorios entre 1 y 6 (como un dado)
Supplier<Integer> diceRoll = () -> random.nextInt(6) + 1;
// Lanzar el dado 3 veces
System.out.println("Resultado del dado: " + diceRoll.get());
System.out.println("Resultado del dado: " + diceRoll.get());
System.out.println("Resultado del dado: " + diceRoll.get());
}
}
2. Creación de nuevas instancias
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class InstanceCreationExample {
public static void main(String[] args) {
// Supplier para crear nuevas listas
Supplier<List<String>> listFactory = ArrayList::new;
// Crear dos listas independientes usando el mismo supplier
List<String> firstList = listFactory.get();
List<String> secondList = listFactory.get();
// Modificar la primera lista
firstList.add("Elemento 1");
// Verificar que son instancias diferentes
System.out.println("Primera lista: " + firstList);
System.out.println("Segunda lista: " + secondList);
System.out.println("¿Son la misma instancia? " + (firstList == secondList));
}
}
3. Valores por defecto
import java.util.Map;
import java.util.HashMap;
import java.util.function.Supplier;
public class DefaultValueExample {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
// Supplier para proporcionar un valor por defecto
Supplier<Integer> defaultScore = () -> 50;
// Obtener puntuación con valor por defecto si no existe
String student = "Charlie";
int score = scores.containsKey(student) ? scores.get(student) : defaultScore.get();
System.out.println("Puntuación de " + student + ": " + score);
}
}
4. Cálculos costosos bajo demanda
import java.util.function.Supplier;
public class ExpensiveComputationExample {
public static void main(String[] args) {
// Supplier que realiza un cálculo costoso
Supplier<Long> expensiveOperation = () -> {
System.out.println("Realizando cálculo costoso...");
long result = 0;
for (long i = 0; i < 100_000_000; i++) {
result += i;
}
return result;
};
// El cálculo no se realiza hasta que se llama a get()
System.out.println("Antes de invocar el cálculo");
// Solo cuando necesitamos el resultado, invocamos get()
if (Math.random() > 0.5) {
System.out.println("Resultado: " + expensiveOperation.get());
} else {
System.out.println("Cálculo evitado");
}
}
}
5. Generación de secuencias
import java.util.function.Supplier;
import java.util.stream.Stream;
public class SequenceGenerationExample {
public static void main(String[] args) {
// Supplier que genera la secuencia de Fibonacci
Supplier<Long> fibonacciSupplier = new Supplier<>() {
private long prev = 0;
private long current = 1;
@Override
public Long get() {
long result = current;
long next = prev + current;
prev = current;
current = next;
return result;
}
};
// Generar los primeros 10 números de Fibonacci
Stream.generate(fibonacciSupplier)
.limit(10)
.forEach(System.out::println);
}
}
Combinación con otras APIs de Java
El método get()
de Supplier
se integra perfectamente con otras APIs de Java:
Con Optional para valores por defecto
import java.util.Optional;
import java.util.function.Supplier;
public class OptionalWithSupplierExample {
public static void main(String[] args) {
Optional<String> emptyOptional = Optional.empty();
// El método get() del Supplier solo se invoca si el Optional está vacío
String value = emptyOptional.orElseGet(() -> {
System.out.println("Generando valor alternativo");
return "Valor por defecto";
});
System.out.println("Resultado: " + value);
}
}
Con Stream para generación infinita
import java.time.LocalTime;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class StreamGenerationExample {
public static void main(String[] args) {
// Supplier que proporciona la hora actual
Supplier<String> timeSupplier = () -> LocalTime.now().toString();
// Generar 5 marcas de tiempo con un segundo de diferencia
Stream.generate(timeSupplier)
.limit(5)
.forEach(time -> {
System.out.println("Hora actual: " + time);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
Consideraciones de rendimiento
Al utilizar el método get()
de un Supplier
, es importante tener en cuenta algunas consideraciones de rendimiento:
- Evaluación perezosa: El código dentro del método
get()
solo se ejecuta cuando se invoca explícitamente, lo que permite diferir cálculos costosos. - Cacheo de resultados: Si el resultado de
get()
es costoso de calcular y no cambia, considera almacenarlo en caché después de la primera invocación. - Efectos secundarios: Aunque técnicamente es posible incluir efectos secundarios en el método
get()
, es mejor evitarlos para mantener la pureza funcional.
import java.util.function.Supplier;
public class MemoizationExample {
public static void main(String[] args) {
// Supplier con memoización (cacheo del resultado)
Supplier<Long> memoizedSupplier = memoize(() -> {
System.out.println("Calculando valor...");
return factorial(20); // Cálculo costoso
});
// Primera llamada - realiza el cálculo
System.out.println("Primer resultado: " + memoizedSupplier.get());
// Segunda llamada - usa el valor cacheado
System.out.println("Segundo resultado: " + memoizedSupplier.get());
}
// Método para crear un Supplier con memoización
private static <T> Supplier<T> memoize(Supplier<T> original) {
return new Supplier<>() {
private boolean initialized = false;
private T value;
@Override
public T get() {
if (!initialized) {
value = original.get();
initialized = true;
}
return value;
}
};
}
// Método para calcular factorial
private static long factorial(int n) {
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
}
El método get()
de la interfaz Supplier
es una herramienta simple pero potente que permite implementar numerosos patrones de programación funcional en Java. Su capacidad para generar valores bajo demanda lo convierte en un componente esencial para escribir código más limpio, modular y eficiente.
Otros ejercicios de programación de Java
Evalúa tus conocimientos de esta lección Interfaz funcional Supplier 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 la definición y propósito de la interfaz funcional Supplier.
- Aprender a implementar el método get() para generar valores sin argumentos.
- Identificar casos de uso comunes como inicialización perezosa, generación de valores aleatorios y provisión de valores por defecto.
- Aplicar Supplier en patrones funcionales y combinaciones con otras APIs de Java como Optional y Stream.
- Evaluar consideraciones de rendimiento y técnicas como memoización para optimizar el uso de Supplier.