Novedades en Java 8: Expresiones Lambda, Streams y Métodos por Defecto

  1. Expresiones Lambda

1.1 Clases anónimas como predecesoras

En versiones anteriores de Java, las clases anónimas eran el principal mecanismo para crear objetos funcionales. Estas clases carecen de nombre y se instancian directamente con new. Una característica clave es su sintaxis dual, que combina definición de clase y creación de objeto.

Las clases anónimas típicamente se usan una sola vez y residen solo en el heap, sin referencia en la pila, lo que facilita su recolección por el GC.

public class EjemploClaseAnonima {
    public static void main(String[] args) {
        ejecutarAccion(new Accion() {
            @Override
            public void realizar() {
                System.out.println("Acción de dibujo ejecutada.");
            }
        });
        ejecutarAccion(new Dibujo());
    }

    public static void ejecutarAccion(Accion accion) {
        accion.realizar();
    }
}

interface Accion {
    void realizar();
}

class Dibujo implements Accion {
    @Override
    public void realizar() {
        System.out.println("Dibujando una imagen.");
    }
}

1.2 Introducción de las expresiones Lambda

Java 8 formalizó el concepto de interfaces con un único método abstracto, denominadas interfaces funcionales. Las expresiones Lambda permiten instanciar estas interfaces de manera concisa, reemplazando a las clases anónimas en contextos como el procesamiento de colecciones.

El núcleo de una Lambda es proporcionar una azúcar sintáctica para implementar el método abstracto de una interfaz funcional.

1.3 Sintaxis y condiciones de uso

La sintaxis general es:

(parametros) -> expresión
o
(parametros) -> { sentencias; }

Características clave:

  • Declaración de tipos opcional: el compilador puede inferirlos.
  • Paréntesis opcionales para un solo parámetro.
  • Llaves opcionales para una sola sentencia.
  • Palabra clave return implícita para expresiones simples.

La interfaz debe ser funcional, es decir, tener un solo método abstracto. La anotación @FunctionalInterface verifica esto a nivel de compilador.

1.4 Comparación con clases anónimas

@FunctionalInterface
interface Proceso {
    void ejecutar();
}

// Implementación tradicional
class Tarea implements Proceso {
    @Override
    public void ejecutar() {
        System.out.println("Procesando tarea...");
    }
}
new Tarea().ejecutar();

// Con clase anónima
new Proceso() {
    @Override
    public void ejecutar() {
        System.out.println("Ejecutando proceso anónimo.");
    }
}.ejecutar();

// Con Lambda
Proceso procesoLambda = () -> System.out.println("Operación Lambda.");
procesoLambda.ejecutar();

1.5 Ejemplo práctico: ordenamiento

import java.util.*;
import java.util.stream.Collectors;

public class EjemploOrdenamiento {
    public static void main(String[] args) {
        List<double> valores = new ArrayList<>(Arrays.asList(3.14, 2.71, 1.62, 4.56));
        System.out.println("Lista original: " + valores);

        // Ordenamiento con clase anónima
        Collections.sort(valores, new Comparator<double>() {
            @Override
            public int compare(Double a, Double b) {
                return Double.compare(b, a);
            }
        });
        System.out.println("Orden descendente:");
        valores.forEach(v -> System.out.println(v));

        // Ordenamiento con Lambda
        Collections.sort(valores, (a, b) -> Double.compare(a, b));
        System.out.println("Orden ascendente:");
        valores.forEach(System.out::println);
    }
}</double></double>
  1. Referencias a métodos

Las referencias a métodos simplifican las expresiones Lambda que solo invocan un método existente. Se aplican cuando la Lambda es de la forma parametros -> Clase.metodo(parametros) o similar.

2.1 Método estático de una clase

interface Calculo {
    double obtener();
}

class MatUtil {
    static double piAprox() {
        return 3.14159;
    }
}

// Lambda
Calculo calculoLambda = () -> MatUtil.piAprox();
// Referencia a método
Calculo calculoRef = MatUtil::piAprox;
System.out.println("Valor de pi: " + calculoRef.obtener());

2.2 Método de un parámetro de instancia

interface Transformador {
    String convertir(Recurso recurso, int nivel);
}

class Recurso {
    String nombre;
    String obtenerNombre(int n) {
        return nombre + "_" + n;
    }
}

// Lambda
Transformador tLambda = (r, n) -> r.obtenerNombre(n);
// Referencia a método
Transformador tRef = Recurso::obtenerNombre;
Recurso res = new Recurso();
res.nombre = "archivo";
System.out.println("Nombre transformado: " + tRef.convertir(res, 5));

