JavaRush /Blog Java /Random-ES /Polimorfismo en Java

Polimorfismo en Java

Publicado en el grupo Random-ES
Las preguntas sobre programación orientada a objetos son una parte integral de una entrevista técnica para el puesto de desarrollador de Java en una empresa de TI. En este artículo hablaremos sobre uno de los principios de la programación orientada a objetos: el polimorfismo. Nos centraremos en aspectos sobre los que a menudo se pregunta durante las entrevistas y también proporcionaremos pequeños ejemplos para mayor claridad.

¿Qué es el polimorfismo?

El polimorfismo es la capacidad de un programa de utilizar de forma idéntica objetos con la misma interfaz sin información sobre el tipo específico de este objeto. Si responde la pregunta qué es el polimorfismo de esta manera, lo más probable es que le pidan que explique lo que quiere decir. Una vez más, sin hacer muchas preguntas adicionales, ponga todo en orden para el entrevistador.

Polimorfismo en Java en una entrevista - 1
Podemos comenzar con el hecho de que el enfoque POO implica construir un programa Java basado en la interacción de objetos que se basan en clases. Las clases son dibujos preescritos (plantillas) según los cuales se crearán los objetos en el programa. Además, una clase siempre tiene un tipo específico que, con un buen estilo de programación, “indica” su propósito por su nombre. Además, cabe señalar que, dado que Java es un lenguaje fuertemente tipado, el código del programa siempre debe indicar el tipo de objeto al declarar variables. Agregue a esto que la escritura estricta aumenta la seguridad del código y la confiabilidad del programa y le permite evitar errores de incompatibilidad de tipos (por ejemplo, un intento de dividir una cadena por un número) en la etapa de compilación. Naturalmente, el compilador debe "conocer" el tipo declarado; puede ser una clase del JDK o una que hayamos creado nosotros mismos. Tenga en cuenta al entrevistador que cuando trabajamos con el código del programa, podemos usar no solo objetos del tipo que asignamos al declarar, sino también sus descendientes. Este es un punto importante: podemos tratar muchos tipos como si fueran solo uno (siempre que esos tipos se deriven de un tipo base). Esto también significa que, habiendo declarado una variable de tipo superclase, podemos asignarle el valor de uno de sus descendientes. Al entrevistador le gustará si le das un ejemplo. Seleccione algún objeto que pueda ser común (base) para un grupo de objetos y herede un par de clases de él. Clase base:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "Yo bailo como todos los demás".);
    }

    @Override
    public String toString() {
        return "Я " + name + ", a mi " + age + " años. " ;
    }
}
En los descendientes, anule el método de la clase base:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// anulando el método de la clase base
    @Override
    public void dance() {
        System.out.println( toString() + "¡Yo bailo boogie eléctrico!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// anulando el método de la clase base
    @Override
    public void dance(){
        System.out.println(toString() + "¡Estoy bailando breakdance!");
    }
}
Un ejemplo de polimorfismo en Java y el uso de objetos en un programa:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Antón", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast al tipo base
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Ígor", 20); // upcast al tipo base

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// llamada al método polimórfico
        }
    }
}
mainMuestre en el código del método lo que hay en las líneas:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Ígor", 20);
Declaramos una variable de tipo superclase y le asignamos el valor de uno de los descendientes. Lo más probable es que se le pregunte por qué el compilador no se queja de la discrepancia entre los tipos declarados a la izquierda y a la derecha del signo de asignación, ya que Java tiene una escritura estricta. Explique que aquí funciona la conversión de tipos hacia arriba: una referencia a un objeto se interpreta como una referencia a la clase base. Además, el compilador, al encontrar dicha construcción en el código, lo hace de forma automática e implícita. Según el código de ejemplo, se puede mostrar que el tipo de clase declarado a la izquierda del signo de asignación Dancertiene varias formas (tipos) declaradas a la derecha BreakDankDancer. ElectricBoogieDancerCada una de las formas puede tener su propio comportamiento único para una funcionalidad común definida en el método de superclase dance. Es decir, un método declarado en una superclase se puede implementar de forma diferente en sus descendientes. En este caso, estamos tratando con la anulación de métodos, y esto es exactamente lo que crea una variedad de formas (comportamientos). Puedes ver esto ejecutando el código del método principal de ejecución: Salida del programa Soy Anton, tengo 18 años. Bailo como todos los demás. Soy Alexey, tengo 19 años. ¡Yo hago breakdance! Soy Igor, tengo 20 años. ¡Bailo el boogie eléctrico! Si no utilizamos la anulación en los descendientes, no obtendremos un comportamiento diferente. BreakDankDancerPor ejemplo, si comentamos ElectricBoogieDancerel método de nuestras clases dance, el resultado del programa será así: Soy Anton, tengo 18 años. Bailo como todos los demás. Soy Alexey, tengo 19 años. Bailo como todos los demás. Soy Igor, tengo 20 años. Bailo como todos los demás. y esto significa que simplemente no tiene sentido crear nuevas BreakDankDancerclases ElectricBoogieDancer. ¿Cuál es exactamente el principio del polimorfismo de Java? ¿Dónde se esconde utilizar un objeto en un programa sin conocer su tipo específico? En nuestro ejemplo, esta es una llamada a un método d.dance()en un objeto dde tipo Dancer. El polimorfismo de Java significa que el programa no necesita saber de qué tipo será el objeto BreakDankDanceru objeto ElectricBoogieDancer. Lo principal es que es descendiente de la clase Dancer. Y si hablamos de descendientes, cabe destacar que la herencia en Java no es sólo extends, sino también implements. Ahora es el momento de recordar que Java no admite herencia múltiple: cada tipo puede tener un padre (superclase) y un número ilimitado de descendientes (subclases). Por lo tanto, las interfaces se utilizan para agregar múltiples funciones a las clases. Las interfaces reducen el acoplamiento de objetos a un padre en comparación con la herencia y se utilizan muy ampliamente. En Java, una interfaz es un tipo de referencia, por lo que un programa puede declarar el tipo como una variable del tipo de interfaz. Este es un buen momento para dar un ejemplo. Creemos la interfaz:
public interface Swim {
    void swim();
}
Para mayor claridad, tomemos objetos diferentes y no relacionados e implementemos una interfaz en ellos:
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"Nado con un anillo inflable".);
    }

    @Override
    public String toString() {
        return "Я " + name + ", a mi " + age + " años. ";
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("Soy un pez " + name + ". Nado moviendo mis aletas".);

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("El submarino navega, girando las hélices, a una velocidad" + speed + "nudos".);
    }
}
Método main:
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Antón", 6);
        Swim fish = new Fish("ballena");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
