Какъв е всъщност?
Проблем
Само някои могат да се зареждат
Фирмата има автопарк от обикновени и електрически коли. Всяка вечер електрическите трябва да се заредят. 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() + "%"); }}Стъпка по стъпка, с думи
- fleet е масив от Car: 2 обикновени и 2 електрически коли.
- c сочи Шкода: instanceof дава „не“. Тялото на if се прескача.
- c сочи Тесла: instanceof дава „да“. e е втора стрелка към същата кола и я зарежда.
- c сочи Дачия: instanceof дава „не“. Тялото на if се прескача.
- c сочи Нисан: instanceof дава „да“. e е втора стрелка към същата кола и я зарежда.
- Колите не се променят. Проверката само поглежда към обекта през по-конкретен тип.
Пробвай
Кола ли е?
Какво ще отпечата програмата?
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);Електрическата кола е и кола, затова първата проверка е вярна. Проверката не пита „точно Car ли е?“, а „може ли обектът да се ползва като Car?“. Обикновената кола d не е електрическа, затова последната проверка е грешна.
Идеята
Другите записи, които ще срещнеш
Шаблонът с променлива (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На кой ред е грешката?
Избери реда с грешката.
Всяка електрическа кола е и Car, затова първата проверка е вярна за всички четири коли. До втората програмата не стига никога. Проверявай първо по-конкретния тип: ако е ElectricCar, брой електрическа, иначе обикновена.
Идеята
Проверка или метод?
Искаш да отпечаташ с какво се движи всяка кола. С проверка на типа става:
for (Car c : fleet) { if (c instanceof ElectricCar) { System.out.println(c.getBrand() + ": ток"); } else { System.out.println(c.getBrand() + ": бензин"); }}Но това е заплатата с if по роля от урока за полиморфизма. Всяка кола има енергия, само че различна. Това е работа за метод в Car, презаписан в ElectricCar:
// в Carpublic String energy() { return "бензин";}
// в ElectricCar@Overridepublic 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();
}
}instanceof: проверява типа и, ако е верен, създава e.Накратко
Четирите стълба на ООП
С този урок модулите „Класове и обекти“ и „Наследяване и полиморфизъм“ са минати. Обектно-ориентираното програмиране стои на четири идеи. Вече ползваш всичките:
- Капсулация. Данните на обекта са скрити, а отвън се стига до тях само през методи, които пазят правилата.
Accountдържи баланса private и дава самоdeposit()иwithdraw(). Капсулация - Наследяване. Общото е на едно място, в родителя, а наследникът добавя своето.
ManagerеEmployeeс бонус. Наследяване - Полиморфизъм. Едно извикване, различно поведение според обекта.
e.monthlyPay()в един цикъл смята заплатата на служител, мениджър и стажант. Полиморфизъм - Абстракция. Казваш какво трябва да може обектът, а как решава всеки клас. Абстрактният
Shapeобещава лице, интерфейсът Payable обещава сума за плащане. Абстрактни класове, Интерфейси
Упражнение
Какъв е всъщност?
Реши задачите в редактора си.
1. Паркинг. Масив SimpleCar[] с обикновени и семейни коли (FamilyCar наследява SimpleCar). За всяка отпечатай какъв е типът ѝ. Внимавай с реда на проверките.
Това е SimpleCarТова е FamilyCarТова е SimpleCar2. Танци. Масив DanceMove[] с обикновени и брейк движения. За брейк движенията отпечатай и сложността. За нея ти трябва променливата от проверката.
moonwalk: DanceMovewindmill: BreakDanceMove, сложност 8салса: DanceMove3. Автопарк. Вземи 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 € фактури.
Тест