Для чого потрібне перевизначення методів




Для чого потрібне перевизначення методів



Перевизначення методів

Якщо в ієрархії класів збігаються імена та сигнатури типів методів із підкласу та супер класу, то кажуть, що метод із підкласу перевизначає метод із супер-класу.

Перевизначення методів виконується тільки в тому випадку, якщо імена та сигнатури типів обох методів однакові. В іншому випадку обидва методи вважаються перевантажуваними.

У прикладі в класі M визначено метод print() . У його спадкоємці класі N теж визначено метод print() з такою самою сигнатурою, але іншою поведінкою. Це і називається перевизначенням методів:

public class M <public int i; public int j; public M(int i, int j) <this.i = i; this.j = j; >public void print() < System.out.println("Метод M i = " + i + " j language-java">public class N extends M <public int k; public N(int i, int j, int k) <super(i, j); this.k = k; >public void print() < System.out.println("Метод N k language-java">public class OverrideExample < public static void main(String[] args) < M obj1 = new M(7, 8); obj1.print(); N obj2 = new N(4, 5, 6); obj2.print(); M obj3 = new N(1, 2, 3); obj3.print(); >>
Метод M i = 7 j = 8 Метод N k = 6 Метод N k = 3

Існує таке поняття в Java як динамічна диспетчеризація методів - це механізм, з допомогою якого виклик перевизначеного методу дозволяється під час виконання, а чи не компіляції.

Перевизначення методів це одна з форм реалізації поліморфізму. Перевизначення методу дозволяє визначити у загальному класі методи, які стануть загальними всім похідних від цього класів, а підкласах - конкретні реалізації деяких чи всіх цих методів.

Розглянемо конкретніший приклад, який показує навіщо перевизначаються методи.

Створимо клас Figure, що описує якусь абстрактну фігуру та класи спадкоємці Triangle та Rectangle. Клас Figure містить метод calculateArea() , що підраховує площу фігури.Кожна фігура має свою формулу для підрахунку площі, тому в класах Triangle і Rectangle метод calculateArea() перевизначається відповідним чином:

public class Figure < double dimension1; double dimension2; public Figure(double dimension1, double dimension2) < this.dimension1 = dimension1; this.dimension2 = dimension2; >public double calculateArea() < System.out.println("Площа фігури не визначена."); return 0; >>
public class Rectangle extends Figure < public Rectangle(double dimension1, double dimension2) < super(dimension1, dimension2); >public double calculateArea() < System.out.println("B області чотирикутника."); return dimension1 * dimension2; >>
public class Triangle extends Figure < public Triangle(double dimension1, double dimension2) < super(dimension1, dimension2); >public double calculateArea() < System.out.println("B області трикутника."); return dimension1*dimension2/2; >>

Створимо масив типу Figure, який міститиме об'єкти типу Figure, Triangle та Rectangle. Підрахуємо площу кожного елемента перебираючи елементи масиву і викликаючи метод calculateArea() кожного елемента. Нам все одно якого типу об'єкт - у кожного є метод, що викликається calculateArea() . JVM за допомогою динамічної диспетчеризації вибирає потрібний варіант методу, ґрунтуючись на реальному типі об'єкта:

public class FindAreas < public static void main(String[] args) < Figure[] figures = new Figure[3]; figures[0] = new Figure(10, 10); figures[1] = new Rectangle(10, 10); figures[2] = new Triangle(10, 10); for (Figure figure : figures) < double area = figure.calculateArea(); System.out.println(area); >> >

Результат виконання коду:

Площу постаті не визначено. 0.0 B області чотирикутника. 100.0 B області трикутника. 50.0

2. Методи підставки

Після виходу Java 5 з'явилася можливість при перевизначенні методів вказувати інший тип значення, що повертається, в якості якого можна використовувати тільки типи, що знаходяться нижче в ієрархії успадкування, ніж вихідний тип. Такі типи ще називаються підступними.

Наприклад, клас S успадковує клас R і перевизначає метод getInstance(). При перевизначенні тип методу може або залишитися таким же - Box6 , або бути змінений на будь-якого спадкоємця класу Box6 - HeavyBox , ColorBox або Shipment :

public class S extends R < HeavyBox getInstance() < return new HeavyBox(); >>

3. Перевизначення та статичні методи

Статичні методи неможливо знайти перевизначені. Клас спадкоємець може оголошувати метод із такою самою сигнатурою, що й супер клас, але це не буде перевизначенням. При виклику перевизначеного методу JVM вибирає потрібний варіант, ґрунтуючись на типі об'єкта. Виклик статичного методу відбувається без об'єкта. Версія викликаного статичного методу завжди визначається етапі компіляції.

При використанні посилання для доступу до статичного члена компілятор при виборі методу враховує тип посилання, а не тип присвоєного їй об'єкта.

Створимо в супер класі та спадкоємці статичні методи з однаковою сигнатурою:

public class Sub extends Base < public static void go() < System.out.println("метод із Sub"); >>

Спробуємо викликати статичний метод, використовуючи змінну типу Base, яка вказує на об'єкт типу Sub. При виклику статичного методу JVM знайде тип змінної та викличе метод того самого класу:

метод із Base метод із Sub

4. Перевизначення методів у класах спадкоємців

Методи оголошені як private ніхто, крім самого класу, не бачить. Тому їхня наявність/відсутність ніяк не відбивається на класах спадкоємців. Вони легко можуть оголошувати методи з такою ж сигнатурою і будь-якими модифікаторами.Але це поганий тон! Також клас спадкоємець може розширити видимість protected методу до public. Звузити видимість клас спадкоємець не може.

5. Анотація @Override

Необов'язкова анотація @Override використовується з методом для вказівки, що він перевизначений. Якщо метод неправильно визначено, код не буде компілюватися:

public class S extends R < @Override HeavyBox getInstance() < return new HeavyBox(); >>

Перевантаження та перевизначення методів у Java

Перевантаження та перевизначення методів є ключовими поняттями мови програмування Java, і тому вони заслуговують на докладний розгляд.

У цій статті ми вивчимо основи цих концепцій та подивимося, в яких ситуаціях вони можуть бути корисними.

2. Перевантаження методу

Перевантаження методів – це потужний механізм, що дозволяє нам визначати пов'язані API-інтерфейси класів. Щоб краще зрозуміти, чому перевантаження методів є такою цінною функцією, розглянемо простий приклад.

Припустимо, що ми написали простий службовий клас, який реалізує різні методи множення двох чисел, трьох чисел тощо.

Якщо ми дали методам вводять в оману або двозначні імена, такі як помножити2() , помножити3() , помножити4() , то це буде погано спроектований клас API. Ось де в гру входить перевантаження методів.

Простіше кажучи, ми можемо реалізувати навантаження методів двома різними способами:

  • реалізація двох або більше методів з однаковими іменами, але з різною кількістю аргументів
  • реалізація двох або більше методів з однаковими іменами, але які приймають аргументи різних типів

2.1. Різна кількість аргументів

Клас Multiplier у двох словах показує, як перевантажити метод multiplier () , просто визначивши дві реалізації, які приймають різну кількість аргументів:


public
class
Multiplier




public
int
multiply(int
a,
int
b)



return
a
*
b;


>



public
int
multiply(int
a,
int
b,
int
c)



return
a
*
b
*
c;


>


>

2.2. Аргументи різних типів

Так само ми можемо перевантажити метод multi() , змусивши його приймати аргументи різних типів:


public
class
Multiplier




public
int
multiply(int
a,
int
b)



return
a
*
b;


>



public
double
multiply(double
a,
double
b)



return
a
*
b;


>


>

Крім того, можна визначити клас Multiplier з обома типами навантаження методів:


public
class
Multiplier




public
int
multiply(int
a,
int
b)



return
a
*
b;


>



public
int
multiply(int
a,
int
b,
int
c)



return
a
*
b
*
c;


>



public
double
multiply(double
a,
double
b)



return
a
*
b;


>


>

Однак варто зазначити, що неможливо мати дві реалізації методів, які відрізняються тільки типами значень, що повертаються .

Щоб зрозуміти чому – розглянемо наступний приклад:


public
int
multiply(int
a,
int
b)



return
a
*
b;


>



public
double
multiply(int
a,
int
b)



return
a
*
b;


>

У цьому випадку код просто не скомпілювався б через неоднозначність виклику методу компілятор не знав би, яку реалізацію multi() викликати.

2.3. Тип акції

Однією із зручних функцій, що надаються перевантаженням методів, є так зване підвищення типу, також відоме як розширення примітивного перетворення.

Простіше кажучи, один заданий тип неявно підвищується до іншого, коли немає відповідності між типами аргументів, переданих у перевантажений метод, та конкретною реалізацією методу.

Щоб краще зрозуміти, як працює перетворення типу, розглянемо наступні реалізації методу multi() :


public
double
multiply(int
a,
long
b)



return
a
*
b;


>



public
int
multiply(int
a,
int
b,
int
c)



return
a
*
b
*
c;


>

Тепер виклик методу з двома аргументами int призведе до того, що другий аргумент буде підвищено до long , тому що в цьому випадку немає відповідної реалізації методу з двома аргументами int .

Давайте подивимося швидкий модульний тест, щоб продемонструвати просування типу:


@Test


public
void
whenCalledMultiplyAndNoMatching_thenTypePromotion()



assertThat(multiplier.multiply(10,
10)).isEqualTo(100.0);


>

І навпаки, якщо ми викликаємо метод із відповідною реалізацією, просування типу просто не відбувається:


@Test


public
void
whenCalledMultiplyAndMatching_thenNoTypePromotion()



assertThat(multiplier.multiply(10,
10,
10)).isEqualTo(1000);


>

Ось зведення правил просування типів, які застосовуються для навантаження методів:

  • byte може бути перетворений на short, int, long, float або double
  • Short може бути підвищений до int, long, float або double
  • char може бути перетворений на int, long, float або double
  • int може бути підвищений до long, float або double
  • long може бути підвищений до float або double
  • float можна збільшити до double

2.4. Статична прив'язка

Можливість пов'язати конкретний виклик методу із тілом методу називається прив'язкою.

У разі навантаження методу прив'язка виконується статично під час компіляції, тому вона називається статичною прив'язкою.

Компілятор може ефективно встановити прив'язку під час компіляції, просто перевіривши методи сигнатури.

3. Перевизначення методу

Перевизначення методів дозволяє нам надавати деталізовані реалізації у підкласах для методів, визначених у базовому класі.

Незважаючи на те, що перевизначення методів є потужною функцією, враховуючи, що це логічний наслідок використання успадкування одного з основних стовпів ООП коли та де його використовувати, необхідно ретельно аналізувати його для кожного варіанту використання .

Тепер давайте подивимося, як використовувати перевизначення методу, створивши просте ставлення на основі успадкування ("є").


public
class
Vehicle




public
String
accelerate(long
mph)



return
"Виробники accelerates at : "
+
mph
+
" MPH.";


>



public
String
stop()



return
"Виробник був схвалений.";


>



public
String
run()



return
"The vehicle is running.";


>


>

І ось надуманий підклас:


public
class
Car
extends
Vehicle




@Override


public
String
accelerate(long
mph)



return
"The car accelerates at : "
+
mph
+
" MPH.";


>


>

У наведеній ієрархії ми просто перевизначили метод Accel() , щоб забезпечити більш досконалу реалізацію підтипу Car.

Тут ясно видно, що якщо програма використовує екземпляри класу Vehicle, то вона може працювати і з екземплярами Car, оскільки обидві реалізації методу прискорення() мають однакову сигнатуру і один і той же тип значення, що повертається.

Давайте напишемо кілька модульних тестів для перевірки класів Vehicle та Car:


@Test


public
void
whenCalledAccelerate_thenOneAssertion()



assertThat(vehicle.accelerate(100))


.isEqualTo("Вектор accelerates at : 100 MPH.");


>



@Test


public
void
whenCalledRun_thenOneAssertion()



assertThat(vehicle.run())


.isEqualTo("The vehicle is running.");


>



@Test


public
void
whenCalledStop_thenOneAssertion()



assertThat(vehicle.stop())


.isEqualTo("Виробник був схвалений.");


>



@Test


public
void
whenCalledAccelerate_thenOneAssertion()



assertThat(car.accelerate(80))


.isEqualTo("The car accelerates at : 80 MPH.");


>



@Test


public
void
whenCalledRun_thenOneAssertion()



assertThat(car.run())


.isEqualTo("The vehicle is running.");


>



@Test


public
void
whenCalledStop_thenOneAssertion()



assertThat(car.stop())


.isEqualTo("Виробник був схвалений.");


>

Тепер давайте подивимося на деякі модульні тести, які показують, як методи run() і stop() , які не перевизначені, повертають однакові значення як Car , так Vehicle :


@Test


public
void
givenVehicleCarInstances_whenCalledRun_thenEqual()



assertThat(vehicle.run()).isEqualTo(car.run());


>



@Test


public
void
givenVehicleCarInstances_whenCalledStop_thenEqual()



assertThat(vehicle.stop()).isEqualTo(car.stop());


>

У нашому випадку ми маємо доступ до вихідного коду обох класів, тому ми ясно бачимо, що виклик методу Accel() для базового екземпляра Vehicle та виклик методу Accelerator() для екземпляра Car повернуть різні значення для одного й того ж аргументу.

Тому наступний тест демонструє, що перевизначений метод викликається для екземпляра Car:


@Test


public
void
whenCalledAccelerateWithSameArgument_thenNotEqual()



assertThat(vehicle.accelerate(100))


.isNotEqualTo(car.accelerate(100));


>

3.1. Тип взаємозамінності

Основним принципом ООП є замінність типів, що тісно пов'язана з принципом підстановки Ліскова (LSP).

Простіше кажучи, LSP стверджує, що якщо програма працює з цим базовим типом, то вона повинна працювати і з будь-яким з її підтипів. . Таким чином, замінність типів зберігається належним чином.

Найбільша проблема з перевизначенням методів у тому, деякі конкретні реалізації методів у похідних класах можуть повністю відповідати LSP і, отже, не підтримувати замінність типів.

Звичайно, допустимо зробити перевизначений метод для прийому аргументів різних типів та повернення іншого типу, але з повним дотриманням наступних правил:

  • Якщо метод базового класу приймає аргумент(и) заданого типу, перевизначений метод повинен приймати той самий тип чи супертип (тобто аргументи контраваріантного методу) .
  • Якщо метод базового класу повертає void, перевизначений метод повинен повертати void.
  • Якщо метод у базовому класі повертає примітив, перевизначений метод має повертати той самий примітив.
  • Якщо метод у базовому класі повертає певний тип, перевизначений метод повинен повертати той самий тип чи підтип (також відомий як підступний тип, що повертається) .
  • Якщо метод у базовому класі видає виняток, перевизначений метод повинен видавати такий самий виняток або підтип виключення базового класу.

3.2. Динамічне зв'язування

Враховуючи, що перевизначення методу може бути реалізовано тільки з успадкуванням, де існує ієрархія базового типу та підтипу (ів), компілятор не може визначити під час компіляції, який метод викликати, оскільки базовий клас і підкласи визначають ті ж методи.

Як наслідок, компілятор необхідно перевірити тип об'єкта, щоб дізнатися, який метод слід викликати.

Оскільки це перевірка відбувається під час виконання, перевизначення методу є типовим прикладом динамічного зв'язування.

4. Висновок

У цьому посібнику ми дізналися, як реалізувати навантаження та перевизначення методів, а також розглянули деякі типові ситуації, в яких вони можуть бути корисними.

Як завжди, всі приклади коду, показані в цій статті, доступні на GitHub.

Перевизначення методів Java

Привіт! Це стаття про перевизначення (override) методів у Java. Вона тісно пов'язана з розумінням принципів ОВП, класів та механізму спадкування. Якщо Ви погано знаєтеся на цих темах, спочатку почитайте:

Що таке перевизначення в Java

Отже, Ви вже знайомі з поняттями класів та методів Java. Напевно, Вам навіть відомо, що одні класи можуть успадковувати інші.

І ось у цей момент – момент успадкування – може виникнути проблема. Щоб проілюструвати, уявімо, що у нас є клас Animal. Цей клас має кілька методів – у тому числі і метод voice() ("голос"):

Давайте тепер уявімо, що ми створюємо клас Cat, який успадковує Animal:

Але зараз якщо ми створимо кішку і запустимо метод voice() , ми отримаємо "This is my voice!". Наприклад, якщо запустимо це:

Як ми знаємо, кішка не кажеThis is my voice!🙂 Кішка нявкає, собака гавкає, і т.д.

Таким чином, ми хотіли б, щоб при виклику методу voice() наша кішка говорила "Мяу". Звичайно, ми можемо створити новий метод - наприклад, catVoice()- Але це було б не дуже ефективно. Що, якщо ми хочемо змінити 3, 5 чи 10 методів? І що, метод батька лежатиме у нас мертвим вантажем?

Ні, нам зовсім не обов'язково йти цим шляхом. Набагато краще, якщо ми просто матимемо "свій варіант" необхідного способу. Ми можемо зробити так, щоб нам не треба було змінювати назву або параметри методу, але він викликав потрібний результат. Для цього ми просто повинні перевизначити метод батька.

приклад

Як нам перевизначити метод voice()? Все дуже просто:

  • По-перше, ми наново прописуємо метод voice() у нашому класі Cat
  • По-друге, ми повинні дати Java зрозуміти, що ми не помилилися і не назвали метод назвою, яка вже є через неуважність. Щоб сказати, що ми знаємо, що робимо і справді хочемо перевизначити(з англ. "override") метод, потрібно над методом написати @Override .

У коді, наприклад, це виглядає так:

Схожі статті

  • Для чого потрібне світло
  • Для чого потрібне відкриття портів
  • Для чого потрібне щеплення винограду
  • Для чого потрібне листя рослини
  • Для чого потрібне свідоцтво про зміну імені
  • Для чого потрібне розмежування прав доступу
  • Для чого використовують мінеральні добрива
  • Для чого призначена система 112
  • Недавні статті

  • Як бродить зернова брага
  • Що робити якщо не засмагаєш на сонці чому засмага погано лягає на шкіру або перестає прилипати
  • Як швидко зняти гель лак без апарату
  • Як робиться Каті голови
  • Яка гребінець краще для об'єму
  • Чим роблять м'яку покрівлю
  • Чи можна залишати крем для обличчя на ніч
  • Де знаходиться датчик селектора
  • географія нашої діяльності
    вулиця Драгоманова, 27
    вул. Курчатова 1Б
    вул. Міцкевича 130
    вул. Лабунського, 1
    вул. Макарова-Пржевальського
    вул. Толстого 10
    вул. Грушевського 28
    вул. Перший промінь (Черняхівського)
    напишіть нам

    сообщение успешно отправлено
    x