Este artículo explora la evolución de las expresiones lambda en Java, comenzando con enfoques tradicionales para el filtrado de colecciones y avanzando hacia soluciones más concisas y modernas.
Filtrado Tradicional de Colecciones
Consideremos dos requisitos comunes: de una lista de estudiantes, necesitamos 1. encontrar a los estudiantes mayores de 20 años y 2. encontrar a los estudiantes con una puntuación inferior a 70. Veamos cómo se implementaría esto tradicionalmente.
public class Student {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public double getScore() { return score; }
public void setScore(double score) { this.score = score; }
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", score=" + score + '}';
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
public class StudentFilterTest {
private List<student> students = Arrays.asList(
new Student("Alice", 21, 58),
new Student("Bob", 23, 88),
new Student("Charlie", 19, 68),
new Student("David", 25, 78),
new Student("Eve", 16, 98)
);
@Test
public void testTraditionalFiltering() {
List<student> olderThan20 = findStudentsByAge();
System.out.println("Estudiantes mayores de 20: " + olderThan20);
System.out.println("-----------");
List<student> lessThan70Score = findStudentsByScore();
System.out.println("Estudiantes con puntuación < 70: " + lessThan70Score);
}
// Filtra estudiantes por edad mayor a 20
public List<student> findStudentsByAge() {
List<student> resultList = new ArrayList<>();
for (Student student : students) {
if (student.getAge() > 20) {
resultList.add(student);
}
}
return resultList;
}
// Filtra estudiantes por puntuación menor a 70
public List<student> findStudentsByScore() {
List<student> resultList = new ArrayList<>();
for (Student student : students) {
if (student.getScore() < 70) {
resultList.add(student);
}
}
return resultList;
}
}
</student></student></student></student></student></student></student>
El código anterior, aunque funcional, resulta verboso. Los bucles for y la lógica de creación de listas son repetitivos. Si se necesitara un nuevo criterio de filtrado, como por nombre, se requeriría un nuevo método, aumentando la duplicación de código. Para abordar esto, podemos refactorizar usando el patrón Strategy.
Evolución 1: Patrón Strategy con Interfaces
Introducimos una interfaz genérica para manejar las condiciones de filtrado.
/**
* Interfaz para manejar criterios de filtrado de estudiantes.
*/
public interface StudentFilterCriterion<t> {
boolean test(T student);
}
</t>
Creamos una implementación concreta para filtrar por edad.
/**
* Criterio para filtrar estudiantes mayores de 20 años.
*/
public class AgeCriterion implements StudentFilterCriterion<student> {
@Override
public boolean test(Student student) {
return student.getAge() > 20;
}
}
</student>
Ahora, refactorizamos el método de filtrado para aceptar esta interfaz.
public class StudentFilterTest {
// ... (definición de Student y lista students)
@Test
public void testStrategyPattern() {
List<student> olderStudents = filterStudents(students, new AgeCriterion());
System.out.println("Estudiantes mayores de 20 (Strategy): " + olderStudents);
}
/**
* Filtra una lista de estudiantes basándose en un criterio dado.
* @param studentList La lista original de estudiantes.
* @param criterion El criterio de filtrado a aplicar.
* @return Una nueva lista con los estudiantes que cumplen el criterio.
*/
public List<student> filterStudents(List<student> studentList, StudentFilterCriterion<student> criterion) {
List<student> resultList = new ArrayList<>();
for (Student student : studentList) {
if (criterion.test(student)) {
resultList.add(student);
}
}
return resultList;
}
}
</student></student></student></student></student>
Este enfoque mejora la extensibilidad. Para filtrar por puntuación, simplemente crearíamos una nueva clase ScoreCriterion implementando StudentFilterCriterion. Sin embargo, esto puede llevar a un exceso de clases pequeñas. Exploremos el uso de clases anónimas.
Evolución 2: Clases Anónimas
Podemos implementar el criterio directamente en el punto de uso, evitando la creación de clases separadas para cada criterio.
public class StudentFilterTest {
// ... (definición de Student y lista students)
@Test
public void testAnonymousClass() {
// Implementación anónima para filtrar estudiantes con puntuación < 70
List<student> lowScoreStudents = filterStudents(students, new StudentFilterCriterion<student>() {
@Override
public boolean test(Student student) {
return student.getScore() < 70;
}
});
System.out.println("Estudiantes con puntuación < 70 (Clase Anónima): " + lowScoreStudents);
}
// ... (método filterStudents)
}
</student></student>
Este método reduce la cantidad de código y clases, pero aún es bastante explícito.
Evolución 3: Expresiones Lambda
Con la introducción de las expresiones lambda en Java 8, podemos lograr una sintaxis aún más concisa.
public class StudentFilterTest {
// ... (definición de Student y lista students)
@Test
public void testLambdaExpression() {
// Expresión lambda para filtrar estudiantes con puntuación < 70
List<student> lowScoreStudents = filterStudents(students, student -> student.getScore() < 70);
System.out.println("Estudiantes con puntuación < 70 (Lambda): " + lowScoreStudents);
}
// El método filterStudents sigue siendo el mismo, esperando una interfaz funcional.
public List<student> filterStudents(List<student> studentList, StudentFilterCriterion<student> criterion) {
List<student> resultList = new ArrayList<>();
for (Student student : studentList) {
if (criterion.test(student)) {
resultList.add(student);
}
}
return resultList;
}
}
</student></student></student></student></student>
La expresión student -> student.getScore() < 70 es una expresión lambda. Representa una instancia de una interfaz funcional (una interfaz con un solo método abstracto, como StudentFilterCriterion). Las lambdas simplifican la escritura, hacen el código más legible y expresivo. Es importante tener en cuenta que las expresiones lambda requieren una versión de Java 8 o superior y dependen de interfaces funcionales.