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

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

  • автор:

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

Полиморфизм – соль ООП. Перегрузка (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 . Ее отсутствие допускается, но компиляция не перегружающего метода с такой аннотацией приведет к ошибке.

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

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

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

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

На этом шаге мы рассмотрим отличие переопределения от перегрузки методов .

Мы познакомились с переопределением метода, когда в производном классе описывается новая версия унаследованного метода. В данном случае речь идет о полном совпадении типа, названия и списка аргументов метода в базовом и производном классах. Но еще есть такой механизм, как перегрузка методов. При перегрузке метода описывается несколько версий этого метода. Для каждой версии название одно и то же, но вот списки аргументов должны отличаться. Эти два механизма (перегрузка и переопределение) могут использоваться одновременно. В примере ниже представлена программа, в которой на фоне наследования используется перегрузка и переопределение методов.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr147_1 < // Базовый класс: class Alpha < // Целочисленное поле: public int alpha; // Метод без аргументов: public void set() < // Значение поля: alpha = 10; // Отображение значения поля: Console.WriteLine("Alpha (без аргументов): ", alpha); > // Метод (виртуальный) с одним аргументом: public virtual void set(int n) < // Значение поля: alpha = n; // Отображение значения поля: Console.WriteLine ("Alpha (один: аргумент): ", alpha); > > // Производный класс: class Bravo: Alpha < // Целочисленное поле: public int bravo; // Переопределение виртуального метода: public override void set(int n) < // Присваивание значений полям: alpha = n; bravo = alpha; // Отображение значений полей: Console.WriteLine("Bravo (один аргумент): и ", alpha, bravo); > // Метод с двумя аргументами: public void set(int m, int n) < // Присваивание значений полям: alpha = m; bravo = n; // Отображение значений полей: Console.WriteLine("Bravo (два аргумента): и ", alpha, bravo); > > // Класс с главным методом: class Program < // Главный метод: static void Main() < // Создание объекта базового класса: Alpha A = new Alpha(); // Вызов методов: A.set(); A.set(20); Console.WriteLine(); // Создание объекта производного класса: Bravo B = new Bravo(); // Вызов методов: B.set(); B.set(30); B.set(40, 50); // Задержка: Console.ReadLine(); > > >

Архив проекта можно взять здесь.

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

Рис.1. Результат выполнения программы

У нас есть два класса: Alpha и Bravo , причем класс Bravo является производным от класса Alpha . В классе Alpha имеется открытое целочисленное поле alpha , которое наследуется в классе Bravo . Еще в классе Bravo появляется целочисленное поле bravo . В классе Alpha описаны две версии метода set() : без аргументов и с одним аргументом. Версия метода set() с одним аргументом описана как виртуальная (использовано ключевое слово virtual ). В классе Bravo появляется еще одна версия метода set() с двумя аргументами, а версия метода с одним аргументом переопределяется (она описана с ключевым словом override ). Каждая версия метода описана так, что полю или полям присваиваются значения, после чего в консольное окно выводится сообщение с указанием названия класса, количества аргументов у метода и фактического значения поля или полей объекта. Таким образом, по сообщению, отображаемому в консольном окне при вызове метода, можно однозначно определить, какая версия метода была вызвана.

В главном методе программы мы создаем объект А класса Alpha и объект B класса Bravo . Из каждого объекта вызываются разные версии метода set() . Для объекта А это две версии (обе описаны в классе Alpha ): без аргументов и с одним аргументом. Для объекта В таких версий три. Это, во-первых, версия метода без аргументов, унаследованная из класса Alpha . Во-вторых, переопределенная в классе Bravo версия метода с одним аргументом. И в-третьих, версия с двумя аргументами, описанная в классе Bravo .

На следующем шаге мы рассмотрим наследование свойств и индексаторов .

Переопределение и перегрузка в 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. Переопределение происходит, когда сигнатура метода в суперклассе и дочернем классе одинакова. Перегрузка возникает, когда два или более метода в одном классе имеют одинаковое имя, но разные параметры.

Как правильно: перегрузка или переопределение?

Чтобы далеко не ходить за примерами я стал искать прямо на сайте ответы по интересующим меня методам Equals и OnModelCreating, которые описаны в документации как виртуальные, и нашел следующие ответы:

  • «следует перегрузить метод OnModelCreating — и написать там примерно следующее»
    27 май ’15 в 15:53, Pavel Mayorov, 9,255
  • «базовый класс всех типов-значений, перегружает метод Equals»
    30 сен ’15 в 0:01, VladD, 74k
  • «Переопределите Equals так, чтобы он сравнивал два экземпляра»
    23 дек ’15 в 8:52, PashaPash, 20.1k

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

UPD: (2/10/2016 8:52 PM)

Перегрузка и переопределение, как сказал rdom: это абсолютно разные вещи.
VladD внес исправления в свой ответ.
Ошибки в других ответах, надеюсь, также будут исправлены.

Отслеживать
задан 9 фев 2016 в 20:36
203 2 2 серебряных знака 11 11 бронзовых знаков

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

По смыслу — это абсолютно разные вещи.

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

Переопределение (Overriding) — относится к виртуальным и абстрактным методам. Это возможность замены виртуального или реализация абстрактного наследуемого метода базового класса методом производного класса.

class A < virtual void SomeMethod(int par1, double par2) < >//Делаем перегруженный вариант метода для другого набора параметров void SomeMethod(double par1, int par2) <> > class B : A < //Переопределяем унаследованный виртуальный метод override void SomeMethod(int par1, double par2) < >> 

UPD

в наследниках или в производных?

тут разночтений нет. Производный класс, класс наследник, а также принятые в Java суб-класс или подкласс, являются синонимами. Также синонимами являются базовый класс, класс предок, супер-класс(java) и над-класс(java).

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

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

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