Что такое сигнатура метода java
Включает ли Сигнатура метода тип возвращаемого значения в Java?
Узнайте, почему сигнатуры методов состоят из имени и списка типов параметров в Java.
1. Обзор
Сигнатура метода является только подмножеством всего определения метода в Java. Таким образом, точная анатомия подписи может вызвать путаницу.
В этом уроке мы изучим элементы сигнатуры метода и ее значение в программировании на Java.
2. Сигнатура метода
Давайте подробнее рассмотрим перегрузку метода и то, как она связана с сигнатурами методов.
3. Ошибки Перегрузки
Давайте рассмотрим следующий код :
Как мы видим, код компилируется, поскольку методы имеют разные списки типов параметров. По сути, компилятор может детерминированно привязать любой вызов к одному или другому.
Теперь давайте проверим, законно ли перегружать, добавив следующий метод:
Давайте попробуем то же самое с модификаторами:
Перегрузка путем изменения брошенных исключений может быть проверена путем добавления:
Последнее, что мы можем проверить, – это то, допускает ли изменение имен параметров перегрузку. Давайте добавим следующий метод:
3. Дженерики и стирание типов
Давайте рассмотрим следующий код:
Несмотря на то, что сигнатуры выглядят по-разному, компилятор не может статически привязать правильный метод после удаления типа.
4. Списки параметров и полиморфизм
Сигнатура метода учитывает точные типы. Это означает, что мы можем перегрузить метод, тип параметра которого является подклассом или суперклассом.
Давайте взглянем на следующий код:
Приведенный выше код совершенно легален и будет компилироваться. При вызове этих методов может возникнуть путаница, поскольку нам нужно не только знать точную сигнатуру вызываемого метода, но и то, как Java статически связывается на основе фактических значений.
Давайте рассмотрим несколько вызовов методов, которые в конечном итоге привязываются к sum(Integer, Integer) :
Для первого вызова у нас есть точные типы параметров Integer, Integer. При втором вызове Java автоматически установит int в Integer для нас . Наконец, Java преобразует значение байта 0x1 в int с помощью продвижения типа, а затем автоматически установит его в Integer.
Аналогично, у нас есть следующие вызовы, которые привязываются к sum(Number, Number) :
При первом вызове у нас есть double значения, которые автоматически преобразуются в Double. А затем, с помощью полиморфизма, Double соответствует Числу. Идентично, Float соответствует Номеру для второго вызова.
Теперь рассмотрим следующий вызов метода:
Чтобы изменить привязку по умолчанию, мы можем использовать явное приведение параметров следующим образом:
5. Параметры Vararg
Теперь давайте обратим наше внимание на то, как varargs влияет на эффективную сигнатуру метода и статическую привязку.
Здесь у нас есть перегруженный метод, использующий varargs :
Итак, каковы эффективные сигнатуры методов? Мы уже видели, что sum(Объект, Объект) является сигнатурой для первого. Переменные аргументы по сути являются массивами, поэтому эффективной сигнатурой для второго после компиляции является sum(Object, Object[]).
Сложный вопрос заключается в том, как мы можем выбрать привязку метода, когда у нас есть только два параметра?
Давайте рассмотрим следующие вызовы:
Последнее, что следует отметить здесь, это то, что объявление следующего метода будет конфликтовать с версией vararg:
6. Заключение
В этом уроке мы узнали, что сигнатуры метода состоят из имени и списка типов параметров. Модификаторы, тип возвращаемого значения, имена параметров и список исключений не могут различать перегруженные методы и, таким образом, не являются частью сигнатуры.
Мы также рассмотрели, как стирание типов и varargs скрывают эффективную сигнатуру метода и как мы можем переопределить привязку статического метода Java.
Сигнатура метода — Java: Основы
Возврат значений
Методы в Java становятся по настоящему полезными, когда они начинают возвращать данные, вместо печати их на экран. Тогда мы можем их использовать для дальнейших вычислений. Посмотрите на пример:
Мы присваиваем переменной результат работы метода. Для работы такого кода, метод должен внутри себя использовать инструкцию return :
return — особая инструкция, которая берет выражение записанное справа и отдает его наружу, тому коду, который вызвал метод. Само выполнение метода на этом завершается, любой код после return не выполняется:
И пример с вычислением:
Параметры методов
Для добавления параметров в собственные методы, достаточно указать их в скобках при определении метода. Ниже пример определения статического метода, который возвращает последний символ в строке (в Java нет такого встроенного):
Точно таким же образом можно указывать два, три и более параметров:
Необязательные параметры методов
В программировании большое количество функций и методов имеют параметры, которые редко меняются. В таких случаях этим параметрам задают значения по умолчанию, которые можно поменять по необходимости. Этим немного сокращается количество одинакового кода. Гипотетический пример:
В Java нет возможности задать значение по умолчанию, но, ее можно имитировать с помощью перегрузки методов. Что это такое? Java позволяет создать несколько методов с одинаковым именем. У этих методов должны быть либо другие типы входных параметров, либо другое число параметров или все это одновременно. Посмотрим на примере метода, складывающего два числа:
Компилятор без проблем выполнит такой код и создаст два метода с одним именем. Каким образом Java узнает какой метод нужно использовать? Все очень просто, во время компиляции выбирается та версия метода, которая совпадает по типу и количеству параметров. Если такой метод не был найден, то возникнет ошибка.
Перегрузка методов, может приводит к дублированию кода, особенно когда речь идет про значения по умолчанию. Логика, в таких ситуациях, одинаковая, разница лишь в начальной инициализации. Для снижения дублирования достаточно определить сначала общий метод, который принимает больше всего параметров, а затем вызывать его из тех методов, где есть значения по умолчанию:
В этом примере мы не сократили код, но он наглядно показывает принцип описанный выше.
Сигнатура и контракт
Теперь когда мы изучили объявление методов, использование параметров и возврат значений, можно перейти к понятию сигнатура метода. Комбинация имени метода и его параметров, включая их количество и порядок, называется сигнатурой. А контрактом метода называется сочетание его сигнатуры и возвращаемого значения, включая его тип. Забегая немного вперёд, стоит отметить, что в контракт метода также входит бросаемые им исключения, о них мы поговорим в дальнейших курсах.
Рассмотрим примеры сигнатур:
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты.
Перегрузка, которая запрещена, или bridge-методы в Java
В большинстве моих собеседований на технические позиции есть задача, в которой кандидату необходимо реализовать 2 очень похожих интерфейса в одном классе:
Реализуйте оба интерфейса одним классом, если это возможно. Объясните, почему это возможно или нет.
От переводчика: Эта статья не призывает вас задавать такие же вопросы на интервью. Но если вы хотите быть во всеоружии, когда этот вопрос зададут вам, то добро пожаловать под кат.
Иногда соискатели, которые не очень уверены в ответе, предпочитают решить вместо этой задачу со следующим условием (позже я в любом случае прошу ее решить):
И правда, вторая задача кажется намного проще, и большинство кандидатов отвечают, что включение обоих методов в один и тот же класс невозможно, потому что сигнатуры S.m(int) и V.m(int) одинаковы, в то время как тип возвращаемого значения — разный. И это абсолютно верно.
Однако иногда я задаю другой вопрос, связанный с этой темой:
Как вы думаете, есть ли смысл в том, чтобы допускать реализацию методов с одинаковой сигнатурой, но разными типами в одном классе? Например, в неком гипотетическом языке на базе JVM или хотя бы на уровне JVM?
Это вопрос, ответ на который неоднозначен. Но, не смотря на то, что я не ожидаю ответа на него, правильный ответ существует. Ответить на него смог бы человек, который часто имеет дело с API рефлексии, манипулирует байт-кодом или знаком со спецификацией JVM.
Сигнатура метода Java и дескриптор метода JVM
Сигнатура метода Java (т.е. название метода и типы параметров) применяется только Java компилятором во время компиляции. В свою очередь, JVM разделяет методы в классе с помощью неквалифицированного имени метода (то есть просто имени метода) и дескриптора метода, то есть перечня параметров дескриптора и одного return-дескриптора.
а для void m(int i) следующий:
Таким образом, JVM вполне комфортно себя чувствует с String m(int i) и void m(int i) в одном классе. Все, что нужно, — это сгенерировать соответствующий байт-код.
Кунг-фу с байт-кодом
У нас есть интерфейсы S и V, теперь мы создадим класс SV, который включает оба интерфейса. В Java, если бы это было разрешено, это должно выглядеть так:
Чтобы сгенерировать байт-код, мы используем Objectweb ASM library, достаточно низкоуровневую библиотеку, чтобы получить представление о JVM байт-коде.
Полный исходный код залит на GitHub, здесь же я приведу и поясню только наиболее важные фрагменты.
Начнем с создания ClassWriter для генерации байт-кода.
Теперь мы объявим класс, в который входят интерфейсы S и V.
Хотя наш референсный псевдо-java код для SV не имеет конструкторов, нам все равно нужно генерировать код для него. Если мы не описываем конструкторы на Java, компилятор неявно генерирует пустой конструктор.
В теле методов мы начнем с получения поля System.out с типом java.io.PrintStream и добавления его в стек операндов. Затем загружаем константу ( String или void ) в стек и вызываем команду println в полученной переменной out со строковой константой в качестве аргумента.
и используем jad (декомпилятор Java), чтобы перевести байт-код обратно в исходный код на Java:
Использование сгенерированного класса
Успешная декомпиляция jad по сути ничего нам не гарантирует. Утилита jad оповещает только об основных проблемах в байт-коде, от таких, как размер фрейма, до несоответствия локальных переменных или отсутствующего оператора возврата.
Чтобы использовать сгенерированный класс во время исполнения, нам необходимо каким-то образом загрузить его в JVM и затем создать его экземпляр.
Теперь используем этот class loader и создадим экземпляр класса:
Поскольку наш класс сгенерирован во время исполнения, мы не можем использовать его в исходном коде. Зато мы можем привести его тип к реализованным интерфейсам. А вызов без рефлексии можно осуществить так:
При выполнении кода мы получим следующий вывод:
Кому-то такой вывод покажется неожиданным: мы обращаемся к одному и тому же (с точки зрения Java) методу в классе, но результаты различаются в зависимости от интерфейса, к которому мы привели объект. Сногсшибательно, правда?
Все станет понятно, если принять во внимание лежащий в основе байт-код. Для нашего вызова компилятор генерирует инструкцию INVOKEINTERFACE, и дескриптор метода исходит не из класса, а из интерфейса.
Таким образом, при первом вызове мы получим:
Объект, на котором мы выполнили вызов, можно получить из стека. Это и есть могущество полиморфизма, присущее Java.
Имя ему — bridge-метод
Кто-то спросит: «Так в чем смысл всего этого? Пригодится ли это когда-нибудь?»
Смысл в том, что мы используем всё то же самое (неявно) при написании обычного Java кода. Например, ковариантные возвращаемые типы, дженерики и доступ к private-полям из внутренних классов реализуются с помощью такой же магии байт-кода.
Взгляните на такой интерфейс:
и его реализацию с возвратом ковариантного типа:
Теперь подумаем над этим кодом:
По сути, компилятор генерирует дополнительный метод, выполняющий роль моста между реальным методом, указанным в классе, и методом, используемым при вызове через интерфейс. Отсюда название — bridge-метод. Если бы в Java такое было возможно, конечный код выглядел бы так:
Послесловие
Язык программирования Java и виртуальная машина Java — это не одно и то же: хотя они имеют в названии общее слово и Java является основным языком для JVM, их возможности и ограничения далеко не всегда одинаковы. Знание JVM помогает лучше понимать Java или любой другой основанный на JVM язык, но, с другой стороны, знание Java и его истории помогают понять определенные решения в дизайне JVM.
От переводчика
Вопросы совместимости рано или поздно начинают волновать любого разработчика. В исходной статье затронут важный вопрос о неявном поведении компилятора Java и влиянии его магии на приложения, который нас как разработчиков фреймворка CUBA Platform волнует довольно сильно, — это напрямую влияет на совместимость библиотек. Совсем недавно мы рассказывали о совместимости в реальных приложениях на JUG в Екатеринбурге в докладе «API на переправе не меняют — как построить стабильный API», видео встречи можно найти по ссылке.
Собеседование по Java — ООП (вопросы и ответы). Часть 1.
Вопросы и ответы по теме ООП (объектно ориентированное программирование) для собеседования по Java.
К списку вопросов по всем темам
Список всех вопросов по ООП
1. Назовите принципы ООП и расскажите о каждом.
2. Дайте определение понятию “класс”.
3. Что такое поле/атрибут класса?
4. Как правильно организовать доступ к полям класса?
5. Дайте определение понятию “конструктор”.
6. Чем отличаются конструкторы по умолчанию, копирования и конструктор с параметрами?
7. Какие модификации уровня доступа вы знаете, расскажите про каждый из них.
8. Расскажите об особенностях класса с единственным закрытым (private) конструктором.
9. О чем говорят ключевые слова “this”, “super”, где и как их можно использовать?
10. Дайте определение понятию “метод”.
11. Что такое сигнатура метода?
12. Какие методы называются перегруженными?
13. Могут ли нестатические методы перегрузить статические?
14. Расскажите про переопределение методов.
15. Может ли метод принимать разное количество параметров (аргументы переменной длины)?
16. Можно ли сузить уровень доступа/тип возвращаемого значения при переопределении метода?
17. Как получить доступ к переопределенным методам родительского класса?
18. Какие преобразования называются нисходящими и восходящими?
19. Чем отличается переопределение от перегрузки?
20. Где можно инициализировать статические/нестатические поля?
21. Зачем нужен оператор instanceof?
22. Зачем нужны и какие бывают блоки инициализации?
23. Каков порядок вызова конструкторов и блоков инициализации двух классов: потомка и его предка?
24. Где и для чего используется модификатор abstract?
25. Можно ли объявить метод абстрактным и статическим одновременно?
26. Что означает ключевое слово static?
27. К каким конструкциям Java применим модификатор static?
28. Что будет, если в static блоке кода возникнет исключительная ситуация?
29. Можно ли перегрузить static метод?
30. Что такое статический класс, какие особенности его использования?
31. Какие особенности инициализации final static переменных?
32. Как влияет модификатор static на класс/метод/поле?
33. О чем говорит ключевое слово final?
34. Дайте определение понятию “интерфейс”.
35. Какие модификаторы по умолчанию имеют поля и методы интерфейсов?
36. Почему нельзя объявить метод интерфейса с модификатором final или static?
37. Какие типы классов бывают в java (вложенные… и.т.д.)
38. Какие особенности создания вложенных классов: простых и статических.
39. Что вы знаете о вложенных классах, зачем они используются? Классификация, варианты использования, о нарушении инкапсуляции.
40. В чем разница вложенных и внутренних классов?
41. Какие классы называются анонимными?
42. Каким образом из вложенного класса получить доступ к полю внешнего класса?
43. Каким образом можно обратиться к локальной переменной метода из анонимного класса, объявленного в теле этого метода? Есть ли какие-нибудь ограничения для такой переменной?
44. Как связан любой пользовательский класс с классом Object?
45. Расскажите про каждый из методов класса Object.
46. Что такое метод equals(). Чем он отличается от операции ==.
47. Если вы хотите переопределить equals(), какие условия должны удовлетворяться для переопределенного метода?
48. Если equals() переопределен, есть ли какие-либо другие методы, которые следует переопределить?
49. В чем особенность работы методов hashCode и equals? Каким образом реализованы методы hashCode и equals в классе Object? Какие правила и соглашения существуют для реализации этих методов? Когда они применяются?
50. Какой метод возвращает строковое представление объекта?
51. Что будет, если переопределить equals не переопределяя hashCode? Какие могут возникнуть проблемы?
52. Есть ли какие-либо рекомендации о том, какие поля следует использовать при подсчете hashCode?
53. Как вы думаете, будут ли какие-то проблемы, если у объекта, который используется в качестве ключа в hashMap изменится поле, которое участвует в определении hashCode?
54. Чем отличается абстрактный класс от интерфейса, в каких случаях что вы будете использовать?
55. Можно ли получить доступ к private переменным класса и если да, то каким образом?
56. Что такое volatile и transient? Для чего и в каких случаях можно было бы использовать default?
57. Расширение модификаторов при наследовании, переопределение и сокрытие методов. Если у класса-родителя есть метод, объявленный как private, может ли наследник расширить его видимость? А если protected? А сузить видимость?
58. Имеет ли смысл объявлять метод private final?
59. Какие особенности инициализации final переменных?
60. Что будет, если единственный конструктор класса объявлен как final?
61. Что такое finalize? Зачем он нужен? Что Вы можете рассказать о сборщике мусора и алгоритмах его работы.
62. Почему метод clone объявлен как protected? Что необходимо для реализации клонирования?
Ответы. Часть 1
1. Назовите принципы ООП и расскажите о каждом.
Объе́ктно-ориенти́рованное программи́рование (ООП) — это методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.
Основные принципы ООП: абстракция, инкапсуляция, наследование, полиморфизм.
Абстракция — означает выделение значимой информации и исключение из рассмотрения незначимой. С точки зрения программирования это правильное разделение программы на объекты. Абстракция позволяет отобрать главные характеристики и опустить второстепенные.
Пример: описание должностей в компании. Здесь название должности значимая информация, а описание обязанностей у каждой должности это второстепенная информация. К примеру главной характеристикой для «директор» будет то, что это должность чем-то управляет, а чем именно (директор по персоналу, финансовый директор, исполнительный директор) это уже второстепенная информация.
Инкапсуляция — свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Для Java корректно будет говорить, что инкапсуляция это «сокрытие реализации». Пример из жизни — пульт от телевизора. Мы нажимаем кнопочку «увеличить громкость» и она увеличивается, но в этот момент происходят десятки процессов, которые скрыты от нас. Для Java: можно создать класс с 10 методами, например вычисляющие площадь сложной фигуры, но сделать из них 9 private. 10й метод будет называться «вычислитьПлощадь()» и объявлен public, а в нем уже будут вызываться необходимые скрытые от пользователя методы. Именно его и будет вызывать пользователь.
Наследование — свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым, родительским или суперклассом. Новый класс — потомком, наследником, дочерним или производным классом.
Полиморфизм — свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта. Пример (чуть переделанный) из Thinking in Java:
Java / Сигнатура метода и перегрузка методов в Java.
Автор: ksp107 ← к списку ← →
Понятие сигнатура метода используется в Java в различных контекстах и в зависимости от ситуации может определяться по-разному.
В простейшем случае под сигнатурой метода понимают совокупность имени метода и списка типов его аргументов. Например, у первого метода будет сигнатура method(double, int)
Правила языка запрещают в одном классе иметь несколько методов, отличающихся только типом возвращаемого значения. Это нужно для того, чтобы компилятор, анализируя вызов метода, смог сгенерировать корректный код.
Более полное понятие сигнатуры метода включает в себя:
– список типов формальных параметров метода (возможно, параметризованные);
– список типов исключений, выбрасываемых методом;
– тип возвращаемого методом значения (возможно, параметризованный);
– формальные типы-параметры метода.
Именно эта информация сохраняется в бинарном представлении классов (байт-коде) и используется виртуальной машиной Java при вызове методов.
Если использовать некоторые приёмы, мы можем получить в одном классе несколько методов, отличающихся только возвращаемым типом.
Рассмотрим следующий код:
Оказывается, если при перекрытии метода сужать возвращаемый тип, то компилятор в классе-потомке неявно создаст дополнительный метод-заглушку, у которого будет точно такая же сигнатура, как и у исходного метода. Т.е. результат компиляции класса B будет соответствовать следующему условному коду:
Нечто подобное используют обфускаторы кода, правда, с другой целью – усложнить реверс-анализ байт-кода 🙂
Если Вам понравился вопрос, проголосуйте за него
Голосов: 52 Голосовать