Сигнатура метода java что это
Перейти к содержимому

Сигнатура метода java что это

  • автор:

Method Signature in Java

Method Signature in java is defined as the structure of a method that is designed by the programmer. Method Signature is the combination of a method's name and its parameter list. A class cannot have two methods with the same signature. If we declare two methods with the same signature, compilation error is thrown.

Method signature does not include the return type of a method.

Why do we need a Method Signature in Java?

Let's suppose you want to declare two methods of the same name for example getArea , the first method is used to find the area of a rectangle, and another method of the same name is used to find the area of a square.

How can you define two methods of the same name? The answer is to use different method signatures of both methods. We can declare two methods of same name by creating a difference in the parameter list of both methods. Two parameter lists are different if either of the following conditions are satisfied:

  • Different number of parameters in both parameter lists
  • At least one parameter is of different type

This event of declaring two methods with same name but different parameter lists (or signature) is called Method Overloading.

An example of Method Overloading is provided below. You can notice, there are two methods with same name sum but different number of parameters.

Let's understand how to implement method signature in java.

Example of Method Overloading

In the example below, we are creating two methods of the same name i.e getArea with method signatures. The first method is used to calculate the area of a rectangle, and the second method is used to calculate the area of a square.

Output:

Example 1: Java Method Signature

In Java, we can have four forms of method signatures. Let's understand each of them one by one.

Without return type and no parameters.

In this form, the method doesn't have its return type i.e the return type is void and without a parameter.

The printName() method doesn't have any return type and parameter in the below example.

Output:

Without Return Type and with Parameter.

In this form, the method doesn't have its return type i.e the return type is void but contains parameters.

The printName() method doesn't have any return type but contains a parameter of String type.

Output:

With return type and no parameter

In this form, the method has its return type but without any parameters.

Output:

With return type and with Parameter.

In this form, the method has both return type and parameters .

The printName() method has a return type of String , and also contains a parameter of String type.

Output:

Example 2: Class with Two Methods with the Same Signature

In java, we cannot implement two methods of the same signature i.e of the same method name and same parameter list. Because it will create a confusion at run time regarding which method to call. Even if the return types are different, two methods with same signatures will throw compile-time error.

Let's understand this using an example.

In the below program we are implementing two methods of the same signature. Let's see what will happen when we try to execute our program.

Output:

Learn More

We can learn more about Java Methods and their characteristics through this article: Methods in Java

Перегрузка методов (Method Overloading) #

В Java несколько методов из одного класса могут иметь одно название.

Одно название и несколько методов #

Давайте сразу рассмотрим пример.

Очевидное-невероятное, но Java сама выбирает подходящий метод, для решения проблемы.Если мы передадим ей два int, то выберется первый метод, если два double, то JDK выберет самостоятельно нижний метод.

Мы можем создать метод с двумя long или с двумя String.

Каждый раз сигнатура метода будет меняться и Java выбирает подходящий.

Сигнатура метода #

Сигнатура метода — это название метода и принимаемые им параметры. У метода выше сигнатурой называется addition(int a, int b). При изменении параметров меняется и сигнатура.

The signature of the method declared above is: calculateAnswer(double, int, double, double);

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

Количество принимаемых атрибутов #

Мы можем менять не только типы “переменных”, но и их количество.

В наш класс Calculator мы можем добавить новые методы:

Сигнатура не должна повторяться. Java видит название метода и шесть разных вариантов переменных:

  1. Два инта
  2. Три инта
  3. Два double
  4. Три double
  5. Два String
  6. Три String

Вполне возможно передать один String и один int. Но это можно сделать лишь единожды. Сигнатура каждый раз должна отличаться.

Перегрузка методов позволяет прописать свои свойства даже для простого сложения. В главе, в которой мы знакомились с Assert мы прописывали максимальное значение Integer. С помощью перегрузки мы должны прописать правильный и корректный метод для каждого типа данных. Возможно и для Vararg понадобится свой метод.

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

