Szukaj…


Wprowadzenie

Polimorfizm jest jedną z głównych koncepcji programowania obiektowego. Słowo polimorfizm pochodzi od greckich słów „poli” i „morphs”. Poli oznacza „wiele”, a morf oznacza „formy” (wiele form).

Istnieją dwa sposoby wykonania polimorfizmu. Przeciążanie metod i zastępowanie metod .

Uwagi

Interfaces są innym sposobem na osiągnięcie polimorfizmu w Javie, oprócz dziedziczenia klasowego. Interfejsy definiują listę metod tworzących interfejs API programu. Klasy muszą implement interface , zastępując wszystkie jego metody.

Przeciążenie metody

Przeciążanie metod , znane również jako przeciążanie funkcji , to zdolność klasy do posiadania wielu metod o tej samej nazwie, pod warunkiem, że różnią się one liczbą lub rodzajem argumentów.

Kompilator sprawdza podpis metody pod kątem przeciążenia metody.

Podpis metody składa się z trzech rzeczy -

  1. Nazwa metody
  2. Liczba parametrów
  3. Rodzaje parametrów

Jeśli te trzy są takie same dla dowolnych dwóch metod w klasie, kompilator zgłasza błąd metody duplikatu .

Ten typ polimorfizmu nazywa się polimorfizmem statycznym lub kompilacyjnym w czasie kompilacji, ponieważ kompilator decyduje o odpowiedniej metodzie w czasie kompilacji na podstawie listy argumentów.

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

}

Spowoduje to:

2
6
4.000000

Przeciążone metody mogą być statyczne lub niestatyczne. Nie powoduje to również przeciążenia metody.

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

Również jeśli zmienisz typ zwracanej metody, nie będziemy w stanie uzyskać jej z powodu przeciążenia metody.

public class Polymorph {  

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

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

Metoda przesłonięcia

Zastępowanie metod to zdolność podtypów do przedefiniowania (zastąpienia) zachowania ich nadtypów.

W Javie przekłada się to na podklasy zastępujące metody zdefiniowane w superklasie. W Javie wszystkie zmienne inne niż pierwotne są w rzeczywistości references , które są podobne do wskaźników do położenia rzeczywistego obiektu w pamięci. references mają tylko jeden typ, który jest typem, w którym zostały zadeklarowane. Mogą jednak wskazywać na obiekt o zadeklarowanym typie lub dowolnym z jego podtypów.

Kiedy metoda jest wywoływana w reference , wywoływana jest odpowiednia metoda wskazywanego obiektu .

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

Zasady, o których należy pamiętać

Aby zastąpić metodę w podklasie, metoda zastępująca (tj. Ta w podklasie) MUSI MIEĆ :

  • Takie samo imię
  • ten sam typ zwracany w przypadku operacji podstawowych (podklasa jest dozwolona dla klas, jest to również znane jako typy kowariancyjne zwracane).
  • ten sam typ i kolejność parametrów
  • może zgłaszać tylko te wyjątki, które są zadeklarowane w klauzuli „throws” metody nadklasy lub wyjątki będące podklasami zadeklarowanych wyjątków. Może również zdecydować się NIE zgłaszać żadnego wyjątku. Nazwy typów parametrów nie mają znaczenia. Na przykład void methodX (int i) jest taki sam jak void methodX (int k)
  • Nie jesteśmy w stanie zastąpić metod ostatecznych ani statycznych. Jedyne, co możemy zrobić, to zmienić tylko treść metody.

Dodawanie zachowania poprzez dodawanie klas bez dotykania istniejącego kodu

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

Wyjaśnienie

a) Klasa MakeThingsFly może współpracować ze wszystkim, co jest typu FlyingMachine .

b) Metoda letTheMachinesFly działa również bez żadnych zmian (!) po dodaniu nowej klasy, na przykład PropellerPlane :

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

To jest siła polimorfizmu. Za jego pomocą możesz wdrożyć zasadę otwartego-zamkniętego .

Funkcje wirtualne

Metody wirtualne to metody w Javie, które są niestatyczne i bez słowa kluczowego Final na początku. Wszystkie metody są domyślnie wirtualne w Javie. Metody wirtualne odgrywają ważną rolę w polimorfizmie, ponieważ klasy potomne w Javie mogą przesłonić metody klas nadrzędnych, jeśli zastępowana funkcja jest niestatyczna i ma taką samą sygnaturę metody.

Istnieje jednak kilka metod, które nie są wirtualne. Na przykład, jeśli metoda jest zadeklarowana jako prywatna lub ze słowem kluczowym final, wówczas metoda nie jest wirtualna.

Rozważ następujący zmodyfikowany przykład dziedziczenia metodami wirtualnymi z tego postu StackOverflow Jak działają funkcje wirtualne w języku C # i Javie? :

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

    }
}

Jeśli wywołamy klasę B i wywołamy hello () i boo (), otrzymamy „No” i „Say haha” jako wynikowy wynik, ponieważ B zastępuje te same metody z A. Mimo że powyższy przykład jest prawie dokładnie taki sam jak nadpisując metodę, należy zrozumieć, że wszystkie metody w klasie A są domyślnie wirtualne.

Dodatkowo możemy zaimplementować metody wirtualne przy użyciu słowa kluczowego abstrakcyjnego. Metody zadeklarowane za pomocą słowa kluczowego „streszczenie” nie mają definicji metody, co oznacza, że treść metody nie jest jeszcze zaimplementowana. Ponownie rozważ przykład z góry, z tą różnicą, że metoda boo () jest zadeklarowana jako abstrakcyjna:

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

    }
}

Jeśli wywołamy boo () z B, wynikiem będzie nadal „Say haha”, ponieważ B dziedziczy abstrakcyjną metodę boo () i tworzy wynik boo () „Say haha”.

Wykorzystane źródła i dalsze odczyty:

Jak działają funkcje wirtualne w C # i Javie?

Sprawdź tę świetną odpowiedź, która daje znacznie pełniejsze informacje na temat funkcji wirtualnych:

Czy umiesz pisać wirtualne funkcje / metody w Javie?

Polimorfizm i różne typy nadpisywania

Z samouczka Java

Słownikowa definicja polimorfizmu odnosi się do zasady w biologii, w której organizm lub gatunek może mieć wiele różnych form lub stadiów. Zasadę tę można także zastosować do programowania obiektowego i języków takich jak język Java. Podklasy klasy mogą definiować własne unikalne zachowania, a jednocześnie udostępniać niektóre z tych samych funkcji klasy nadrzędnej.

Spójrz na ten przykład, aby zrozumieć różne typy zastępowania.

  1. Klasa podstawowa nie zapewnia implementacji, a podklasa musi zastąpić pełną metodę - (abstrakt)
  2. Klasa podstawowa zapewnia domyślną implementację, a podklasa może zmienić zachowanie
  3. super.methodName() dodaje rozszerzenie do implementacji klasy podstawowej, wywołując super.methodName() jako pierwszą instrukcję
  4. Klasa podstawowa określa strukturę algorytmu (metoda szablonowa), a podklasa zastąpi część algorytmu

fragment kodu:

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

wynik:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow