Buscar..


Introducción

El polimorfismo es uno de los principales conceptos de programación orientada a objetos (POO). La palabra polimorfismo se derivó de las palabras griegas "poli" y "morfos". Poli significa "muchos" y morfos significa "formas" (muchas formas).

Hay dos formas de realizar el polimorfismo. Método de sobrecarga y método de anulación .

Observaciones

Interfaces son otra forma de lograr el polimorfismo en Java, aparte de la herencia basada en clases. Las interfaces definen una lista de métodos que forman la API del programa. Las clases deben implement una interface anulando todos sus métodos.

Método de sobrecarga

La sobrecarga de métodos , también conocida como sobrecarga de funciones , es la capacidad de una clase para tener múltiples métodos con el mismo nombre, dado que difieren en el número o tipo de argumentos.

El compilador comprueba la firma del método para la sobrecarga del método.

Método de firma consiste en tres cosas:

  1. Nombre del método
  2. Numero de parametros
  3. Tipos de parametros

Si estos tres son iguales para cualquiera de los dos métodos en una clase, el compilador arroja un error de método duplicado .

Este tipo de polimorfismo se denomina polimorfismo de tiempo de compilación o estático porque el compilador decide el método apropiado a ser llamado durante el tiempo de compilación basado en la lista de argumentos.

class Polymorph {

    public int add(int a, int b){
        return a + b;
    }
    
    public int add(int a, int b, int c){
        return a + b + c;
    }

    public float add(float a, float b){
        return a + b;
    }

    public static void main(String... args){
        Polymorph poly = new Polymorph();
        int a = 1, b = 2, c = 3;
        float d = 1.5, e = 2.5;

        System.out.println(poly.add(a, b));
        System.out.println(poly.add(a, b, c));
        System.out.println(poly.add(d, e));
    }

}

Esto resultará en:

2
6
4.000000

Los métodos sobrecargados pueden ser estáticos o no estáticos. Esto tampoco afecta la sobrecarga de métodos.

public class Polymorph {

    private static void methodOverloaded()
    {
        //No argument, private static method
    }
 
    private int methodOverloaded(int i)
    {
        //One argument private non-static method
        return i;
    }
 
    static int methodOverloaded(double d)
    {
        //static Method
        return 0;
    }
 
    public void methodOverloaded(int i, double d)
    {
        //Public non-static Method
    }
}

Además, si cambia el tipo de método de retorno, no podemos obtenerlo como método de sobrecarga.

public class Polymorph {  

void methodOverloaded(){
    //No argument and No return type
}

int methodOverloaded(){
    //No argument and int return type 
    return 0;
}

Método Anulando

El método de anulación es la capacidad de los subtipos para redefinir (anular) el comportamiento de sus supertipos.

En Java, esto se traduce en subclases que anulan los métodos definidos en la súper clase. En Java, todas las variables no primitivas son en realidad references , que son similares a los punteros a la ubicación del objeto real en la memoria. Las references solo tienen un tipo, que es el tipo con el que se declararon. Sin embargo, pueden apuntar a un objeto de su tipo declarado o cualquiera de sus subtipos.

Cuando se llama a un método en una reference , se invoca el método correspondiente del objeto real al que se apunta .

class SuperType {
    public void sayHello(){
        System.out.println("Hello from SuperType");
    }

    public void sayBye(){
        System.out.println("Bye from SuperType");
    }
}

class SubType extends SuperType {
    // override the superclass method
    public void sayHello(){
        System.out.println("Hello from SubType");
    }
}

class Test {
    public static void main(String... args){
        SuperType superType = new SuperType();
        superType.sayHello(); // -> Hello from SuperType

        // make the reference point to an object of the subclass
        superType = new SubType();
        // behaviour is governed by the object, not by the reference
        superType.sayHello(); // -> Hello from SubType

        // non-overridden method is simply inherited
        superType.sayBye(); // -> Bye from SuperType
    }
}

Reglas a tener en cuenta

Para anular un método en la subclase, el método de anulación (es decir, el que está en la subclase) DEBE TENER :

  • mismo nombre
  • mismo tipo de retorno en el caso de primitivas (se permite una subclase para las clases, esto también se conoce como tipos de retorno covariantes).
  • mismo tipo y orden de parametros
  • puede lanzar solo aquellas excepciones que están declaradas en la cláusula de lanzamientos del método de la superclase o excepciones que son subclases de las excepciones declaradas. También puede optar por NO lanzar ninguna excepción. Los nombres de los tipos de parámetros no importan. Por ejemplo, void methodX (int i) es igual que void methodX (int k)
  • No podemos anular los métodos finales o estáticos. Lo único que podemos hacer es cambiar solo el cuerpo del método.

Agregar comportamiento agregando clases sin tocar el código existente

import java.util.ArrayList;
import java.util.List;

import static java.lang.System.out;

public class PolymorphismDemo {

    public static void main(String[] args) {
        List<FlyingMachine> machines = new ArrayList<FlyingMachine>();
        machines.add(new FlyingMachine());
        machines.add(new Jet());
        machines.add(new Helicopter());
        machines.add(new Jet());

        new MakeThingsFly().letTheMachinesFly(machines);
    }
}

class MakeThingsFly {
    public void letTheMachinesFly(List<FlyingMachine> flyingMachines) {
        for (FlyingMachine flyingMachine : flyingMachines) {
            flyingMachine.fly();
        }
    }
}

class FlyingMachine {
    public void fly() {
        out.println("No implementation");
    }
}

class Jet extends FlyingMachine {
    @Override
    public void fly() {
        out.println("Start, taxi, fly");
    }

    public void bombardment() {
        out.println("Fire missile");
    }
}

class Helicopter extends FlyingMachine {
    @Override
    public void fly() {
        out.println("Start vertically, hover, fly");
    }
}

Explicación

a) La clase MakeThingsFly puede trabajar con todo lo que es de tipo FlyingMachine .

b) El método letTheMachinesFly también funciona sin ningún cambio (!) cuando agrega una nueva clase, por ejemplo, PropellerPlane :

public void letTheMachinesFly(List<FlyingMachine> flyingMachines) {
        for (FlyingMachine flyingMachine : flyingMachines) {
            flyingMachine.fly();
        }
    }
}

Ese es el poder del polimorfismo. Puedes implementar el principio abierto-cerrado con él.

Funciones virtuales

Los métodos virtuales son métodos en Java que son no estáticos y sin la palabra clave Final en primer plano. Todos los métodos por defecto son virtuales en Java. Los métodos virtuales desempeñan funciones importantes en el polimorfismo porque las clases secundarias en Java pueden anular los métodos de sus clases primarias si la función que se reemplaza no es estática y tiene la misma firma de método.

Hay, sin embargo, algunos métodos que no son virtuales. Por ejemplo, si el método se declara privado o con la palabra clave final, entonces el método no es Virtual.

Considere el siguiente ejemplo modificado de herencia con métodos virtuales de esta publicación de StackOverflow ¿Cómo funcionan las funciones virtuales en C # y Java? :

public class A{
    public void hello(){
        System.out.println("Hello");
    }
    
    public void boo(){
        System.out.println("Say boo");

    }
}

public class B extends A{
     public void hello(){
        System.out.println("No");
     }
    
    public void boo(){
        System.out.println("Say haha");

    }
}

Si invocamos la clase B y llamamos hola () y boo (), obtendríamos "No" y "Diga jaja" como la salida resultante porque B anula los mismos métodos de A. Aunque el ejemplo anterior es casi exactamente el mismo que Al reemplazar los métodos, es importante entender que los métodos en la clase A son todos, por defecto, virtuales.

Además, podemos implementar métodos virtuales utilizando la palabra clave abstracta. Los métodos declarados con la palabra clave "resumen" no tienen una definición de método, lo que significa que el cuerpo del método aún no está implementado. Considere el ejemplo de arriba nuevamente, excepto que el método boo () se declara abstracto:

public class A{
   public void hello(){
        System.out.println("Hello");
    }
    
    abstract void boo();
}

public class B extends A{
     public void hello(){
        System.out.println("No");
     }
    
    public void boo(){
        System.out.println("Say haha");

    }
}

Si invocamos boo () desde B, la salida seguirá siendo "Say haha", ya que B hereda el método abstracto boo () y hace que boo () muestre "Say haha".

Fuentes utilizadas y lecturas adicionales:

¿Cómo funcionan las funciones virtuales en C # y Java?

Echa un vistazo a esta gran respuesta que proporciona información mucho más completa sobre las funciones virtuales:

¿Puedes escribir funciones / métodos virtuales en Java?

Polimorfismo y diferentes tipos de anulación.

De java tutorial

La definición del diccionario de polimorfismo se refiere a un principio en biología en el que un organismo o especie puede tener muchas formas o etapas diferentes. Este principio también puede aplicarse a la programación orientada a objetos y lenguajes como el lenguaje Java. Las subclases de una clase pueden definir sus propios comportamientos únicos y, sin embargo, compartir algunas de las mismas funciones de la clase principal.

Eche un vistazo a este ejemplo para comprender los diferentes tipos de anulación.

  1. La clase base no proporciona implementación y la subclase tiene que anular el método completo - (resumen)
  2. La clase base proporciona implementación predeterminada y la subclase puede cambiar el comportamiento
  3. La super.methodName() agrega extensión a la implementación de la clase base al llamar a super.methodName() como primera declaración
  4. La clase base define la estructura del algoritmo (método de plantilla) y la subclase anulará una parte del algoritmo

fragmento de código:

import java.util.HashMap;

abstract class Game implements Runnable{

    protected boolean runGame = true;
    protected Player player1 = null;
    protected Player player2 = null;
    protected Player currentPlayer = null;
    
    public Game(){
        player1 = new Player("Player 1");
        player2 = new Player("Player 2");
        currentPlayer = player1;
        initializeGame();
    }

    /* Type 1: Let subclass define own implementation. Base class defines abstract method to force
        sub-classes to define implementation    
    */
    
    protected abstract void initializeGame();
    
    /* Type 2: Sub-class can change the behaviour. If not, base class behaviour is applicable */
    protected void logTimeBetweenMoves(Player player){
        System.out.println("Base class: Move Duration: player.PlayerActTime - player.MoveShownTime");
    }
    
    /* Type 3: Base class provides implementation. Sub-class can enhance base class implementation by calling
        super.methodName() in first line of the child class method and specific implementation later */
    protected void logGameStatistics(){
        System.out.println("Base class: logGameStatistics:");
    }
    /* Type 4: Template method: Structure of base class can't be changed but sub-class can some part of behaviour */
    protected void runGame() throws Exception{
        System.out.println("Base class: Defining the flow for Game:");    
        while (runGame) {
            /*
            1. Set current player
            2. Get Player Move
            */
            validatePlayerMove(currentPlayer);    
            logTimeBetweenMoves(currentPlayer);
            Thread.sleep(500);
            setNextPlayer();
        }
        logGameStatistics();
    }
    /* sub-part of the template method, which define child class behaviour */
    protected abstract void validatePlayerMove(Player p);
    
    protected void setRunGame(boolean status){
        this.runGame = status;
    }
    public void setCurrentPlayer(Player p){
        this.currentPlayer = p;
    }
    public void setNextPlayer(){
        if (currentPlayer == player1) {
            currentPlayer = player2;
        }else{
            currentPlayer = player1;
        }
    }
    public void run(){
        try{
            runGame();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

class Player{
    String name;
    Player(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}

/* Concrete Game implementation  */
class Chess extends Game{
    public Chess(){
        super();
    }
    public void initializeGame(){
        System.out.println("Child class: Initialized Chess game");
    }
    protected void validatePlayerMove(Player p){
        System.out.println("Child class: Validate Chess move:" + p.getName());
    }
    protected void logGameStatistics(){
        super.logGameStatistics();
        System.out.println("Child class: Add Chess specific logGameStatistics:");
    }
}
class TicTacToe extends Game{
    public TicTacToe(){
        super();
    }
    public void initializeGame(){
        System.out.println("Child class: Initialized TicTacToe game");
    }
    protected void validatePlayerMove(Player p){
        System.out.println("Child class: Validate TicTacToe move:" + p.getName());
    }
}

public class Polymorphism{
    public static void main(String args[]){
        try{
        
            Game game = new Chess();
            Thread t1 = new Thread(game);
            t1.start();
            Thread.sleep(1000);
            game.setRunGame(false);
            Thread.sleep(1000);
                        
            game = new TicTacToe();
            Thread t2 = new Thread(game);
            t2.start();
            Thread.sleep(1000);
            game.setRunGame(false);
        
        }catch(Exception err){
            err.printStackTrace();
        }        
    }
}

salida:

Child class: Initialized Chess game
Base class: Defining the flow for Game:
Child class: Validate Chess move:Player 1
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Child class: Validate Chess move:Player 2
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Base class: logGameStatistics:
Child class: Add Chess specific logGameStatistics:

Child class: Initialized TicTacToe game
Base class: Defining the flow for Game:
Child class: Validate TicTacToe move:Player 1
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Child class: Validate TicTacToe move:Player 2
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Base class: logGameStatistics:


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow