Чем отличается перегрузка от переопределения?
Полиморфизм – соль ООП. Перегрузка (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. В различных источниках могут использоваться другие варианты перевода. В любом случае, первичным остается смысл оригинальных терминов на английском.