2.3 Método de una instancia existente

interface Validador {
    boolean comprobar(String cadena);
}

class Texto {
    boolean esValido(String s) {
        return s.length() > 0;
    }
}

Texto txt = new Texto();
// Lambda
Validador vLambda = s -> txt.esValido(s);
// Referencia a método
Validador vRef = txt::esValido;
System.out.println("Cadena válida: " + vRef.comprobar("test"));

2.4 Constructor de una clase

interface Fabrica {
    Elemento crear(String tipo, double valor);
}

class Elemento {
    String tipo;
    double valor;
    Elemento(String t, double v) {
        this.tipo = t;
        this.valor = v;
    }
}

// Lambda
Fabrica fLambda = (t, v) -> new Elemento(t, v);
// Referencia a constructor
Fabrica fRef = Elemento::new;
Elemento elem = fRef.crear("metal", 9.81);
System.out.println("Elemento: " + elem.tipo + ", " + elem.valor);
  1. Interfaces funcionales

Java 8 introduce el paquete java.util.function con interfaces funcionales predefinidas. Algunas comunes:

import java.util.function.*;

// Supplier: no recibe parámetros, retorna un valor
Supplier<string> proveedor = () -> "Suministrado";
System.out.println(proveedor.get());

// Consumer: recibe un parámetro, no retorna valor
Consumer<integer> consumidor = n -> System.out.println("Procesado: " + n);
consumidor.accept(42);

// Predicate: recibe un parámetro, retorna boolean
Predicate<string> predicado = s -> s.startsWith("A");
System.out.println("Empieza con A: " + predicado.test("Algoritmo"));

// BiFunction: recibe dos parámetros, retorna un valor
BiFunction<string double="" registro=""> biFuncion = (nom, val) -> new Registro(nom, val);
Registro reg = biFuncion.apply("dato", 3.14);</string></string></integer></string>
  1. API de Stream

El API Stream permite procesar colecciones de datos de manera declarativa medinate pipelines con operaciones intermedias y terminales.

4.1 Creación de streams

List<persona> personas = Arrays.asList(
    new Persona("Ana", 28),
    new Persona("Carlos", 35),
    new Persona("Elena", 22)
);

Stream<persona> stream1 = personas.stream();
Stream<integer> stream2 = Stream.of(10, 20, 30);</integer></persona></persona>

4.2 Restricciones

Un stream solo se puede operar una vez. Las operaciones intermedias son perezosas y no se ejecutan sin una operación terminal.

4.3 Operaciones comunes

// Filtrado y transformación
personas.stream()
    .filter(p -> p.edad > 25)
    .map(p -> p.nombre.toUpperCase())
    .forEach(System.out::println);

// Saltar y limitar elementos
personas.stream()
    .skip(1)
    .limit(2)
    .collect(Collectors.toList());

// Operaciones de coincidencia
boolean todosMayores = personas.stream().allMatch(p -> p.edad > 18);
boolean algunJoven = personas.stream().anyMatch(p -> p.edad < 30);
boolean ningunoMayor60 = personas.stream().noneMatch(p -> p.edad > 60);

4.4 Reducción y recolección

// Reducción
int suma = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);

// Recolección a lista
List<string> nombres = personas.stream()
    .map(p -> p.nombre)
    .collect(Collectors.toList());

// Recolección a arreglo
String[] arregloNombres = personas.stream()
    .map(p -> p.nombre)
    .toArray(String[]::new);

// Agrupación
Map<string list="">> grupos = personas.stream()
    .collect(Collectors.groupingBy(p -> p.edad > 30 ? "mayores" : "menores"));</string></string>
  1. Métodos default y static en interfaces

Java 8 permite definir métodos con implementación en interfaces usando default y static, ofreciendo mayor flexibilidad.

5.1 Sintaxis básica

interface Dispositivo {
    String encender();

    default String reiniciar() {
        return "Reinicio estándar";
    }

    static String voltaje() {
        return "220V";
    }
}

5.2 Uso y resolución de conflictos

Si una clase implementa múltiples interfaces con métodos default idénticos, debe proporcionar una implementación explícita. La herencia de clase tiene prioridad sobre los métodos default de interfaces.

interface Motor {
    default String potencia() {
        return "100HP";
    }
}

interface Bateria {
    default String potencia() {
        return "12V";
    }
}

class Vehiculo implements Motor, Bateria {
    @Override
    public String potencia() {
        return Motor.super.potencia(); // Selección explícita
    }
}

Etiquetas: Java 8 lambda Method Reference Functional Interface Stream API

Publicado el 6-16 16:45