Забегая вперёд в Java три важные части или даже кита, на которых она стоит.

Три(четыре) кита на которых стоит Java #

  1. Наследование — inheritance.
  2. Инкапсуляция — encapsulation.
  3. Полиморфизм — polymorphism.
    • перегрузка — overloading.
    • переопределение — overriding
  4. Абстракция — data abstraction.

Очень часто абстракцию не указывают как несущую часть ООП, считая её частью наследования. Я для себя истину не выяснил. В спорах участвовать мне лень. Вам я привожу оба существующих мнения.

В течении следующей дюжины глав я попробую осветить все эти термины.

Полиморфизм #

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

Defining Methods

The only required elements of a method declaration are the method's return type, name, a pair of parentheses, () , and a body between braces, <> .

More generally, method declarations have six components, in order:

  1. Modifiers—such as public , private , and others you will learn about later.
  2. The return type—the data type of the value returned by the method, or void if the method does not return a value.
  3. The method name—the rules for field names apply to method names as well, but the convention is a little different.
  4. The parameter list in parenthesis—a comma-delimited list of input parameters, preceded by their data types, enclosed by parentheses, () . If there are no parameters, you must use empty parentheses.
  5. An exception list—to be discussed later.
  6. The method body, enclosed between braces—the method's code, including the declaration of local variables, goes here.

Modifiers, return types, and parameters will be discussed later in this lesson. Exceptions are discussed in a later lesson.

The signature of the method declared above is:

Naming a Method

Although a method name can be any legal identifier, code conventions restrict method names. By convention, method names should be a verb in lowercase or a multi-word name that begins with a verb in lowercase, followed by adjectives, nouns, etc. In multi-word names, the first letter of each of the second and following words should be capitalized. Here are some examples:

Typically, a method has a unique name within its class. However, a method might have the same name as other methods due to method overloading.

Overloading Methods

The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists (there are some qualifications to this that will be discussed in the lesson titled "Interfaces and Inheritance").

Suppose that you have a class that can use calligraphy to draw various types of data (strings, integers, and so on) and that contains a method for drawing each data type. It is cumbersome to use a new name for each method—for example, drawString , drawInteger , drawFloat , and so on. In the Java programming language, you can use the same name for all the drawing methods but pass a different argument list to each method. Thus, the data drawing class might declare four methods named draw , each of which has a different parameter list.

Overloaded methods are differentiated by the number and the type of the arguments passed into the method. In the code sample, draw(String s) and draw(int i) are distinct and unique methods because they require different argument types.

You cannot declare more than one method with the same name and the same number and type of arguments, because the compiler cannot tell them apart.

The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type.

Перегрузка, которая запрещена, или bridge-методы в Java

В большинстве моих собеседований на технические позиции есть задача, в которой кандидату необходимо реализовать 2 очень похожих интерфейса в одном классе:

Реализуйте оба интерфейса одним классом, если это возможно. Объясните, почему это возможно или нет.

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

Иногда соискатели, которые не очень уверены в ответе, предпочитают решить вместо этой задачу со следующим условием (позже я в любом случае прошу ее решить):

И правда, вторая задача кажется намного проще, и большинство кандидатов отвечают, что включение обоих методов в один и тот же класс невозможно, потому что сигнатуры S.m(int) и V.m(int) одинаковы, в то время как тип возвращаемого значения — разный. И это абсолютно верно.

Однако иногда я задаю другой вопрос, связанный с этой темой:

Как вы думаете, есть ли смысл в том, чтобы допускать реализацию методов с одинаковой сигнатурой, но разными типами в одном классе? Например, в неком гипотетическом языке на базе JVM или хотя бы на уровне JVM?

Это вопрос, ответ на который неоднозначен. Но, не смотря на то, что я не ожидаю ответа на него, правильный ответ существует. Ответить на него смог бы человек, который часто имеет дело с API рефлексии, манипулирует байт-кодом или знаком со спецификацией JVM.

Сигнатура метода Java и дескриптор метода JVM

Сигнатура метода Java (т.е. название метода и типы параметров) применяется только Java компилятором во время компиляции. В свою очередь, JVM разделяет методы в классе с помощью неквалифицированного имени метода (то есть просто имени метода) и дескриптора метода, то есть перечня параметров дескриптора и одного return-дескриптора.

Например, если мы хотим вызвать метод String m(int i) непосредственно на классе foo.Bar , необходим следующий байт-код:

а для 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 со строковой константой в качестве аргумента.

Наконец, для String m(int i) добавляем в стек константу ссылочного типа со значением null и используем оператор return соответствующего типа, то есть ARETURN , чтобы вернуть значение в инициатор вызова метода. Для void m(int i) необходимо использовать нетипизированный RETURN только для того, чтобы вернуться к инициатору вызова метода без возврата значения. Чтобы убедиться в правильности байт-кода (что я делаю постоянно, многократно исправляя ошибки), мы записываем сгенерированный класс на диск.

и используем jad (декомпилятор Java), чтобы перевести байт-код обратно в исходный код на Java:

Использование сгенерированного класса

Успешная декомпиляция jad по сути ничего нам не гарантирует. Утилита jad оповещает только об основных проблемах в байт-коде, от таких, как размер фрейма, до несоответствия локальных переменных или отсутствующего оператора возврата.

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

Давайте реализуем собственный AsmClassLoader . Это просто удобная обёртка для ClassLoader.defineClass :

Теперь используем этот class loader и создадим экземпляр класса:

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

При выполнении кода мы получим следующий вывод:

Кому-то такой вывод покажется неожиданным: мы обращаемся к одному и тому же (с точки зрения Java) методу в классе, но результаты различаются в зависимости от интерфейса, к которому мы привели объект. Сногсшибательно, правда?

Все станет понятно, если принять во внимание лежащий в основе байт-код. Для нашего вызова компилятор генерирует инструкцию INVOKEINTERFACE, и дескриптор метода исходит не из класса, а из интерфейса.

Таким образом, при первом вызове мы получим:

Объект, на котором мы выполнили вызов, можно получить из стека. Это и есть могущество полиморфизма, присущее Java.

Имя ему — bridge-метод

Кто-то спросит: «Так в чем смысл всего этого? Пригодится ли это когда-нибудь?»

Смысл в том, что мы используем всё то же самое (неявно) при написании обычного Java кода. Например, ковариантные возвращаемые типы, дженерики и доступ к private-полям из внутренних классов реализуются с помощью такой же магии байт-кода.

Взгляните на такой интерфейс:

и его реализацию с возвратом ковариантного типа:

Теперь подумаем над этим кодом:

Для iz.getZero() компилятор вызова будет генерировать INVOKEVIRTUAL с методом дескриптора ()Ljava/lang/Integer; , в то время как для zp.getZero() он сгенерирует INVOKEINTERFACE с дескриптором метода ()Ljava/lang/Number; . Мы уже знаем, что JVM выполняет диспетчеризацию вызова объекта с помощью имени и дескриптора метода. Так как дескрипторы разные, эти 2 вызова не могут направляться в один и тот же метод в экземпляре IntegerZero .

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

Послесловие

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

От переводчика

Вопросы совместимости рано или поздно начинают волновать любого разработчика. В исходной статье затронут важный вопрос о неявном поведении компилятора Java и влиянии его магии на приложения, который нас как разработчиков фреймворка CUBA Platform волнует довольно сильно, — это напрямую влияет на совместимость библиотек. Совсем недавно мы рассказывали о совместимости в реальных приложениях на JUG в Екатеринбурге в докладе «API на переправе не меняют — как построить стабильный API», видео встречи можно найти по ссылке.

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

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