Definició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.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
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.
Aprendizajes 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.
Completa Java y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs