Поиск…


Вступление

Полиморфизм является одним из основных концепций ООП (объектно-ориентированного программирования). Слово полиморфизма было получено из греческих слов «poly» и «morphs». Поли означает «много», а морфы означают «формы» (многие формы).

Существует два способа выполнения полиморфизма. Перегрузка метода и переопределение метода .

замечания

Interfaces - это еще один способ добиться полиморфизма в Java, помимо классового наследования. Интерфейсы определяют список методов, которые формируют API программы. Классы должны implement interface , переопределяя все его методы.

Перегрузка метода

Перегрузка метода , также известная как перегрузка функций , - это способность класса иметь несколько методов с тем же именем, если они отличаются друг от друга по количеству или типу аргументов.

Компилятор проверяет подпись метода для перегрузки метода.

Подпись метода состоит из трех вещей:

  1. Имя метода
  2. Количество параметров
  3. Типы параметров

Если эти три одинаковы для любых двух методов в классе, то компилятор выдает ошибку повторяющегося метода .

Этот тип полиморфизма называется статическим или компиляционным полиморфизмом, потому что соответствующий метод, который должен быть вызван, определяется компилятором во время компиляции на основе списка аргументов.

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

}

Это приведет к:

2
6
4.000000

Перегруженные методы могут быть статическими или нестационарными. Это также не приводит к перегрузке метода.

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

Также, если вы измените тип возвращаемого метода, мы не сможем получить его как перегрузку метода.

public class Polymorph {  

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

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

Переопределение метода

Переопределение метода - это способность подтипов переопределять (переопределять) поведение их супертипов.

В Java это переводит в подклассы, переопределяя методы, определенные в суперклассе. В Java все непримитивные переменные являются фактически references , которые схожи с указателями на местоположение фактического объекта в памяти. references имеют только один тип, который является типом, с которым они были объявлены. Однако они могут указывать на объект либо их объявленного типа, либо любого из его подтипов.

Когда метод вызывается по reference , вызывается соответствующий метод фактического объекта, на который указывают .

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

Правила, которые следует учитывать

Чтобы переопределить метод в подклассе, метод переопределения (т. Е. Один в подклассе) ДОЛЖЕН ИМЕТЬ :

  • то же имя
  • тот же тип возврата в случае примитивов (подкласс разрешен для классов, это также называется ковариантными типами возврата).
  • тот же тип и порядок параметров
  • он может вызывать только те исключения, которые объявлены в предложении throws метода суперкласса или исключениях, которые являются подклассами объявленных исключений. Он также может выбрать НЕ выбрасывать какие-либо исключения. Имена типов параметров не имеют значения. Например, void methodX (int i) аналогичен void methodX (int k)
  • Мы не можем переопределить конечные или статические методы. Единственное, что мы можем сделать, это изменить только тело метода.

Добавление поведения путем добавления классов без касания существующего кода

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

объяснение

a) Класс MakeThingsFly может работать со всем, что относится к типу FlyingMachine .

b) Метод letTheMachinesFly также работает без каких-либо изменений (!) при добавлении нового класса, например PropellerPlane :

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

Это сила полиморфизма. Вы можете реализовать с ним открытый-закрытый принцип .

Виртуальные функции

Виртуальные методы - это методы в Java, которые нестатические и без ключевого слова Final впереди. Все методы по умолчанию являются виртуальными в Java. Виртуальные методы играют важную роль в полиморфизме, потому что классы детей в Java могут переопределять методы своих родительских классов, если переопределенная функция нестатическая и имеет одну и ту же подпись метода.

Однако существуют некоторые методы, которые не являются виртуальными. Например, если метод объявлен private или с ключевым словом final, то метод не является виртуальным.

Рассмотрим следующий измененный пример наследования с Virtual Methods из этого сообщения StackOverflow. Как виртуальные функции работают на C # и 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");

    }
}

Если мы вызываем класс B и вызываем hello () и boo (), мы получим «No» и «Say haha» в качестве выходного результата, потому что B переопределяет те же методы из A. Хотя приведенный выше пример почти такой же, как метод переопределения, важно понимать, что методы в классе A - это все по умолчанию Virtual.

Кроме того, мы можем реализовать виртуальные методы, используя ключевое слово abstract. Методы, объявленные с ключевым словом «abstract», не имеют определения метода, то есть тело метода еще не реализовано. Рассмотрим пример сверху, за исключением того, что метод boo () объявлен абстрактным:

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

    }
}

Если мы будем вызывать boo () из B, выход будет по-прежнему «Say haha», так как B наследует абстрактный метод boo () и делает вывод boo () «Say haha».

Используемые источники и дальнейшие чтения:

Как виртуальные функции работают на C # и Java?

Ознакомьтесь с этим замечательным ответом, в котором содержится более полная информация о виртуальных функциях:

Можете ли вы писать виртуальные функции / методы на Java?

Полиморфизм и различные типы переопределения

Из учебника java

Словариское определение полиморфизма относится к принципу в биологии, в котором организм или вид могут иметь много разных форм или стадий. Этот принцип также может быть применен к объектно-ориентированному программированию и языкам, таким как язык Java. Подклассы класса могут определять свое собственное уникальное поведение и совместно использовать одни и те же функциональные возможности родительского класса.

Взгляните на этот пример, чтобы понять различные типы переопределения.

  1. Базовый класс не обеспечивает реализацию, а подкласс должен переопределить полный метод - (аннотация)
  2. Базовый класс обеспечивает реализацию по умолчанию, а подкласс может изменять поведение
  3. super.methodName() добавляет расширение к реализации базового класса, вызывая super.methodName() как первый оператор
  4. Базовый класс определяет структуру алгоритма (метод Template), а подкласс будет переопределять часть алгоритма

фрагмент кода:

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

выход:

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow