Java Challengers #1: Перегрузка методов в JVM

Перегрузка методов в JVM: рассмотрим основные моменты с примерами, разберемся в назначении, пройдемся по примитивным типам и решим задачу!

Всем доброго дня.

У нас уже запустился очередной поток курса «Разработчик Java», но у нас ещё осталось немного материалов, которыми бы хотели с вами поделиться.

Добро пожаловать в серию статей Java Challengers! Эта серия статей посвящена особенностям программирования на Java. Их освоение – ваш путь к становлению в качестве высококвалифицированного программиста на Java.

Освоение техник, рассматриваемых в этой серии статей, требует некоторых усилий, но они будут иметь большое значение в вашем повседневном опыте в качестве java-разработчика. Избежать ошибок проще, когда вы знаете, как правильно применять основные техники программирования Java, и отслеживать ошибки намного проще, когда вы точно знаете, что происходит в вашем java-коде.

Готовы ли вы приступить к освоению основных концепций программирования на Java? Тогда давайте начнем с нашей первой задачки!

Java Challengers #1: Перегрузка методов в JVM

Термин «Перегрузка методов»

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

Что такое перегрузка методов?

Перегрузка методов – это приём программирования, который позволяет разработчику в одном классе для методов с разными параметрами использовать одно и то же имя. В этом случае мы говорим, что метод перегружен.

В Листинге 1 показаны методы с разными параметрами, которые различаются количеством, типом и порядком.

Листинг 1. Три варианта перегрузки методов

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

В Листинге 1 вы видите примитивные типы int и double. Давайте отвлечёмся на минуту и вспомним примитивные типы в Java.

Таблица 1. Примитивные типы в Java

Java Challengers #1: Перегрузка методов в JVM

Зачем мне использовать перегрузку методов?

Использование перегрузки делает ваш код чище и проще для чтения, а также помогает избежать ошибок в программе.

В противоположность Листингу 1 представьте программу, где у вас будет много методов calculate() с именами, похожими на calculate1, calculate2, calculate3… не хорошо, правда? Перегрузка метода calculate() позволяет использовать одно и то же имя и изменять только то, что необходимо, – параметры. Также очень легко найти перегруженные методы, поскольку они сгруппированы в коде.

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

Помните, что изменение имени переменной не является перегрузкой. Следующий код не скомпилируется:

Вы также не можете перегрузить метод, изменяя возвращаемое значение в сигнатуре метода. Этот код также не скомпилируется:

Перегрузка конструктора

Вы можете перегрузить конструктор таким же способом, как и метод:

Решите задачку по перегрузке методов

Готовы ли вы к первому испытанию? Давайте выясним!

Начните с внимательного изучения следующего кода.

Листинг 2. Сложная задача по перегрузке методов

Хорошо. Вы изучили код. Какой будет вывод?

  1. befe
  2. bfce
  3. efce
  4. aecf

Правильный ответ приведён в конце статьи.

Что сейчас произошло? Как JVM компилирует перегруженные методы?

Чтобы понять, что произошло в Листинге 2, вам нужно знать несколько вещей о том, как JVM компилирует перегруженные методы.

Прежде всего, JVM разумно ленива: она всегда будет прилагать наименьшие усилия для выполнения метода. Таким образом, когда вы думаете о том, как JVM обрабатывает перегрузку, имейте в виду три важных особенности компилятора:

  1. Расширение (widening).
  2. Упаковка (autoboxing and unboxing).
  3. Аргументы переменной длины (varargs).

Если вы никогда не сталкивались с этими техниками, то несколько примеров должны вам помочь их понять. Обратите внимание, что JVM выполняет их в том порядке, в котором они указаны.

Вот пример расширения:

Это порядок расширения примитивных типов:

Java Challengers #1: Перегрузка методов в JVM

(Прим. переводчика – В JLS расширение примитивов описано с большими вариациями, например, long может быть расширен во float или в double.)

Пример автоупаковки:

Обратите внимание, что происходит за кулисами при компиляции кода:

А вот пример распаковки:

Вот что происходит при компиляции этого кода:

И пример метода с аргументами переменной длины. Обратите внимание, что методы переменной длины всегда являются последними для выполнения.

Что такое аргументы переменной длины?

Аргументы переменной длины – это просто массив значений, заданный тремя точками (…). Мы можем передать сколько угодно чисел int этому методу.

Например:

Аргументы переменной длины (varargs) очень удобны тем, что значения могут передаваться непосредственно в метод. Если бы мы использовали массивы, нам пришлось бы создать экземпляр массива со значениями.

Расширение: практический пример

Когда мы передаем число 1 прямо в метод executeAction(), JVM автоматически интерпретирует его как int. Вот почему это число не будет передано в метод executeAction(short var).

Если мы передаём число 1.0, JVM также автоматически распознает, что это double.

Конечно, число 1.0 может быть и float, но тип таких литералов предопределен. Поэтому в Листинге 2 выполняется метод executeAction(double var).

Когда мы используем обёртку Double, есть два варианта: либо число может быть распаковано в примитивный тип, либо оно может быть расширено в Object (помните, что каждый класс в Java расширяет класс Object). В этом случае JVM выбирает расширение типа Double в Object, потому что это требует меньше усилий, чем распаковка.

Последним мы передаём 1L, и так как мы указали тип – это long.

Распространенные ошибки с перегрузкой

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

Автоупаковка с обёртками (autoboxing with wrappers)

Java – это строго типизированный язык программирования и, когда мы используем автоупаковку с обёртками, есть несколько вещей, которые мы должны учитывать. Во-первых, следующий код не компилируется:

Автоупаковка будет работать только с типом double потому что, когда вы скомпилируете код, он будет эквивалентен этому:

Этот код скомпилируется. Первый int будет расширен до double и потом будет упакован в Double. Но при автоупаковке нет расширения типов, и конструктор Double.valueof ожидает double, а не int. В этом случае автоупаковка будет работать, если мы сделаем явное приведение типа, например:

Помните, что Integer не может быть Long и Float и не может быть Double. Здесь нет наследования. Каждый из этих типов (IntegerLongFloat, и Double) – Number и Object.

Если Вы сомневаетесь, просто помните, что обёртки чисел (wrapper numbers) могут быть расширены до Number или Object (есть еще много чего, что можно сказать про обёртки, но оставим это для другой статьи).

Литералы чисел в коде

Когда мы не указываем тип числа-литерала, JVM вычислит тип за нас. Если напрямую используем число 1 в коде, то JVM создаст его как int. Если мы попытаемся передать 1 напрямую в метод, который принимает short, то он не скомпилируется.

Например:

Такое же правило будет применяться, когда используется число 1.0. Хотя это может быть и float, JVM будет считать его double.

Другой распространенной ошибкой является предположение, что Double или любая другая обертка лучше подойдет для метода, получающего double.

Факт в том, что JVM требуется меньше усилий для расширения обертки Double в Object вместо её распаковки в примитивный тип double.

Подводя итог, при использовании непосредственно в java-коде, 1 будет int и 1.0 будет double. Расширение – это самый лёгкий путь к выполнению, далее идёт упаковка или распаковка и последней операцией всегда будут методы переменной длины.

Как любопытный факт. Знаете ли вы, что тип char принимает числа?

Что необходимо помнить о перегрузке

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

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

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

Вот порядок самого ленивого пути к исполнению:

  • Первое – расширение (widening).
  • Второе – упаковка (boxing).
  • Третье – аргументы переменной длины (varargs).

Что следует учитывать: сложные ситуации возникают при объявлении чисел напрямую: 1 будет int и 1.0 будет double.

Также помните, что вы можете объявить эти типы явно, используя синтаксис 1F или 1f для float и 1Dили 1d для double.

На этом мы закончим о роли JVM в перегрузке методов. Важно понимать, что JVM по своей сути ленива, и всегда будет следовать по самому ленивому пути.

Ответ

Ответ к Листингу 2 – Вариант 3. efce.

Подробнее о перегрузке методов в Java

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

Узнайте больше о том, почему важно, что Java является строго типизированным языком, и изучите примитивные типы Java.

Изучите ограничения и недостатки перегрузки методов, а также способы их устранения путем использования пользовательских типов и объектов параметров.

Автор статьи

Еще больше полезных материалов:

Блог компании OTUS онлайн-образование


proglib.io

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

Ваш e-mail не будет опубликован.

2 × 5 =