Interfaz funcional Supplier

Intermedio
Java
Java
Actualizado: 08/05/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

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.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

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 o Consumer, el método get() 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

⭐⭐⭐⭐⭐
4.9/5 valoración