El resultado de ejecutar un método polimórfico definido en una interfaz nos permite ver diferencias en el comportamiento de los tipos que implementan esa interfaz. Consisten en diferentes resultados de la ejecución del método swim. Después de estudiar nuestro ejemplo, el entrevistador puede preguntar por qué, al ejecutar el código desdemain
for (Swim s : swimmers) {
            s.swim();
}
¿Los métodos definidos en estas clases se llaman para nuestros objetos? ¿Cómo se selecciona la implementación deseada de un método al ejecutar un programa? Para responder a estas preguntas, debemos hablar de enlace tardío (dinámico). Por vinculación nos referimos a establecer una conexión entre la llamada a un método y su implementación específica en las clases. Básicamente, el código determina cuál de los tres métodos definidos en las clases se ejecutará. Java utiliza de forma predeterminada el enlace tardío (en tiempo de ejecución en lugar de en tiempo de compilación, como es el caso del enlace anticipado). Esto significa que al compilar el código
for (Swim s : swimmers) {
            s.swim();
}
el compilador aún no sabe de qué clase proviene el código Humanni Fishsi Uboatse ejecutará en el formato swim. Esto se determinará solo cuando el programa se ejecute gracias al mecanismo de despacho dinámico: verificando el tipo de objeto durante la ejecución del programa y seleccionando la implementación deseada del método para este tipo. Si le preguntan cómo se implementa esto, puede responder que al cargar e inicializar objetos, la JVM crea tablas en la memoria y en ellas asocia variables con sus valores y objetos con sus métodos. Además, si un objeto se hereda o implementa una interfaz, primero se verifica la presencia de métodos anulados en su clase. Si los hay, se vinculan a este tipo, si no, se busca un método que esté definido en la clase un nivel superior (en el padre) y así hasta la raíz en una jerarquía multinivel. Hablando sobre el polimorfismo en programación orientada a objetos y su implementación en el código del programa, observamos que es una buena práctica utilizar descripciones abstractas para definir clases base utilizando clases e interfaces abstractas. Esta práctica se basa en el uso de la abstracción: aislar el comportamiento y las propiedades comunes y encerrarlos dentro de una clase abstracta, o aislar solo el comportamiento común, en cuyo caso creamos una interfaz. Construir y diseñar una jerarquía de objetos basada en interfaces y herencia de clases es un requisito previo para cumplir el principio del polimorfismo de programación orientada a objetos. Respecto al tema del polimorfismo y las innovaciones en Java, podemos mencionar que al crear clases e interfaces abstractas, comenzando con Java 8, es posible escribir una implementación predeterminada de métodos abstractos en clases base usando la palabra clave default. Por ejemplo:
public interface Swim {
    default void swim() {
        System.out.println("Simplemente flotando");
    }
}
A veces pueden preguntar sobre los requisitos para declarar métodos en clases base para que no se viole el principio de polimorfismo. Aquí todo es simple: estos métodos no deben ser estáticos , privados y finales . Private hace que el método esté disponible solo en la clase y no puede anularlo en el descendiente. Estático hace que el método sea propiedad de la clase, no del objeto, por lo que siempre se llamará al método de la superclase. Final hará que el método sea inmutable y esté oculto a sus herederos.

¿Qué nos aporta el polimorfismo en Java?

Lo más probable es que también surja la pregunta de qué nos aporta el uso del polimorfismo. Aquí puedes responder brevemente, sin entrar demasiado en detalles:
  1. Le permite reemplazar implementaciones de objetos. En esto se basan las pruebas.
  2. Proporciona capacidad de ampliación del programa: resulta mucho más fácil crear una base para el futuro. Agregar nuevos tipos basados ​​en los existentes es la forma más común de ampliar la funcionalidad de los programas escritos en estilo OOP.
  3. Le permite combinar objetos con un tipo o comportamiento común en una colección o matriz y administrarlos de manera uniforme (como en nuestros ejemplos, hacer que todos bailen - un método danceo naden - un método swim).
  4. Flexibilidad al crear nuevos tipos: puede optar por implementar un método de un padre o anularlo en un hijo.

Palabras de despedida para el viaje.

El principio del polimorfismo es un tema muy importante y amplio. Cubre casi la mitad de la programación orientada a objetos de Java y una buena parte de los conceptos básicos del lenguaje. No podrá salirse con la suya al definir este principio en una entrevista. Lo más probable es que el desconocimiento o la mala comprensión del mismo ponga fin a la entrevista. Por lo tanto, no seas perezoso en comprobar tus conocimientos antes del examen y actualizarlos si es necesario.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION