CodeGym /Java блог /Случаен /Полиморфизъм на Java
John Squirrels
Ниво
San Francisco

Полиморфизъм на Java

Публикувано в групата
Въпросите, свързани с ООП, са неразделна част от техническото интервю за позиция Java разработчик в ИТ компания. В тази статия ще говорим за един принцип на ООП – полиморфизма. Ще се съсредоточим върху аспектите, които често се задават по време на интервютата, и ще дадем няколко примера за яснота.

Какво е полиморфизъм в Java?

Полиморфизмът е способността на програмата да третира обекти със същия интерфейс по един и същи начин, без информация за конкретния тип на обекта. Ако отговорите на въпрос Howво е полиморфизъм, най-вероятно ще бъдете помолени да обясните Howво имате предвид. Без да предизвиквате куп допълнителни въпроси, изложете всичко на интервюиращия още веднъж. Време за интервю: полиморфизъм в Java - 1Можете да започнете с факта, че OOP подходът включва изграждане на Java програма, базирана на взаимодействието между обекти, които са базирани на класове. Класовете са предварително написани чертежи (шаблони), използвани за създаване на обекти в програмата. Освен това класът винаги има специфичен тип, който при добър стил на програмиране има име, което подсказва целта му. Освен това може да се отбележи, че тъй като Java е строго типизирана, програмният code трябва винаги да указва тип обект, когато се декларират променливи. Добавете към това факта, че стриктното въвеждане подобрява сигурността и надеждността на codeа и прави възможно, дори при компилация, предотвратяването на грешки, дължащи се на типове несъвместимост (например опит за разделяне на низ на число). Естествено, компилаторът трябва да "знае" декларирания тип – може да бъде клас от JDK or такъв, който сме създали сами. Обърнете внимание на интервюиращия, че нашият code може да използва не само обектите от типа, посочен в декларацията, но и неговите наследници.Това е важен момент: можем да работим с много различни типове като един тип (при condition, че тези типове са извлечени от базов тип). Това също означава, че ако декларираме променлива, чийто тип е суперклас, тогава можем да присвоим екземпляр на един от неговите наследници на тази променлива. Интервюиращият ще хареса, ако дадете пример. Изберете клас, който може да бъде споделен от (основен клас за) няколко класа и накарайте няколко от тях да го наследят. Базов клас:

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() + " I dance like everyone else.");
    }

    @Override
    public String toString() {
        Return "I'm " + name + ". I'm " + age + " years old.";
    }
}
В подкласовете заменете метода на базовия клас:

public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
Пример за полиморфизъм и How тези обекти могат да се използват в програма:

public class Main {

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

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type 
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
В основния метод покажете, че линиите

Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
декларирайте променлива на суперклас и й присвоете обект, който е екземпляр на един от неговите наследници. Най-вероятно ще бъдете попитани защо компилаторът не прескача поради несъответствието на типовете, декларирани от лявата и дясната страна на оператора за присвояване - в крайна сметка Java е строго типизирана. Обяснете, че тук работи преобразуване на разширяващ тип — препратка към обект се третира като препратка към неговия базов клас. Нещо повече, след като е срещнал такава конструкция в codeа, компилаторът извършва преобразуването автоматично и имплицитно. Примерният code показва, че типът, деклариран от лявата страна на оператора за присвояване ( Dancer ), има множество форми (типове), които са декларирани от дясната страна ( Breakdancer , ElectricBoogieDancer). Всяка форма може да има свое собствено уникално поведение по отношение на общата функционалност, дефинирана в суперкласа ( методът на танца ). Това означава, че метод, деклариран в суперклас, може да бъде имплементиран по различен начин в своите наследници. В този случай имаме работа с отмяна на метода, което е точно това, което създава множество форми (поведения). Това може да се види чрез стартиране на codeа в главния метод: Програмен изход: Аз съм Фред. На 18 години съм. Танцувам като всички останали. Аз съм Джей. Аз съм на 19 години. Аз танцувам брейк! Аз съм Марсия. Аз съм на 20 години. Аз танцувам електрик буги! Ако не заменим метода в подкласовете, тогава няма да получим различно поведение. Например,ElectricBoogieDancer класове, тогава резултатът от програмата ще бъде следният: Аз съм Фред. На 18 години съм. Танцувам като всички останали. Аз съм Джей. Аз съм на 19 години. Танцувам като всички останали. Аз съм Марсия. Аз съм на 20 години. Танцувам като всички останали. А това означава, че просто няма смисъл да се създават класове Breakdancer и ElectricBoogieDancer . Къде по-конкретно се проявява принципът на полиморфизма? Къде се използва обект в програмата, без да се знае конкретният му тип? В нашия пример това се случва, когато методът dance() се извика на обекта Dancer d . В Java полиморфизмът означава, че програмата не трябва да знае дали обектът е aБрейкдансър or ElectricBoogieDancer . Важното е, че е потомък на класа Dancer . И ако споменавате потомци, трябва да отбележите, че наследяването в Java не само разширява , но също така прилага. Тук е моментът да споменем, че Java не поддържа множествено наследяване — всеки тип може да има един родител (суперклас) и неограничен брой потомци (подкласове). Съответно, интерфейсите се използват за добавяне на множество набори от функции към класове. В сравнение с подкласовете (наследяване), интерфейсите са по-малко свързани с родителския клас. Те се използват много широко. В Java интерфейсът е референтен тип, така че програмата може да декларира променлива от типа интерфейс. Сега е време да дадем пример. Създайте интерфейс:

public interface CanSwim {
    void swim();
}
За по-голяма яснота ще вземем различни несвързани класове и ще ги накараме да реализират интерфейса:

public class Human implements CanSwim {
    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()+" I swim with an inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}
 
public class Fish implements CanSwim {
    private String name;

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

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

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

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
основен метод:

public class Main {

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

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
Резултатите, извикващи полиморфен метод, дефиниран в интерфейс, ни показват разликите в поведението на типовете, които имплементират този интерфейс. В нашия случай това са различните струни, показани чрез метода на плуване . След като проучи нашия пример, интервюиращият може да попита защо изпълнява този code в основния метод

for (Swim s : swimmers) {
            s.swim();        
}
причинява извикването на основните методи, дефинирани в нашите подкласове? Как се избира желаното изпълнение на метода, докато програмата работи? За да отговорите на тези въпроси, трябва да обясните късното (динамично) свързване. Свързването означава установяване на съпоставяне между извикване на метод и неговата специфична реализация на клас. По същество codeът определя кой от трите метода, дефинирани в класовете, ще бъде изпълнен. Java използва късно свързване по подразбиране, т.е. свързването се случва по време на изпълнение, а не по време на компorране, Howъвто е случаят с ранното свързване. Това означава, че когато компилаторът компorра този code

for (Swim s : swimmers) {
            s.swim();        
}
не знае кой клас ( Човек , Риба or Uboat ) има codeа, който ще бъде изпълнен, когато плуваметодът се нарича. Това се определя само когато програмата се изпълнява, благодарение на механизма за динамично свързване (проверка на типа на обекта по време на изпълнение и избор на правилната реализация за този тип). Ако ви попитат How се реализира това, можете да отговорите, че при зареждане и инициализиране на обекти, JVM изгражда таблици в паметта и свързва променливи с техните стойности и обекти с техните методи. При това, ако даден клас е наследен or имплементира интерфейс, първият ред на работа е да се провери наличието на заменени методи. Ако има такива, те са обвързани с този тип. Ако не, търсенето на съвпадащ метод се премества към класа, който е една стъпка по-висок (родител) и така нататък до корена в многостепенна йерархия. Когато става въпрос за полиморфизъм в ООП и внедряването му в code, отбелязваме, че е добра практика да се използват абстрактни класове и интерфейси за предоставяне на абстрактни дефиниции на базови класове. Тази практика следва от принципа на абстракцията — идентифициране на общо поведение и свойства и поставянето им в абстрактен клас or идентифициране само на общо поведение и поставянето му в интерфейс. Проектирането и създаването на йерархия на обекти, базирана на интерфейси и наследяване на класове, са необходими за прилагане на полиморфизъм. По отношение на полиморфизма и иновациите в Java, ние отбелязваме, че започвайки с Java 8, при създаването на абстрактни класове и интерфейси е възможно да се използва or идентифициране само на общо поведение и поставянето му в интерфейс. Проектирането и създаването на йерархия на обекти, базирана на интерфейси и наследяване на класове, са необходими за прилагане на полиморфизъм. По отношение на полиморфизма и иновациите в Java, ние отбелязваме, че започвайки с Java 8, при създаването на абстрактни класове и интерфейси е възможно да се използва or идентифициране само на общо поведение и поставянето му в интерфейс. Проектирането и създаването на йерархия на обекти, базирана на интерфейси и наследяване на класове, са необходими за прилагане на полиморфизъм. По отношение на полиморфизма и иновациите в Java, ние отбелязваме, че започвайки с Java 8, при създаването на абстрактни класове и интерфейси е възможно да се използваключова дума по подразбиране , за да напишете имплементация по подразбиране за абстрактни методи в базовите класове. Например:

public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
Понякога интервюиращите питат How трябва да се декларират методите в базовите класове, така че да не се нарушава принципът на полиморфизма. Отговорът е прост: тези методи не трябва да бъдат статични , лични or окончателни . Private прави метод достъпен само в рамките на клас, така че няма да можете да го замените в подклас. Static асоциира метод с класа, а не с който и да е обект, така че методът на суперкласа винаги ще бъде извикан. И final прави метод неизменен и скрит от подкласовете.

Какво ни дава полиморфизмът?

Също така най-вероятно ще бъдете попитани Howва е ползата от полиморфизма. Можете да отговорите на това накратко, без да се затъвате в косматите подробности:
  1. Това дава възможност да се заменят реализациите на класове. Тестването е изградено върху него.
  2. Той улеснява разширяването, което прави много по-лесно създаването на основа, върху която може да се надгражда в бъдеще. Добавянето на нови типове на базата на съществуващи е най-честият начин за разширяване на функционалността на ООП програмите.
  3. Позволява ви да комбинирате обекти, които споделят общ тип or поведение в една колекция or масив и да ги обработвате еднакво (Howто в нашите примери, където принудихме всички да танцуват() or плуват() :)
  4. Гъвкавост при създаване на нови типове: можете да изберете внедряването на метод от родителя or да го замените в подклас.

Някои думи за раздяла

Полиморфизмът е много важна и обширна тема. Това е предмет на почти половината от тази статия за ООП в Java и представлява добра част от основата на езика. Няма да можете да избегнете дефинирането на този принцип на интервю. Ако не го знаете or не го разбирате, интервюто вероятно ще приключи. Така че не бъдете мързеливец — преценете знанията си преди интервюто и ги опреснете, ако е необходимо.
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION