Suche…


Einführung

Polymorphismus ist eines der wichtigsten OOP-Konzepte (objektorientierte Programmierung). Das Polymorphismuswort wurde von den griechischen Wörtern "Poly" und "Morphen" abgeleitet. Poly bedeutet "viele" und Morphen bedeutet "Formen" (viele Formen).

Es gibt zwei Möglichkeiten, Polymorphie durchzuführen. Method Overloading und Method Overriding .

Bemerkungen

Interfaces sind ein weiterer Weg, um Polymorphismus in Java zu erreichen, abgesehen von der Klassenvererbung. Schnittstellen definieren eine Liste von Methoden, die die API des Programms bilden. Klassen müssen eine interface implement interface indem sie alle ihre Methoden überschreiben.

Methodenüberladung

Methodenüberladung , auch Funktionsüberladung genannt , ist die Fähigkeit einer Klasse, mehrere Methoden mit demselben Namen zu haben, vorausgesetzt, sie unterscheiden sich entweder in der Anzahl oder im Typ der Argumente.

Der Compiler überprüft die Methodensignatur auf Methodenüberladung.

Die Methodensignatur besteht aus drei Dingen:

  1. Methodenname
  2. Anzahl der Parameter
  3. Arten von Parametern

Wenn diese drei für zwei Methoden in einer Klasse gleich sind, gibt der Compiler einen doppelten Methodenfehler aus .

Diese Art von Polymorphismus wird als statischer Polymorphismus oder als Kompilierzeitpolymorphismus bezeichnet, da die geeignete aufzurufende Methode vom Compiler während der Kompilierzeit auf Grundlage der Argumentliste festgelegt wird.

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));
    }

}

Dies führt zu:

2
6
4.000000

Überladene Methoden können statisch oder nicht statisch sein. Dies wirkt sich auch nicht auf das Überladen von Methoden aus.

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
    }
}

Wenn Sie den Rückgabetyp der Methode ändern, können wir sie nicht als Methodenüberladung abrufen.

public class Polymorph {  

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

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

Überschreiben der Methode

Das Überschreiben von Methoden ist die Fähigkeit von Subtypen, das Verhalten ihrer Supertypen neu zu definieren (zu überschreiben).

In Java wird dies in Unterklassen übersetzt, die die in der Oberklasse definierten Methoden überschreiben. In Java sind alle nicht primitiven Variablen tatsächlich references , die mit Zeigern auf die Position des tatsächlichen Objekts im Arbeitsspeicher verwandt sind. Die references nur einen Typ, den Typ, mit dem sie deklariert wurden. Sie können jedoch auf ein Objekt des deklarierten Typs oder eines seiner Subtypen zeigen.

Wenn eine Methode für eine reference aufgerufen wird, wird die entsprechende Methode des tatsächlichen Objekts aufgerufen, auf das gezeigt wird .

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
    }
}

Regeln, die zu beachten sind

Um eine Methode in der Unterklasse zu überschreiben, MUSS die überschreibende Methode (dh die in der Unterklasse) HABEN :

  • gleicher Name
  • gleicher Rückgabetyp bei Primitiven (eine Unterklasse ist für Klassen zulässig, dies wird auch als kovariante Rückgabetypen bezeichnet)
  • gleicher Typ und Reihenfolge der Parameter
  • Es kann nur die Ausnahmen werfen, die in der Throws-Klausel der Methode der Superklasse deklariert sind, oder Ausnahmen, die Unterklassen der deklarierten Ausnahmen sind. Es kann sich auch entscheiden, KEINE Ausnahme auszulösen. Die Namen der Parametertypen spielen keine Rolle. Zum Beispiel ist void methodX (int i) mit void methodX (int k) identisch.
  • Endgültige oder statische Methoden können nicht überschrieben werden. Nur, dass wir nur den Methodenkörper ändern können.

Hinzufügen von Verhalten durch Hinzufügen von Klassen, ohne vorhandenen Code zu berühren

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");
    }
}

Erläuterung

a) Die MakeThingsFly Klasse kann mit FlyingMachine Typen vom Typ FlyingMachine .

b) Die Methode letTheMachinesFly funktioniert auch ohne Änderung (!), wenn Sie eine neue Klasse hinzufügen, beispielsweise PropellerPlane :

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

Das ist die Kraft des Polymorphismus. Sie können das Open-Closed-Prinzip damit implementieren.

Virtuelle Funktionen

Virtuelle Methoden sind Methoden in Java, die nicht statisch sind und das Schlüsselwort Final nicht enthalten. Alle Methoden sind standardmäßig in Java virtuell. Virtuelle Methoden spielen eine wichtige Rolle in Polymorphism, da untergeordnete Klassen in Java die Methoden ihrer übergeordneten Klassen überschreiben können, wenn die zu überschreibende Funktion nicht statisch ist und dieselbe Methodensignatur aufweist.

Es gibt jedoch einige Methoden, die nicht virtuell sind. Wenn die Methode beispielsweise als privat oder mit dem Schlüsselwort final deklariert ist, ist die Methode nicht virtuell.

Betrachten Sie das folgende modifizierte Beispiel für die Vererbung mit virtuellen Methoden aus diesem StackOverflow-Beitrag. Wie funktionieren virtuelle Funktionen in C # und 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");

    }
}

Wenn wir die Klasse B aufrufen und hello () und boo () aufrufen, erhalten wir als Ergebnis die Ausgabe "No" und "Say haha", da B die gleichen Methoden von A überschreibt Wenn Sie die Methode überschreiben, ist es wichtig zu verstehen, dass die Methoden in Klasse A standardmäßig alle Virtual sind.

Außerdem können wir virtuelle Methoden mit dem abstrakten Schlüsselwort implementieren. Methoden, die mit dem Schlüsselwort "abstract" deklariert werden, haben keine Methodendefinition, dh der Körper der Methode ist noch nicht implementiert. Betrachten Sie das Beispiel von oben noch einmal, außer die boo () -Methode ist als abstrakt deklariert:

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");

    }
}

Wenn wir boo () von B aufrufen, ist die Ausgabe immer noch "Say haha", da B die abstrakte Methode boo () erbt und boo () die Ausgabe "Say haha" ausgibt.

Verwendete Quellen und weitere Lesungen:

Wie funktionieren virtuelle Funktionen in C # und Java?

Sehen Sie sich diese großartige Antwort an, die umfassendere Informationen zu virtuellen Funktionen enthält:

Können Sie virtuelle Funktionen / Methoden in Java schreiben?

Polymorphismus und verschiedene Arten des Überschreibens

Aus dem Java- Tutorial

Die Wörterbuchdefinition für Polymorphismus bezieht sich auf ein Prinzip in der Biologie, bei dem ein Organismus oder eine Spezies viele verschiedene Formen oder Stufen haben kann. Dieses Prinzip kann auch auf objektorientierte Programmierung und Sprachen wie die Java-Sprache angewendet werden. Unterklassen einer Klasse können ihre eigenen eindeutigen Verhalten definieren und dabei einige Funktionen der übergeordneten Klasse gemeinsam nutzen.

Sehen Sie sich dieses Beispiel an, um die verschiedenen Arten des Überschreibens zu verstehen.

  1. Die Basisklasse stellt keine Implementierung bereit und die Unterklasse muss die vollständige Methode überschreiben. (Abstrakt)
  2. Die Basisklasse stellt eine Standardimplementierung bereit und eine Unterklasse kann das Verhalten ändern
  3. Die Unterklasse fügt der Basisklassenimplementierung eine Erweiterung hinzu, indem sie super.methodName() als erste Anweisung super.methodName()
  4. Die Basisklasse definiert die Struktur des Algorithmus (Template-Methode) und die Unterklasse überschreibt einen Teil des Algorithmus

Code-Auszug:

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();
        }        
    }
}

Ausgabe:

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow