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ícate

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.

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.

Aprende Java online

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

Test

Gestión de errores y excepciones

Código

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Clases abstractas

Test

Listas

Código

Métodos de la clase String

Código

Streams: reduce()

Test

API java.nio 2

Puzzle

Polimorfismo

Código

Pattern Matching

Código

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Inferencia de tipos con var

Código

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Arrays y matrices

Código

Clases y objetos

Código

Interfaz funcional Consumer

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Interfaces

Código

Enumeraciones Enums

Código

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Sobrecarga de métodos

Código

CRUD de productos en Java

Proyecto

Clases sealed

Código

Creación de Streams

Test

Records

Código

Encapsulación

Código

Streams: min max

Puzzle

Herencia

Código

Métodos avanzados de la clase String

Puzzle

Funciones

Código

Polimorfismo de tiempo de compilación

Test

Reto sintaxis Java

Proyecto

Conjuntos

Código

Estructuras de control

Código

Recursión

Código

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

Operadores

Código

Variables

Código

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Mapas

Código

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Tipos de datos

Código

Clases abstractas

Código

Instalación

Test

Funciones

Código

Excepciones

Código

Estructuras de control

Código

Herencia de clases

Código

La clase Scanner

Código

Generics

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

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

Accede GRATIS a Java y certifícate

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.