Java 8, lanzado en marzo de 2014, introdujo una serie de características nuevas y poderosas que han transformado la forma en que los desarrolladores escriben código en Java. Este módulo cubrirá las características más importantes de Java 8, proporcionando explicaciones detalladas, ejemplos prácticos y ejercicios para reforzar el aprendizaje.
Contenido
Expresiones Lambda
Introducción
Las expresiones lambda son una característica clave de Java 8 que permiten tratar la funcionalidad como un argumento del método o almacenar la funcionalidad en una variable. Las lambdas proporcionan una forma clara y concisa de representar un método anónimo.
Sintaxis
o
Ejemplo
// Ejemplo de una expresión lambda simple Runnable runnable = () -> System.out.println("Hola, Mundo!"); runnable.run();
Explicación
En este ejemplo, () -> System.out.println("Hola, Mundo!")
es una expresión lambda que implementa la interfaz Runnable
.
Ejercicio
Escribe una expresión lambda que tome dos enteros y devuelva su suma.
@FunctionalInterface interface Suma { int sumar(int a, int b); } public class LambdaExample { public static void main(String[] args) { Suma suma = (a, b) -> a + b; System.out.println("Suma: " + suma.sumar(5, 3)); // Salida: Suma: 8 } }
Streams
Introducción
Los Streams en Java 8 proporcionan una forma moderna y funcional de procesar colecciones de datos. Permiten realizar operaciones como filtrado, mapeo y reducción de manera declarativa.
Ejemplo
import java.util.Arrays; import java.util.List; public class StreamExample { public static void main(String[] args) { List<String> nombres = Arrays.asList("Ana", "Juan", "Pedro", "Maria"); nombres.stream() .filter(nombre -> nombre.startsWith("P")) .forEach(System.out::println); // Salida: Pedro } }
Explicación
En este ejemplo, nombres.stream()
crea un stream a partir de la lista nombres
. Luego, filter(nombre -> nombre.startsWith("P"))
filtra los nombres que comienzan con "P", y forEach(System.out::println)
imprime cada nombre filtrado.
Ejercicio
Usa Streams para encontrar el número máximo en una lista de enteros.
import java.util.Arrays; import java.util.List; public class MaxExample { public static void main(String[] args) { List<Integer> numeros = Arrays.asList(3, 5, 7, 2, 8); int max = numeros.stream() .max(Integer::compare) .get(); System.out.println("Máximo: " + max); // Salida: Máximo: 8 } }
Interfaz Funcional
Introducción
Una interfaz funcional es una interfaz que contiene exactamente un método abstracto. Estas interfaces pueden ser implementadas por expresiones lambda.
Ejemplo
@FunctionalInterface interface Operacion { int operar(int a, int b); } public class FunctionalInterfaceExample { public static void main(String[] args) { Operacion suma = (a, b) -> a + b; System.out.println("Suma: " + suma.operar(10, 5)); // Salida: Suma: 15 } }
Explicación
En este ejemplo, Operacion
es una interfaz funcional con un método abstracto operar
. La expresión lambda (a, b) -> a + b
implementa esta interfaz.
Ejercicio
Crea una interfaz funcional que tenga un método que reciba un string y devuelva su longitud.
@FunctionalInterface interface Longitud { int obtenerLongitud(String s); } public class LongitudExample { public static void main(String[] args) { Longitud longitud = s -> s.length(); System.out.println("Longitud: " + longitud.obtenerLongitud("Hola")); // Salida: Longitud: 4 } }
Métodos Predeterminados y Estáticos en Interfaces
Introducción
Java 8 permite definir métodos predeterminados y estáticos en interfaces. Los métodos predeterminados proporcionan una implementación predeterminada que las clases pueden usar o sobrescribir.
Ejemplo
interface Vehiculo { default void encender() { System.out.println("El vehículo está encendido."); } static void revisar() { System.out.println("Revisando el vehículo."); } } public class Carro implements Vehiculo { public static void main(String[] args) { Carro carro = new Carro(); carro.encender(); // Salida: El vehículo está encendido. Vehiculo.revisar(); // Salida: Revisando el vehículo. } }
Explicación
En este ejemplo, Vehiculo
tiene un método predeterminado encender
y un método estático revisar
. La clase Carro
puede usar estos métodos directamente.
Ejercicio
Crea una interfaz con un método predeterminado que imprima un mensaje de saludo y un método estático que imprima un mensaje de despedida.
interface Saludo { default void saludar() { System.out.println("Hola, bienvenido!"); } static void despedir() { System.out.println("Adiós, hasta luego!"); } } public class SaludoExample implements Saludo { public static void main(String[] args) { SaludoExample ejemplo = new SaludoExample(); ejemplo.saludar(); // Salida: Hola, bienvenido! Saludo.despedir(); // Salida: Adiós, hasta luego! } }
API de Fecha y Hora
Introducción
Java 8 introdujo una nueva API de fecha y hora en el paquete java.time
, que es más intuitiva y menos propensa a errores que las clases Date
y Calendar
anteriores.
Ejemplo
import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; public class DateTimeExample { public static void main(String[] args) { LocalDate fecha = LocalDate.now(); LocalTime hora = LocalTime.now(); LocalDateTime fechaHora = LocalDateTime.now(); System.out.println("Fecha: " + fecha); // Salida: Fecha: 2023-10-01 (ejemplo) System.out.println("Hora: " + hora); // Salida: Hora: 10:15:30.123 (ejemplo) System.out.println("Fecha y Hora: " + fechaHora); // Salida: Fecha y Hora: 2023-10-01T10:15:30.123 (ejemplo) } }
Explicación
En este ejemplo, LocalDate.now()
, LocalTime.now()
, y LocalDateTime.now()
obtienen la fecha, hora y fecha y hora actuales, respectivamente.
Ejercicio
Crea un programa que imprima la fecha y hora actuales en un formato específico.
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class DateTimeFormatExample { public static void main(String[] args) { LocalDateTime fechaHora = LocalDateTime.now(); DateTimeFormatter formato = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); String fechaHoraFormateada = fechaHora.format(formato); System.out.println("Fecha y Hora Formateada: " + fechaHoraFormateada); // Salida: Fecha y Hora Formateada: 01-10-2023 10:15:30 (ejemplo) } }
Referencias a Métodos
Introducción
Las referencias a métodos proporcionan una forma de referenciar métodos existentes sin tener que invocarlos explícitamente. Son una forma abreviada de expresiones lambda.
Ejemplo
import java.util.Arrays; import java.util.List; public class MethodReferenceExample { public static void main(String[] args) { List<String> nombres = Arrays.asList("Ana", "Juan", "Pedro", "Maria"); nombres.forEach(System.out::println); // Salida: Ana, Juan, Pedro, Maria } }
Explicación
En este ejemplo, System.out::println
es una referencia a método que reemplaza la expresión lambda nombre -> System.out.println(nombre)
.
Ejercicio
Usa una referencia a método para ordenar una lista de cadenas en orden alfabético.
import java.util.Arrays; import java.util.List; public class MethodReferenceSortExample { public static void main(String[] args) { List<String> nombres = Arrays.asList("Ana", "Juan", "Pedro", "Maria"); nombres.sort(String::compareToIgnoreCase); nombres.forEach(System.out::println); // Salida: Ana, Juan, Maria, Pedro } }
Opcional
Introducción
La clase Optional
se introdujo en Java 8 para manejar valores que pueden estar presentes o no, evitando así el uso excesivo de null
y reduciendo la posibilidad de errores NullPointerException
.
Ejemplo
import java.util.Optional; public class OptionalExample { public static void main(String[] args) { Optional<String> nombre = Optional.of("Juan"); nombre.ifPresent(System.out::println); // Salida: Juan Optional<String> nombreVacio = Optional.empty(); System.out.println(nombreVacio.orElse("Nombre no disponible")); // Salida: Nombre no disponible } }
Explicación
En este ejemplo, Optional.of("Juan")
crea un Optional
que contiene el valor "Juan". Optional.empty()
crea un Optional
vacío. ifPresent
y orElse
son métodos útiles para manejar valores opcionales.
Ejercicio
Crea un programa que use Optional
para manejar un valor que puede estar presente o no, y proporciona un valor predeterminado si no está presente.
import java.util.Optional; public class OptionalDefaultExample { public static void main(String[] args) { Optional<String> mensaje = Optional.ofNullable(null); String mensajeFinal = mensaje.orElse("Mensaje predeterminado"); System.out.println(mensajeFinal); // Salida: Mensaje predeterminado } }
Conclusión
En este módulo, hemos explorado las características más importantes de Java 8, incluyendo expresiones lambda, streams, interfaces funcionales, métodos predeterminados y estáticos en interfaces, la nueva API de fecha y hora, referencias a métodos y la clase Optional
. Estas características no solo hacen que el código sea más conciso y legible, sino que también introducen un enfoque más funcional en la programación en Java. Asegúrate de practicar con los ejemplos y ejercicios proporcionados para consolidar tu comprensión de estos conceptos. ¡Prepárate para el siguiente módulo donde exploraremos más características avanzadas de Java!
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
Módulo 2: Flujo de Control
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
Módulo 4: Programación Orientada a Objetos Avanzada
Módulo 5: Estructuras de Datos y Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection