Skip to content

3 Polimorfismo

Casting de objetos

Como ocurre con los tipos primitivos, también es posible realizar casting entre objetos siempre y cuando esté dentro de la estructura jerárquica en su herencia.

Para crear un objeto, hay que declarar una variable cuyo tipo es una clase:

Vehicle vehicle; // vehicle es una variable de tipo Vehicle

Eso significa que vehicle es de tipo Vehicle y que nunca va a cambiar de tipo, siempre va a ser de tipo Vehicle

Creemos ahora una variable de tipo Car:

Car car; // car es una variable de tipo car.

Lo mismo que antes, car es una variable de tipo Car y nunca va a cambiar de tipo, siempre va a ser de tipo Car.

A la variable vehicle se le puede asignar un objeto de tipo Vehicle y también se le puede asignar objetos cuyo tipo sean una subclase de Vehicle:

vehicle = car; // A vehicle se le está asignando un objeto de tipo Car, que es una subclase de Vehicle

La variable vehicle contiene un objeto de tipo Car pero su tipo es Vehicle, entonces solamente podrá acceder a los atributos y métodos de Vehicle:

vehicle.refuel(50); // Error de compilación: el método refuel no está definido para el tipo Vehicle

La variable vehicle no puede acceder al método refuel porque dicho método está definido en la clase Car. Es decir, una variable padre puede contener un objeto de tipo hijo pero solamente podrá acceder a los atributos y métodos definidos en el padre.

¿y si la asignación la hiciéramos al revés, es decir, a car le asignamos vehicle?

car = vehicle; // Error de compilación

Daría un error de coincidencia de tipos, pero se podría solucionar con un casting:

car = (Car) vehicle; // Solucionado con un casting ya que Vehicle contiene un Car

Hay que tener en cuenta que para que el casting funciones, la variable vehicle debe contener objeto de tipo Car, porque si no, dará un error de ejecución ClassCastException.

vehicle = new Vehicle(2, "blanco");
car = (Car) vehicle; // Error de ejecución: ClassCastException ya que Vehicle no contiene un objeto de tipo car

Modificador final

El modificador final tiene varios usos en función de dónde se utilice:

  • Delante de una variable en su declaración, crea una constante. La constante puede recibir el valor en tiempo de compilación o en tiempo de ejecución.

    final double PI = 3.141591;
    

    public class MyClass{
        private final int NUMBER;
    
        public MyClass(int n){
            NUMBER = n;
        }
    }
    
    La constante NUMBER recibe el valor en la construcción del objeto.

  • Delante de una variable que referencia a un objeto: dicha variable no puede referenciar a otro objeto.

    final Car car = new Car(4, "rojo");
    car = new Car(4, "blanco"); //ERROR: no puede referencias a otro objeto
    
  • En la declaración de un método: dicho método no se puede anular por las subclases:

    public class Vehicle{
        //...
        final public void accelerate(double amount){
            speed += amount;
        }
        //...
    }
    
    public class Car extends Vehicle{
        //...
        @Override
        public void accelerate(double amount){ // ERROR, no se puede anular el método
            super.accelerate(amount);
            gasoline *= 0.5;
        }
        // ...
    }
    
    • En la definición de una clase: significa que ese clase no puede tener descendencia.

Polimorfismo

Polimorfismo es la capacidad de un objeto de adquirir varias formas.

La sobrecarga de métodos es un tipo de polimorfismo estático porque se resuelve en tiempo de compilación el método apropiado a ser llamado basado en la lista de argumentos.

La anulación de métodos es un tipo de polimorfismo dinámico porque se resuelve en tiempo de ejecución atendiendo al tipo del objeto. Con una variable de tipo padre, si se utiliza un objeto del padre para invocar al método, entonces se ejecutará el método de la clase padre, pero si se utiliza un objeto de la clase hija para invocar al método, entonces se ejecutará el método de la clase hija

public class Polimorfismo {
    public void show(){
        Vehicle vehicle = new Vehicle(2, "azul");
        vehicle.accelerate(100.39); // Método del padre
        System.out.printf("La velocidad del vehículo es %.2f km/h\n", vehicle.getSpeed());

        vehicle = new Car(4, "rojo");
        vehicle.accelerate(50.89); // Método del hijo
        System.out.printf("La velocidad del vehículo es %.2f km/h\n", vehicle.getSpeed());
    }

    public static void main(String[] args) {
        new Polimorfismo().show();
    }
}

Otro de las ventajas del polimorfismo es poder tener un método que solicite un padre, y poder pasarle un hijo:

public class Polimorfismo2 {
    public void show(){
        accelerateVehicle(new Car(4, "rojo"));
    }

    private void accelerateVehicle(Vehicle vehicle) {
        vehicle.accelerate(100.39); // Método del padre
        System.out.printf("La velocidad del vehículo es %.2f km/h\n", vehicle.getSpeed());s
    }

    public static void main(String[] args) {
        new Polimorfismo2().show();
    }
}

instanceof

El operador instanceof permite comprobar si un determinado objeto pertenece a una clase concreta. Se utiliza de esta forma:

object instanceof class

Devuelve true si el objeto pertenece a dicha clase.

public class InstanceOf {
    public void show(){
        Vehicle vehicle = new Vehicle(2, "azul");
        Car car = new Car(4, "rojo");

        System.out.println(vehicle instanceof Vehicle); // true
        System.out.println(vehicle instanceof Car); // false
        System.out.println(car instanceof Vehicle); // true
        System.out.println(car instanceof Car); // true
    }

    public static void main(String[] args) {
        new InstanceOf().show();
    }
}

Tal y como podemos observar en el ejemplo, car también devuelve true con Vehicle ya que los objetos de las subclases también devuelven true con la superclase.

toString

La clase Object es la clase raíz de todo el árbol de la jerarquía de clases Java, es decir, es una superclase implícita de todas las demás clases. En otras palabras, todas las demás clases son subclases de Object. Esto significa que una variable de referencia de tipo Object puede referirse a un objeto de cualquier otra clase.

La clase Object proporciona un cierto número de métodos de utilidad general que pueden utilizar todos los objetos ya que los heredan. Pero normalmente hay que sobrescribirlos para que funcionen adecuadamente adaptándolos a la clase correspondiente. Esto se hace con la idea de que todas las clases utilicen el mismo nombre y prototipo de método para hacer operaciones comunes. Como por ejemplo, toString() que se utiliza para obtener una cadena de texto que represente al objeto. El método toString() de la clase Object devuelve una cadena que consiste en el nombre de la clase del objeto, el carácter arroba ‘@’ y la representación hexadecimal sin signo del código hash del objeto. Siempre se recomienda sobrescribir el método toString() para obtener nuestra propia representación del objeto.

public class Vehicle{
    //...
    @Override
    public String toString() {
        return "Vehicle [wheelCount=" + wheelCount+ ", speed=" + speed + ", color=" + color + "]";
    }
}

Algunos entornos IDEs, puede autogenerar el código del método toString.

Haciendo uso del método para sacar por consola un objeto no es necesario hacer toString de forma explícita, ya que dicho método lo llama de forma implícita.

public class ToString {
    public void show(){
        Vehicle vehicle = new Vehicle(2, "azul");

        System.out.println(vehicle);
    }

    public static void main(String[] args) {
        new ToString().show();
    }
}