JavaRush /Blog Java /Random-PL /Polimorfizm w Javie

Polimorfizm w Javie

Opublikowano w grupie Random-PL
Pytania dotyczące OOP są integralną częścią rozmowy technicznej na stanowisko programisty Java w firmie IT. W tym artykule porozmawiamy o jednej z zasad OOP - polimorfizmie. Skoncentrujemy się na aspektach, o które często pyta się podczas rozmów kwalifikacyjnych, a także podamy małe przykłady dla przejrzystości.

Co to jest polimorfizm?

Polimorfizm to zdolność programu do identycznego używania obiektów o tym samym interfejsie bez informacji o konkretnym typie tego obiektu. Jeśli w ten sposób odpowiesz na pytanie czym jest polimorfizm, najprawdopodobniej zostaniesz poproszony o wyjaśnienie, co masz na myśli. Po raz kolejny, nie zadając szeregu dodatkowych pytań, uporządkuj wszystko przed osobą przeprowadzającą rozmowę kwalifikacyjną.

Polimorfizm w Javie podczas wywiadu - 1
Zacznijmy od tego, że podejście OOP polega na budowaniu programu w Javie w oparciu o interakcję obiektów bazujących na klasach. Zajęcia to gotowe rysunki (szablony), według których będą tworzone obiekty w programie. Co więcej, klasa zawsze ma określony typ, który przy dobrym stylu programowania „określa” swój cel po nazwie. Ponadto można zauważyć, że ponieważ Java jest językiem silnie typowanym, kod programu zawsze musi wskazywać typ obiektu podczas deklarowania zmiennych. Dodaj do tego, że ścisłe typowanie zwiększa bezpieczeństwo kodu i niezawodność programu oraz pozwala zapobiec błędom związanym z niekompatybilnością typów (na przykład próbą podzielenia ciągu przez liczbę) na etapie kompilacji. Kompilator musi oczywiście „znać” zadeklarowany typ – może to być klasa z JDK lub taka, którą sami stworzyliśmy. Proszę zwrócić uwagę ankieterowi, że pracując z kodem programu możemy wykorzystywać nie tylko obiekty typu, który przypisaliśmy podczas deklaracji, ale także ich potomków. To ważna kwestia: wiele typów możemy traktować tak, jakby były jednym (o ile te typy pochodzą od typu podstawowego). Oznacza to również, że deklarując zmienną typu nadklasowego, możemy przypisać jej wartość jednego z jej potomków. Osoba przeprowadzająca rozmowę będzie zadowolona, ​​jeśli podasz przykład. Wybierz jakiś obiekt, który może być wspólny (bazowy) dla grupy obiektów i odziedzicz z niego kilka klas. Klasa bazowa:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + „Tańczę jak wszyscy”.);
    }

    @Override
    public String toString() {
        return "Я " + name + ", Dla mnie " + age + "lata". ;
    }
}
W potomkach zastąp metodę klasy bazowej:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// przesłonięcie metody klasy bazowej
    @Override
    public void dance() {
        System.out.println( toString() + „Tańczę elektryczne boogie!”);
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// przesłonięcie metody klasy bazowej
    @Override
    public void dance(){
        System.out.println(toString() + "Tańczę breakdance!");
    }
}
Przykład polimorfizmu w Javie i wykorzystania obiektów w programie:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer(„Aleksiej”, 19);// rzutowanie w górę na typ podstawowy
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // rzutowanie w górę na typ podstawowy

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// wywołanie metody polimorficznej
        }
    }
}
mainPokaż w kodzie metody, co jest w liniach:
Dancer breakDanceDancer = new BreakDankDancer(„Aleksiej”, 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
Zadeklarowaliśmy zmienną typu nadklasy i przypisaliśmy jej wartość jednego z potomków. Najprawdopodobniej zostaniesz zapytany, dlaczego kompilator nie będzie narzekał na niezgodność typów zadeklarowanych po lewej i prawej stronie znaku przypisania, ponieważ Java ma ścisłe typowanie. Wyjaśnij, że działa tutaj konwersja typu w górę - odwołanie do obiektu jest interpretowane jako odwołanie do klasy bazowej. Co więcej, kompilator, napotkawszy w kodzie taką konstrukcję, robi to automatycznie i niejawnie. Na podstawie przykładowego kodu można wykazać, że typ klasy zadeklarowany po lewej stronie znaku przypisania Dancerma kilka form (typów) zadeklarowanych po prawej stronie BreakDankDancer, ElectricBoogieDancer. Każdy z formularzy może mieć swoje własne, unikalne zachowanie dla wspólnej funkcjonalności zdefiniowanej w metodzie nadklasy dance. Oznacza to, że metoda zadeklarowana w nadklasie może być zaimplementowana w inny sposób w jej potomkach. W tym przypadku mamy do czynienia z nadpisywaniem metod i właśnie to powoduje powstawanie różnorodnych form (zachowań). Można to zobaczyć uruchamiając kod głównej metody wykonania: Dane wyjściowe programu Nazywam się Anton, mam 18 lat. Tańczę jak wszyscy. Jestem Aleksiej, mam 19 lat. Tańczę breakdance! Jestem Igor, mam 20 lat. Tańczę elektryczne boogie! Jeśli nie zastosujemy przesłaniania w potomkach, nie uzyskamy innego zachowania. BreakDankDancerNa przykład, jeśli skomentujemy ElectricBoogieDancermetodę naszych zajęć dance, wynik programu będzie następujący: Jestem Anton, mam 18 lat. Tańczę jak wszyscy. Jestem Aleksiej, mam 19 lat. Tańczę jak wszyscy. Jestem Igor, mam 20 lat. Tańczę jak wszyscy. a to oznacza, że ​​po prostu nie ma sensu tworzyć nowych BreakDankDancerklas ElectricBoogieDancer. Jaka jest dokładnie zasada polimorfizmu Java? Gdzie jest ukryte używanie obiektu w programie bez znajomości jego konkretnego typu? W naszym przykładzie jest to wywołanie metody d.dance()na obiekcie dtypu Dancer. Polimorfizm Java oznacza, że ​​program nie musi wiedzieć, jakiego typu będzie obiekt BreakDankDancerlub obiekt ElectricBoogieDancer. Najważniejsze, że jest potomkiem klasy Dancer. A jeśli mówimy o potomkach, należy zauważyć, że dziedziczenie w Javie to nie tylko extends, ale także implements. Nadszedł czas, aby pamiętać, że Java nie obsługuje dziedziczenia wielokrotnego - każdy typ może mieć jednego rodzica (nadklasę) i nieograniczoną liczbę potomków (podklas). Dlatego interfejsy służą do dodawania wielu funkcji do klas. Interfejsy ograniczają łączenie obiektów z obiektem nadrzędnym w porównaniu z dziedziczeniem i są bardzo szeroko stosowane. W Javie interfejs jest typem referencyjnym, więc program może zadeklarować typ jako zmienną typu interfejsu. To dobry moment, aby dać przykład. Stwórzmy interfejs:
public interface Swim {
    void swim();
}
Dla jasności weźmy różne i niepowiązane ze sobą obiekty i zaimplementujmy w nich interfejs:
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+„Pływam z nadmuchiwanym kółkiem”.);
    }

    @Override
    public String toString() {
        return "Я " + name + ", Dla mnie " + age + "lata".;
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println(„Jestem rybą” + name + „. Pływam, poruszając płetwami”.);

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println(„Okręt podwodny płynie, obracając śmigła z dużą prędkością” + speed + "węzły".);
    }
}
Metoda main:
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("wieloryb");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
Wynik wykonania metody polimorficznej zdefiniowanej w interfejsie pozwala nam zobaczyć różnice w zachowaniu typów, które implementują ten interfejs. Polegają one na różnych wynikach wykonania metody swim. Po przestudiowaniu naszego przykładu osoba przeprowadzająca wywiad może zapytać dlaczego podczas wykonywania kodumain
for (Swim s : swimmers) {
            s.swim();
}
Czy metody zdefiniowane w tych klasach są wywoływane dla naszych obiektów? Jak wybrać żądaną implementację metody podczas wykonywania programu? Aby odpowiedzieć na te pytania, musimy porozmawiać o późnym (dynamicznym) wiązaniu. Przez wiązanie rozumiemy ustanowienie połączenia pomiędzy wywołaniem metody a jej konkretną implementacją w klasach. Zasadniczo kod określa, która z trzech metod zdefiniowanych w klasach zostanie wykonana. Java domyślnie używa późnego wiązania (w czasie wykonywania, a nie w czasie kompilacji, jak ma to miejsce w przypadku wczesnego wiązania). Oznacza to, że podczas kompilacji kodu
for (Swim s : swimmers) {
            s.swim();
}
kompilator nie wie jeszcze, z której klasy pochodzi kod Humani Fishczy Uboatzostanie wykonany w formacie swim. Zostanie to określone dopiero podczas wykonywania programu, dzięki mechanizmowi dynamicznej wysyłki - sprawdzaniu typu obiektu w trakcie wykonywania programu i wybieraniu pożądanej implementacji metody dla tego typu. Jeśli zostaniesz zapytany, jak to jest zaimplementowane, możesz odpowiedzieć, że podczas ładowania i inicjowania obiektów JVM buduje tabele w pamięci i w nich kojarzy zmienne z ich wartościami, a obiekty z ich metodami. Co więcej, jeśli obiekt jest dziedziczony lub implementuje interfejs, najpierw sprawdzana jest obecność w jego klasie przesłoniętych metod. Jeśli takowe istnieją, są one powiązane z tym typem, jeśli nie, przeszukiwana jest metoda zdefiniowana w klasie o jeden poziom wyżej (w rodzicu) i tak dalej aż do korzenia w wielopoziomowej hierarchii. Mówiąc o polimorfizmie w OOP i jego implementacji w kodzie programu, zauważamy, że dobrą praktyką jest stosowanie abstrakcyjnych opisów do definiowania klas bazowych za pomocą klas abstrakcyjnych, a także interfejsów. Praktyka ta opiera się na wykorzystaniu abstrakcji - izolowaniu typowych zachowań i właściwości i zamykaniu ich w klasie abstrakcyjnej lub izolowaniu tylko typowych zachowań - w takim przypadku tworzymy interfejs. Budowanie i projektowanie hierarchii obiektów w oparciu o interfejsy i dziedziczenie klas jest warunkiem koniecznym do spełnienia zasady polimorfizmu OOP. Odnosząc się do kwestii polimorfizmu i innowacji w Javie, możemy wspomnieć, że tworząc klasy i interfejsy abstrakcyjne, począwszy od Javy 8, istnieje możliwość napisania domyślnej implementacji metod abstrakcyjnych w klasach bazowych za pomocą słowa kluczowego default. Na przykład:
public interface Swim {
    default void swim() {
        System.out.println(„Po prostu unoszący się”);
    }
}
Czasami mogą zapytać o wymagania dotyczące deklarowania metod w klasach bazowych, aby nie naruszyć zasady polimorfizmu. Tutaj wszystko jest proste: metody te nie powinny być statyczne , prywatne i ostateczne . Private sprawia, że ​​metoda jest dostępna tylko w klasie i nie można jej zastąpić w potomku. Statyczny sprawia, że ​​metoda staje się właściwością klasy, a nie obiektu, więc metoda nadklasy będzie zawsze wywoływana. Final sprawi, że metoda będzie niezmienna i ukryta przed jej spadkobiercami.

Co daje nam polimorfizm w Javie?

Najprawdopodobniej pojawi się również pytanie, co daje nam zastosowanie polimorfizmu. Tutaj możesz odpowiedzieć krótko, bez wchodzenia zbyt głęboko w chwasty:
  1. Umożliwia zamianę implementacji obiektów. Na tym opiera się testowanie.
  2. Zapewnia możliwość rozbudowy programu - znacznie łatwiej jest stworzyć fundament na przyszłość. Dodawanie nowych typów w oparciu o istniejące to najczęstszy sposób na rozszerzenie funkcjonalności programów napisanych w stylu OOP.
  3. Umożliwia łączenie obiektów o wspólnym typie lub zachowaniu w jedną kolekcję lub tablicę i jednolite zarządzanie nimi (jak w naszych przykładach, zmuszanie wszystkich do tańca – metoda dancelub pływanie – metoda swim).
  4. Elastyczność podczas tworzenia nowych typów: możesz zaimplementować metodę z rodzica lub zastąpić ją w podrzędnym.

Pożegnalne słowa na podróż

Zasada polimorfizmu jest bardzo ważnym i szerokim tematem. Obejmuje prawie połowę OOP Java i znaczną część podstaw języka. Podczas rozmowy kwalifikacyjnej nie uda Ci się uniknąć określenia tej zasady. Nieznajomość lub niezrozumienie tego najprawdopodobniej zakończy rozmowę kwalifikacyjną. Dlatego nie zwlekaj, sprawdź swoją wiedzę przed sprawdzianem i odśwież ją, jeśli zajdzie taka potrzeba.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION