1. Profundizamos en las clases anónimas
Una clase anónima es una subclase sin nombre o una implementación de interfaz que se crea justo en el lugar de uso. Antes de la llegada de las lambdas (Java 8) era la forma más práctica de implementar «de una sola vez» una interfaz o una clase abstracta.
Un clásico:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("¡Hola desde una clase anónima!");
}
};
r.run();
Aquí hemos declarado e implementado la interfaz Runnable en el mismo sitio — sin un archivo aparte ni nombre de clase. Estas implementaciones se usaban a menudo para manejadores de eventos, comparadores, hilos y otras tareas en las que se necesita inyectar rápidamente un comportamiento.
Si una lambda es «una expresión al vuelo», una clase anónima es «un pequeño actor sin nombre»: aparece para un papel breve y desaparece.
2. Comparación con las expresiones lambda
Sintaxis
Clase anónima:
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
Expresión lambda:
Comparator<String> comp = (a, b) -> a.length() - b.length();
La diferencia es evidente: la lambda es más compacta — no hace falta escribir explícitamente los tipos, el nombre del método ni llaves adicionales cuando la acción es simple.
Funcionalidad
- Clase anónima — un objeto completo. Se pueden declarar campos, métodos adicionales y sobrescribir métodos de Object (toString, equals, etc.).
- Expresión lambda — implementación de un único método abstracto de una interfaz funcional. Dentro no se pueden declarar campos propios ni métodos adicionales.
¿Cuándo elegir cada uno?
- Lambda — cuando se necesita implementar de forma breve un único método de una interfaz funcional.
- Clase anónima — cuando necesitas:
- implementar varios métodos (por ejemplo, de una clase abstracta);
- declarar campos para mantener estado;
- sobrescribir métodos de Object (por ejemplo, toString);
- aprovechar rasgos de herencia/acceso (por ejemplo, a miembros protegidos de la superclase).
3. Ámbito y la palabra clave this
Aquí hay una trampa habitual:
- en una clase anónima, this hace referencia a la instancia de la clase anónima;
- en una expresión lambda, this hace referencia a la clase externa donde se declara la lambda.
Ejemplo: comparemos el comportamiento
public class Outer {
String name = "Clase externa";
void test() {
Runnable anon = new Runnable() {
String name = "Clase anónima";
@Override
public void run() {
System.out.println(this.name); // "Clase anónima"
}
};
Runnable lambda = () -> System.out.println(this.name); // "Clase externa"
anon.run();
lambda.run();
}
}
Salida:
Clase anónima
Clase externa
En la clase anónima this apunta a la propia clase anónima (se toma su campo name). En la lambda this es Outer.
4. ¿Cuándo utilizar clases anónimas?
Si necesitas implementar más de un método
Una lambda funciona solo con interfaces funcionales (exactamente un método abstracto). Si la interfaz/la clase abstracta exige implementar varios métodos, necesitas una clase anónima.
abstract class Animal {
abstract void say();
abstract void jump();
}
Animal cat = new Animal() {
@Override
void say() {
System.out.println("¡Miau!");
}
@Override
void jump() {
System.out.println("¡Salto!");
}
};
Si necesitas guardar estado (campos)
Runnable r = new Runnable() {
int counter = 0;
@Override
public void run() {
counter++;
System.out.println("Invocado " + counter + " vez(es)");
}
};
r.run(); // Invocado 1 vez(es)
r.run(); // Invocado 2 vez(es)
Si necesitas sobrescribir métodos de Object
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
@Override
public String toString() {
return "Comparador por longitud de cadena";
}
};
System.out.println(comp); // Comparador por longitud de cadena
5. Ejemplos: Comparator y Runnable — lambda vs clase anónima
Ordenar cadenas por longitud
Clase anónima:
List<String> words = Arrays.asList("gato", "elefante", "ratón", "tigre");
words.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
System.out.println(words);
Expresión lambda:
List<String> words = Arrays.asList("gato", "elefante", "ratón", "tigre");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);
El resultado es el mismo, pero el código con lambda es más corto y más fácil de leer.
Runnable: lanzamiento de un hilo
Clase anónima:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hilo mediante clase anónima");
}
});
t1.start();
Expresión lambda:
Thread t2 = new Thread(() -> System.out.println("Hilo mediante lambda"));
t2.start();
Clase anónima con campos
Runnable r = new Runnable() {
int count = 0;
@Override
public void run() {
count++;
System.out.println("Invocado " + count + " vez(es)");
}
};
r.run(); // Invocado 1 vez(es)
r.run(); // Invocado 2 vez(es)
En una lambda no es posible — no se pueden declarar campos.
6. Particularidades: ámbito, variables y final
Tanto en clases anónimas como en expresiones lambda, las variables locales del método externo se pueden usar solo si son final o «efectivamente final» (no cambian tras la inicialización). Pero hay un matiz con los nombres:
- en una clase anónima se puede declarar una variable con el mismo nombre que en el ámbito externo («ocultación»);
- en una lambda — no: el nombre no debe entrar en conflicto con el de la variable externa.
Ejemplo:
int x = 10;
Runnable r = new Runnable() {
@Override
public void run() {
int x = 20; // OK: oculta la variable externa
System.out.println(x); // 20
}
};
r.run();
Runnable l = () -> {
// int x = 30; // Error de compilación: la variable ya está definida
System.out.println(x); // 10
};
l.run();
7. ¿Cuándo una lambda es mejor y cuándo una clase anónima es insustituible?
Las expresiones lambda son tu elección si:
- necesitas implementar una función corta para una interfaz funcional;
- no necesitas guardar estado;
- no necesitas sobrescribir métodos de Object;
- la implementación se usa «aquí y ahora» y es sencilla.
Una clase anónima es necesaria si:
- hay que implementar una interfaz con varios métodos o una clase abstracta;
- necesitas declarar campos o métodos adicionales;
- se requiere sobrescribir toString, equals, hashCode;
- necesitas acceso a miembros protegidos de la superclase.
8. Práctica: comparación con ejemplos
Ejercicio 1: Filtrar una lista usando Predicate
Clase anónima:
List<String> animals = Arrays.asList("gato", "elefante", "ratón", "tigre");
animals.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() < 4;
}
});
System.out.println(animals); // [elefante, ratón, tigre]
Expresión lambda:
List<String> animals = Arrays.asList("gato", "elefante", "ratón", "tigre");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [elefante, ratón, tigre]
Ejercicio 2: Comparar el ámbito de this
public class Demo {
String name = "Demo";
void check() {
Runnable anon = new Runnable() {
String name = "Anon";
@Override
public void run() {
System.out.println(this.name); // "Anon"
}
};
Runnable lambda = () -> System.out.println(this.name); // "Demo"
anon.run();
lambda.run();
}
public static void main(String[] args) {
new Demo().check();
}
}
9. Errores típicos al trabajar con clases anónimas y expresiones lambda
Error n.º 1: suponer que una lambda puede implementar varios métodos. Una lambda funciona solo con interfaces funcionales (un método abstracto). Si hay más métodos, utiliza una clase anónima.
Error n.º 2: confusión con el ámbito de this. En una lambda this es la clase externa; en una clase anónima, la propia clase anónima. Por eso es fácil obtener campos y valores «equivocados».
Error n.º 3: intentar declarar campos en una lambda. En una lambda no se pueden declarar campos propios: solo usar variables del contexto externo (final/«efectivamente final»). Para estado, usa una clase anónima.
Error n.º 4: ocultación de variables. En una clase anónima se puede declarar una variable local con el mismo nombre que la externa — esto es ocultación. En una lambda no se puede: el compilador dará error.
Error n.º 5: lógica demasiado compleja en una lambda. Si el cuerpo de la lambda se alarga más de 3–5 líneas, la legibilidad se resiente. Es mejor extraer el código a un método aparte o usar una clase anónima (si necesitas estado/varios métodos).
GO TO FULL VERSION