Към съдържанието
wannacode

Какъв е всъщност?

Проблем

Само някои могат да се зареждат

Фирмата има автопарк от обикновени и електрически коли. Всяка вечер електрическите трябва да се заредят. ElectricCar наследява Car и добавя charge(), затова всички са в един масив Car[]:

Car[] fleet = {
new Car("Шкода"),
new ElectricCar("Тесла", 35),
new Car("Дачия"),
new ElectricCar("Нисан", 60)
};
for (Car c : fleet) {
c.charge();
}

Програмата не се компилира:

„cannot find symbol: method charge()“.

От урока за полиморфизма знаеш защо: типът на променливата решава какво можеш да извикаш. c е Car, а в Car няма зареждане.

Обикновената кола наистина не може да се зареди. Трябва да питаш всяка кола: „ти електрическа ли си?“

Идеята

Питай обекта какъв е

for (Car c : fleet) {
if (c instanceof ElectricCar e) {
e.charge();
System.out.println(e.getBrand() + ": заредена до " + e.getBattery() + "%");
}
}
Изход
Тесла: заредена до 100%
Нисан: заредена до 100%

c instanceof ElectricCar e прави две неща наведнъж:

  • проверява дали обектът, към който сочи c, е ElectricCar;
  • ако е, създава променлива e от тип ElectricCar, която сочи същия обект.

През e вече можеш да викаш charge(). Колите остават същите. Само поглеждаш към обекта през по-конкретен тип.

Това се казва проверка на типа. Преходът от родителския тип към типа на наследника се казва превръщане надолу (downcast): надолу по веригата Object → Car → ElectricCar.

e съществува само вътре в if. Извън него обектът е пак просто Car.

for (Car c : fleet) {
if (c instanceof ElectricCar e) {
e.charge();
System.out.println(e.getBrand() + ": заредена до " + e.getBattery() + "%");
}
}
Стъпка по стъпка, с думи
  1. fleet е масив от Car: 2 обикновени и 2 електрически коли.
  2. c сочи Шкода: instanceof дава „не“. Тялото на if се прескача.
  3. c сочи Тесла: instanceof дава „да“. e е втора стрелка към същата кола и я зарежда.
  4. c сочи Дачия: instanceof дава „не“. Тялото на if се прескача.
  5. c сочи Нисан: instanceof дава „да“. e е втора стрелка към същата кола и я зарежда.
  6. Колите не се променят. Проверката само поглежда към обекта през по-конкретен тип.

Пробвай

Кола ли е?

Какво ще отпечата програмата?

Car c = new ElectricCar("Тесла", 35);
Car d = new Car("Шкода");
System.out.println(c instanceof Car);
System.out.println(c instanceof ElectricCar);
System.out.println(d instanceof ElectricCar);
Отговор

Идеята

Другите записи, които ще срещнеш

Шаблонът с променлива (instanceof ElectricCar e) е от Java 16. В по-стар код проверката и превръщането са отделно:

Car c = fleet[1];
if (c instanceof ElectricCar) {
ElectricCar e = (ElectricCar) c;
e.charge();
System.out.println(e.getBrand() + ": " + e.getBattery() + "%");
}
Изход
Тесла: 100%

(ElectricCar) c е превръщане на типа, със същите скоби като (double) от модул „Основи“. То казва на компилатора: „повярвай ми, тук има електрическа кола“. Ако няма, програмата спира с ClassCastException. Затова превръщане без проверка преди него е риск.

По-точно, за любопитните
  • Проверката е винаги грешна за null: null instanceof Car е false. Затова след успешен instanceof никога нямаш null.
  • Ако двата типа нямат нищо общо (String и Car), instanceof не се компилира: компилаторът вижда, че отговорът винаги е „не“.
  • Проверката работи и с интерфейси: a instanceof Swimmable s пита дали класът на обекта реализира договора.

Пробвай

Намери грешката

Програмата брои обикновените и електрическите коли. В автопарка има две от всеки вид, а тя печата:

Изход
Обикновени: 4, електрически: 0

На кой ред е грешката?

Избери реда с грешката.

Идеята

Проверка или метод?

Искаш да отпечаташ с какво се движи всяка кола. С проверка на типа става:

for (Car c : fleet) {
if (c instanceof ElectricCar) {
System.out.println(c.getBrand() + ": ток");
} else {
System.out.println(c.getBrand() + ": бензин");
}
}

Но това е заплатата с if по роля от урока за полиморфизма. Всяка кола има енергия, само че различна. Това е работа за метод в Car, презаписан в ElectricCar:

// в Car
public String energy() {
return "бензин";
}
// в ElectricCar
@Override
public String energy() {
return "ток";
}
for (Car c : fleet) {
System.out.println(c.getBrand() + ": " + c.energy());
}
Изход
Шкода: бензин
Тесла: ток
Дачия: бензин
Нисан: ток

Утре идва хибридна кола. С метода пишеш нов клас. С проверките търсиш всеки if в програмата.

Правилото:

  • Всеки обект го прави, но по свой начин: метод в родителя и презаписване.
  • Само някои обекти го могат (зареждане, педали): проверка на типа.

Пробвай

Кое липсва?

for (Car c : fleet) {
    if (c  ElectricCar e) {
        e.charge();
    }
}

Накратко

Четирите стълба на ООП

С този урок модулите „Класове и обекти“ и „Наследяване и полиморфизъм“ са минати. Обектно-ориентираното програмиране стои на четири идеи. Вече ползваш всичките:

  • Капсулация. Данните на обекта са скрити, а отвън се стига до тях само през методи, които пазят правилата. Account държи баланса private и дава само deposit() и withdraw(). Капсулация
  • Наследяване. Общото е на едно място, в родителя, а наследникът добавя своето. Manager е Employee с бонус. Наследяване
  • Полиморфизъм. Едно извикване, различно поведение според обекта. e.monthlyPay() в един цикъл смята заплатата на служител, мениджър и стажант. Полиморфизъм
  • Абстракция. Казваш какво трябва да може обектът, а как решава всеки клас. Абстрактният Shape обещава лице, интерфейсът Payable обещава сума за плащане. Абстрактни класове, Интерфейси

Упражнение

Какъв е всъщност?

Реши задачите в редактора си.

1. Паркинг. Масив SimpleCar[] с обикновени и семейни коли (FamilyCar наследява SimpleCar). За всяка отпечатай какъв е типът ѝ. Внимавай с реда на проверките.

Изход
Това е SimpleCar
Това е FamilyCar
Това е SimpleCar

2. Танци. Масив DanceMove[] с обикновени и брейк движения. За брейк движенията отпечатай и сложността. За нея ти трябва променливата от проверката.

Изход
moonwalk: DanceMove
windmill: BreakDanceMove, сложност 8
салса: DanceMove

3. Автопарк. Вземи Car и ElectricCar от упражнението в урока „Наследяване“. В масив Car[] сложи „Шкода“ с 40 л, „Тесла“ с ефективност 5, „Дачия“ с 5 л и „Нисан“ с ефективност 4. Всяка да измине 100 км: електрическите с driveElectric, останалите с drive. После отпечатай километрите и горивото или батерията.

Изход
Шкода: 100 км, гориво 30 л
Тесла: 100 км, батерия 80%
Горивото не стига за 100 км
Дачия: 0 км, гориво 5 л
Нисан: 100 км, батерия 75%

4. Зоопарк. Абстрактен клас Animal със sound(), наследници Cat, Dog и Fish. Само Fish реализира Swimmable с swim(). В цикъл през животните извикай звука на всяко, а на тези, които могат да плуват, и swim().

Изход
Мяу, мяу!
Бау, бау!
Рибата мълчи.
Рибата плува.

Още задачи

  • Педали. В упражнението от урока „Абстрактни класове“ garage[1].pedal() не се компилираше. Поправи го с проверка на типа: в цикъла през Vehicle[] всяко превозно средство потегля, а велосипедът и върти педали.
  • Плащания по вид. От урока „Интерфейси“: в масива Payable[] има служители и фактури. Отпечатай отделно сумата за заплати и сумата за фактури. За Иван, Мария и двете фактури: 5500 € заплати и 2140 € фактури.

Тест

Провери се

1. c instanceof ElectricCar e е вярно. Какво е e?
2. c сочи ElectricCar. Колко е c instanceof Car?
3. Проверяваш дали колата е Car или ElectricCar. В какъв ред?
4. В програмата има много if-ове „ако е ElectricCar, прави едно, иначе друго“. Какво подсказва това?
5. c сочи обикновена кола. Какво става при ElectricCar e = (ElectricCar) c;?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·