CodeGym /Cursos /JAVA 25 SELF /Clases anónimas: diferencias con las lambda, ejemplos

Clases anónimas: diferencias con las lambda, ejemplos

JAVA 25 SELF
Nivel 48, Lección 4
Disponible

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).

1
Tarea
JAVA 25 SELF, nivel 48, lección 4
Bloqueada
Censura en el zoológico digital 🦒
Censura en el zoológico digital 🦒
1
Tarea
JAVA 25 SELF, nivel 48, lección 4
Bloqueada
Voces del mundo interior 🎭
Voces del mundo interior 🎭
1
Cuestionario/control
Expresiones lambda, nivel 48, lección 4
No disponible
Expresiones lambda
Expresiones lambda
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION