— Амиго, харесваш ли китове?

"Китове? Не, никога не съм чувал за тях."

„Това е като крава, само че е по-голямо и плува. Между другото, китовете са произлезли от крави. Ъъъ, or поне споделят общ прародител. Няма meaning.“

Полиморфизъм и преодоляване - 1

„Слушайте. Искам да ви разкажа за друг много мощен инструмент на ООП: полиморфизъм . Той има четири функции.“

1) Замяна на метода.

Представете си, че сте написали клас "Крава" за игра. Има много членски променливи и методи. Обектите от този клас могат да правят различни неща: да ходят, да ядат, да спят. Кравите също бият звънец, когато ходят. Да приемем, че сте внедрor всичко в класа до най-малкия детайл.

Полиморфизъм и преодоляване - 2

Тогава изведнъж клиентът казва, че иска да пусне ново ниво на играта, където всички действия се развиват в морето, а главният герой е кит.

Започнахте да проектирате класа Whale и осъзнахте, че той е само малко по-различен от класа Cow. И двата класа използват много сходна логика и вие решавате да използвате наследяване.

Класът Cow е идеално подходящ да бъде родителски клас: той вече има всички необходими променливи и методи. Всичко, което трябва да направите, е да добавите способността на кита да плува. Но има проблем: вашият кит има крака, рога и камбана. В крайна сметка класът Cow реализира тази функционалност. Какво можеш да направиш?

Полиморфизъм и преодоляване - 3

Замяната на метод идва на помощ. Ако наследим метод, който не прави точно това, от което се нуждаем в нашия нов клас, можем да заменим метода с друг.

Полиморфизъм и преодоляване - 4

Как става това? В нашия наследствен клас ние декларираме метода, който искаме да променим (със същия подпис на метода като в родителския клас) . След това пишем нов code за метода. Това е. Сякаш старият метод на родителския клас не съществува.

Ето How работи:

Код Описание
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Тук дефинираме два класа:  Cow и  WhaleWhaleнаследява  Cow.

Класът  Whale замества  printName();метода.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Този code показва на екрана « Аз съм крава ».
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Този code показва на екрана « Аз съм кит ».

След като наследи Cowи замени printName, Whaleкласът всъщност има следните данни и методи:

Код Описание
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Не знаем нищо за нито един стар метод.

— Честно казано, това очаквах.

2) Но това не е всичко.

„Да предположим, че  Cow класът има  printAllметод, който извиква другите два метода. Тогава codeът ще работи така:“

Екранът ще покаже:
Аз съм бял,
аз съм кит

Код Описание
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Екранът ще покаже:
Аз съм бял,
аз съм кит

Имайте предвид, че когато методът printAll () на класа Cow се извика на обект Whale, ще се използва методът printName() на Whale, а не този на Cow.

Важното е не класът, в който е написан методът, а по-скоро типът (класът) на обекта, на който се извиква методът.

"Виждам."

„Можете да наследявате и отменяте само нестатичните методи. Статичните методи не се наследяват и следователно не могат да бъдат отменяни.“

Ето How изглежда класът Whale, след като приложим наследяване и заменим методите:

Код Описание
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Ето How изглежда класът Whale, след като приложим наследяване и сменим метода. Не знаем нищо за нито един стар printNameметод.

3) Отливане на типа.

Ето още по-интересен момент. Тъй като един клас наследява всички методи и данни на своя родителски клас, обект от този клас може да бъде рефериран от променливи на родителския клас (и родителя на родителя и т.н., до класа Object). Помислете за този пример:

Код Описание
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
Екранът ще покаже:
Аз съм бял.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
Екранът ще покаже:
Аз съм бял.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
Екранът ще покаже:
Whale@da435a.
Методът toString() е наследен от класа Object.

„Хубави неща. Но защо ви трябва това?“

„Това е ценна функция. По-късно ще разберете, че е много, много ценна.“

4) Късно подвързване (динамично изпращане).

Ето How изглежда:

Код Описание
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Екранът ще покаже:
Аз съм кит.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
Екранът ще покаже:
Аз съм кит.

Обърнете внимание, че не типът на променливата определя кой конкретен метод printName извикваме (този на класа Cow or Whale), а по-скоро типът на обекта, към който се отнася променливата.

Променливата Cow съхранява препратка към обект Whale и методът printName , дефиниран в класа Whale , ще бъде извикан.

— Е, не са добавor това за по-голяма яснота.

„Да, не е толкова очевидно. Запомнете това важно правило:“

Наборът от методи, които можете да извикате на променлива, се определя от типа на променливата. Но кой специфичен метод/имплементация се извиква се определя от типа/класа на обекта, към който се отнася променливата.

"Ще опитам."

„Ще се натъквате на това постоянно, така че бързо ще го разберете и никога няма да забравите.“

5) Отливане на типа.

Кастингът работи по различен начин за референтни типове, т.е. класове, отколкото за примитивни типове. Разширяващите и стесняващите преобразувания обаче се прилагат и за референтни типове. Помислете за този пример:

Разширяване на преобразуването Описание
Cow cow = new Whale();

Класическо разширяващо се преобразуване. Сега можете да извиквате само методи, дефинирани в класа Cow на обекта Whale.

Компилаторът ще ви позволи да използвате променливата cow само за извикване на онези методи, дефинирани от типа Cow.

Стесняване на преобразуването Описание
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
Класическо стесняващо преобразуване с проверка на типа. Променливата cow от тип Cow съхранява препратка към обект Whale.
Проверяваме дали това е така и след това извършваме (разширяващо) преобразуване на типа. Това се нарича още тип кастинг .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
Можете също така да извършите стесняващо преобразуване на референтен тип без проверка на типа на обекта.
В този случай, ако променливата крава сочи към нещо различно от обект Whale, ще бъде хвърлено изключение (InvalidClassCastException).

6) А сега нещо вкусно. Извикване на оригиналния метод.

Понякога, когато заменяте наследен метод, не искате да го замените изцяло. Понякога просто искате да добавите малко към него.

В този случай наистина искате codeът на новия метод да извика същия метод, но в базовия клас. И Java ви позволява да направите това. Ето How се прави  super.method():.

Ето няколко примера:

Код Описание
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Екранът ще покаже:
Аз съм бял.
Това е невярно: Аз съм крава,
аз съм кит

"Хм. Е, това беше няHowъв урок. Ушите ми на робота почти се стопиха."

„Да, това не са прости неща. Това е един от най-трудните материали, които ще срещнете. Професорът обеща да предостави връзки към материали от други автори, така че ако все още не разбирате нещо, можете да попълните пропуски."