Чем отличается переопределение от перегрузки java
Перейти к содержимому

Чем отличается переопределение от перегрузки java

  • автор:

Чем отличается перегрузка от переопределения?

Полиморфизм – соль ООП. Перегрузка (overload) и переопределение (override) – два инструмента достижения полиморфного поведения в Java.

Перегрузкой реализуется ad-hoc-полиморфизм. Это значит «один и тот же» метод может работать с разными параметрами. С технической точки зрения это просто два разных метода, сигнатуры которых имеют одинаковое название, но разный набор параметров. Важно помнить, что для перегрузки не достаточно различий только модификаторов, возвращаемых типов и списков исключений.

Ad-hoc – не совсем настоящий полиморфизм, так как при нём используется раннее, или статическое связывание (early binding, static dispatch). Это значит, что для выбора конкретного варианта метода используется информация о типе переменной, а не объекта в ней лежащего, и происходит это еще при компиляции.

Если в классе объявлены два перегруженных метода, а аргумент в вызове подходит под оба, случится ошибка компиляции. В примере ниже компилятор не может выбрать между вариантами метода println с параметром char[] и со String , так как null может быть и тем и другим.

Переопределение (override) дает полиморфизм подтипов. Это реализация/подмена метода нефинального родительского класса или интерфейса. С помощью этого механизма достигается поведение, когда экземпляр хранится под типом родителя, но реализация методов используется специфичная для этого конкретного подтипа. Пример:

List list = new LinkedList<>();
list.add(“foo“);

Здесь метод add вызывается общий для всех списков, но добавлен будет именно элемент связного списка.

Выбор конкретного метода происходит в последний момент, в процессе работы программы, в зависимости от типа объекта. Это называется позднее или динамическое связывание методов (late binding, dynamic dispatch).

Переопределение имеет непосредственное отношение к принципу подстановки Лисков (LSP): в хорошем объектно-ориентированном коде для вызывающего кода переопределенный метод не должен быть отличим от оригинального.

Переопределенный метод принято снабжать аннотацией @Override . Ее отсутствие допускается, но компиляция не перегружающего метода с такой аннотацией приведет к ошибке.

При переопределении можно сузить набор выбрасываемых исключений или тип результата, и заменить модификатор доступа на менее строгий.

Статические методы нельзя переопределить, можно только перегрузить.

О внутренностях процесса связывания можно почитать в этой статье.

Переопределение и перегрузка в Java

Переопределение (overriding) и перегрузка (overloading) – одни из ключевых понятий в программировании на Java. Эти механизмы позволяют реализовать полиморфизм в программах Java. Полиморфизм — одна из концепций ООП.

На этом скриншоте показано, где в коде Java происходит перегрузка, а где – переопределение.

Переопределением называют случаи, когда сигнатура метода (имя и параметры) в суперклассе и дочернем классе совпадают. Когда два или более метода в одном классе имеют одинаковое имя, но разные параметры, это называется перегрузкой.

Перегрузка и переопределение: сравнительный анализ

Переопределение Перегрузка
Реализует «полиморфизм времени выполнения» Реализует «полиморфизм времени компиляции»
Вызов метода определяется во время выполнения на основе типа объекта. Вызов метода определяется во время компиляции
Происходит между суперклассом и подклассом Происходит между методами в рамках одного класса
Одинаковая сигнатура (имя и аргументы метода) Одинаковое имя, но разные параметры
В случае возникновения ошибки эффект будет виден во время выполнения Ошибка может быть обнаружена во время компиляции.

Переопределение и перегрузка: примеры

Давайте рассмотрим следующие примеры, чтобы понять, как работает переопределение и перегрузка в программах Java. За основу возьмем такой код:

package com.journaldev.examples; import java.util.Arrays; public class Processor < public void process(int i, int j) < System.out.printf("Processing two integers:%d, %d", i, j); >public void process(int[] ints) < System.out.println("Adding integer array:" + Arrays.toString(ints)); >public void process(Object[] objs) < System.out.println("Adding integer array:" + Arrays.toString(objs)); >> class MathProcessor extends Processor < @Override public void process(int i, int j) < System.out.println("Sum of integers is " + (i + j)); >@Override public void process(int[] ints) < int sum = 0; for (int i : ints) < sum += i; >System.out.println("Sum of integer array elements is " + sum); > >

Переопределение

Метод process() и параметры int i, int j в Processor переопределяются дочерним классом MathProcessor. Обратите внимание на строки 7 и 23:

public class Processor  public void process(int i, int j) < /* . */ > > /* . */ class MathProcessor extends Processor  @Override public void process(int i, int j) < /* . */ > >

Метод process() и int[] ints в Processor также переопределяются в дочернем классе. Смотрите строки 11 и 28.

public class Processor  public void process(int[] ints) < /* . */ > > /* . */ class MathProcessor extends Processor  @Override public void process(Object[] objs) < /* . */ > >

Перегрузка

Метод process() перегружается в классе Processor. Об этом говорят строки 7, 11 и 15:

public class Processor  public void process(int i, int j) < /* . */ > public void process(int[] ints) < /* . */ > public void process(Object[] objs) < /* . */ > > Итоги

В этом руководстве мы на простом примере рассмотрели переопределение и перегрузку в Java. Переопределение происходит, когда сигнатура метода в суперклассе и дочернем классе одинакова. Перегрузка возникает, когда два или более метода в одном классе имеют одинаковое имя, но разные параметры.

Полиморфизм vs Переопределение vs Перегрузка в Java

Часто возникает путаница при понимании различий между понятиями полиморфизма, переопределения и перегрузки в программировании на Java. Это связано с тем, что все эти термины относятся к основным принципам объектно-ориентированного программирования (ООП). Для понимания разницы между ними, рассмотрим каждый из них в отдельности.

Полиморфизм является одним из основных принципов ООП. Это способность объекта использовать методы производного класса, который не существует на момент создания базового класса. Полиморфизм позволяет использовать объекты производного класса как объекты базового класса, выполняя при этом методы производного класса.

Например, есть базовый класс Animal и производные от него классы Dog и Cat . У всех этих классов есть метод makeSound() . Если создать массив объектов класса Animal и поместить в него объекты классов Dog и Cat , то при вызове метода makeSound() для каждого из объектов в массиве, будет вызван соответствующий метод класса, которому принадлежит объект.

Переопределение в Java — это возможность класса наследника предоставить свою реализацию метода, который уже предоставлен в родительском классе. Этот механизм позволяет классу наследнику наследовать методы родительского класса и изменять их поведение, если это требуется.

Например, класс Shape имеет метод getArea() , который возвращает площадь фигуры. Классы Circle и Rectangle , наследующие класс Shape , переопределяют метод getArea() для предоставления своей собственной реализации расчета площади.

Перегрузка методов в Java — это возможность класса иметь два или более метода с одинаковыми именами, но разными параметрами. Методы могут отличаться количеством параметров, типом параметров или их порядком.

Например, в классе Rectangle может быть два метода setDimensions() : один принимает два параметра (длину и ширину прямоугольника), а второй — один параметр (длину стороны, если прямоугольник является квадратом).

Таким образом, полиморфизм, переопределение и перегрузка — это разные понятия, хотя и связанные с объектно-ориентированным программированием. Полиморфизм относится к использованию методов производного класса с объектами базового класса, переопределение позволяет изменять поведение наследуемых методов, а перегрузка позволяет использовать одно и то же имя метода для методов с разными параметрами.

Перегрузка и переопределение методов в Java: примеры

Метод в Java — это функция, которая определяет, что умеет делать объект этого класса. Одна из главных задач методов — выполнение действий над данными объекта. Они могут менять значение, преобразовывать данные, выводить их в консоль.

Методы можно перегружать и переопределять. Как это делать и в чём разница между этими двумя механизмами — разберёмся в этой статье.

Перегрузка метода

Перегрузка методов в Java — это использование одного имени метода с разными параметрами.

Чтобы разобраться с этим механизмом, начнём с простого — создадим помощника, который будет здороваться с пользователями.

public class Assistant  
public void sayHello(String name) System.out.println("Добрый день, " + name + "!");
>

public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил");
>
>

В консоли будет выведена фраза «Добрый день, Михаил!».

Допустим, Михаил пришёл не один, а с другом Виталием. Сейчас метод реализован так, что помощник поприветствует только Михаила, а Виталия проигнорирует. Чтобы исправить это, реализуем в классе два метода. Имя у них будет одинаковое. Но параметры они принимают разные.

public class Assistant  
public void sayHello(String firstGuest) System.out.println("Добрый вечер, " + firstGuest + "!");
>

public void sayHello(String firstGuest, String secondGuest) System.out.println("Добрый день, " + firstGuest + " и " + secondGuest + "!");
>
public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил", "Виталий");
>
>

Теперь в консоли отобразится фраза «Добрый день, Михаил и Виталий!».

Мы уже перегрузили sayHello(). Теперь программа стала более гибкой — помощник может приветствовать сразу двух гостей. Но что произойдёт, если придут трое, четверо или пятеро? Проверим:

public class Assistant  
public void sayHello(String firstGuest) System.out.println("Добрый вечер, " + firstGuest + "!");
>

public void sayHello(String firstGuest, String secondGuest) System.out.println("Добрый день, " + firstGuest + " и " + secondGuest + "!");
>
public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил", "Виталий", "Марина");
>
>

В ответ получим ошибку, потому что sayHello() готов принимать только два аргумента. Решение в лоб — перегружать его дальше. Сделать так, чтобы .sayHello() принимал троих, четверых, пятерых и больше гостей. Но это не похоже на гибкую работу программы. Придётся постоянно дописывать код.

Более гибкое решение — передать в качестве параметра аргумент переменной длины (String… names). Это позволит sayHello() принимать любое количество строк. А чтобы выводить в консоль приветствие каждого гостя, используем цикл.

public class Assistant  
public void sayHello(String firstGuest) System.out.println("Добрый вечер, " + firstGuest + "!");
>

public void sayHello(String… names)
for (String name: names) System.out.println("Добрый вечер, " + name + "!");
>
>

public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил", "Виталий", "Марина", "Андрей", "Анна");
>

В консоли отобразится приветствие каждого переданного гостя:

Добрый вечер, Михаил!
Добрый вечер, Виталий!
Добрый вечер, Марина!
Добрый вечер, Андрей!
Добрый вечер, Анна!

Порядок аргументов

В примере выше мы не думали о порядке аргументов, потому что все они были строками. Нет разницы, с кем здороваться сначала — с Михаилом или с Анной.

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

public class User  
public static void sayYourAge(String greeting, int age) System.out.println(greeting + " " + age);
>

public static void main(String[] args) sayYourAge(20, "Мой возраст - "); //ошибка!
>
>

На этапе компиляции возникнет ошибка, потому что при определении sayYourAge() мы задали, что сначала должна быть строка, а затем — число, но аргументы передали в обратном порядке.

Чтобы исправить ошибку, достаточно передать аргументы в правильном порядке:

public class User  
public static void sayYourAge(String greeting, int age) System.out.println(greeting + " " + age);
>

public static void main(String[] args) sayYourAge("Мой возраст - ", 20);
>
>

Чтобы избежать ошибок, можно сделать перегрузку порядком параметров. Например, вот так:

public class User  
public static void sayYourAge(String greeting, int age) System.out.println(greeting + " " + age);
>
public static void sayYourAge(int age, String greeting) System.out.println(greeting + " " + age);
>

public static void main(String[] args) sayYourAge("Мой возраст - ", 20);
sayYourAge(20, "Мой возраст - ");
>
>

Теперь не имеет значения, в каком порядке передавать аргументы — оба варианты будут понятны программе.

Варианты перегрузки

Из примеров выше можно выделить три варианта перегрузки.

  1. По количеству параметров.
public class Calculator void calculate(int number1, int number2) < > 
void calculate(int number1, int number2, int number3) < >
>
  1. По типам параметров:
public class Calculator void calculate(int number1, int number2) < > 
void calculate(double number1, double number2) < >
>
  1. По порядку параметров:
public class Calculator void calculate(double number1, int number2) < > 
void calculate(int number1, double number2) < >
>

Напоследок повторим, что означает перегрузка метода в Java . Это механизм языка, который позволяет создавать несколько методов с одинаковым названием, но разными параметрами. Так можно делать не во всех языках.

Перегрузка — это часть полиморфизма, одной из ключевых составляющих объектно-ориентированного программирования. Главный плюс перегрузки в Java — можно использовать схожие методы с одинаковыми именами.

Переопределение метода

Переопределение метода в Java позволяет взять метод родительского класса и создать специфическую реализацию в классе-наследнике.

Проще понять на примере. Допустим, вы создаёте класс Animal с методом voice(). Он нужен для того, чтобы животное могло подать голос:

public class Animal  
public void voice()
System.out.println("Говори!");
>
>

И сразу возникает проблема — все животные издают разные звуки. Можно создать для каждого отдельный метод. Например, у кошки это будет voiceCat(), а у собаки — voiceDog(). Но представьте, сколько строк кода придётся написать, чтобы дать возможность всем животным подать голос?

Здесь на помощь и приходит механизм переопределения в Java. Он позволяет заменить реализацию в классе-наследнике. Посмотрим на примере кошки и собаки:

public class Cat extends Animal  
@Override
public void voice() System.out.println("Мяу!");
>
>

public class Dog extends Animal
@Override
public void voice() System.out.println("Гав!");
>
>

В выводе отобразится сначала «Мяу», а затем — «Гав». Чтобы добиться такого результата, нужно:

  1. В классе-наследнике создать метод с таким же именем, как в родительском классе.
  2. Добавить перед ним аннотацию @Override (с английского переводится как «переопределён»). Эта аннотация сообщит компилятору, что это не ошибка, вы намеренно переопределяете метод. Отметим, что наличие аннотации необязательно. Если в дочернем классе создать метод с такой же сигнатурой, как у родительского, метод все равно переопределится. Но рекомендуется ставить аннотацию всегда, так как это улучшает «читабельность» кода, а также при этом компилятор проверит на этапе сборки, что такой метод действительно есть в родительском классе.

Собственная реализация пишется для каждого класса-наследника. Если этого не сделать, то будет использована реализация родительского класса.

Даже после переопределения вы можете обратиться к методу родительского класса при условии, что он не определён модификатором private. Для этого используется ключевое слово super:

super.method();

Ограничения при переопределении

У переопределения методов класса в Java есть ряд ограничений.

  • Название метода должно быть таким же, как у метода родителя (то есть сигнатура метода должна быть одинаковой).
  • Аргументы должны оставаться такими же, как у метода родителя.
  • Тип возвращаемого значения должен быть таким же, как у метода родителя.
  • Модификатор доступа должен быть таким же, как у метода родителя.
  • Окончательные методы (final) нельзя переопределять. Это один из способов запрета переопределения — объявить метод с помощью ключевого слова final.
class Parent final void show() <>
>

class Child extends Parent void show() <>
>

Такой код вернёт ошибку, потому что в родительском классе использовано ключевое слово final.

  • Статические методы (static) нельзя переопределять. Если вы определите в классе-наследнике такую же сигнатуру метода, как в родительском классе, то выполните сокрытие. Подробнее об этом вы можете прочитать в документации .
  • Приватные методы (private) нельзя переопределять, так как они связываются на этапе компиляции, а не выполнения.
  • Нельзя сужать модификатор доступа — например, с public до private. Расширение уровня доступа возможно.
  • Нельзя менять тип возвращаемого значения, однако можно сузить возвращаемое значение, если они совместимы.

Переопределение класса в Java подчиняется указанным выше правилам, которые необходимо соблюдать.

Собственные правила переопределения есть у отдельных методов. Например, equals() и hashCode(). Самое важное условие — если вы переопределяете equals(), то должны переопределить и hashCode(). В противном случае классы и методы, которые пользуются контрактами стандартной реализации этих двух методов, могут работать с ошибками. Подробнее от этом мы рассказали в отдельной статье.

Заключение

Переопределение и перегрузка методов в Java — важные части полиморфизма, однако это разные механизмы. При перегрузке вы создаёте внутри одного класса много методов с одинаковым названием, но разными параметрами. При переопределении вы берёте один и тот же метод и заставляете его делать разные вещи в зависимости от того, в каком классе его вызвали.

Но есть характеристики, в которых перегрузка и переопределение Java похожи. Оба механизма помогают сделать код чище и читабельнее, а также уменьшить количество ошибок при выполнении программ.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *