Enthält die Signatur einer Methode den Rückgabetyp in Java?

1. Übersicht

Die Methodensignatur ist nur eine Teilmenge der gesamten Methodendefinition in Java. Daher kann die genaue Anatomie der Signatur zu Verwirrung führen.

In diesem Tutorial lernen wir die Elemente der Methodensignatur und ihre Auswirkungen auf die Java-Programmierung kennen.

2. Methodensignatur

Methoden in Java unterstützen das Überladen. Dies bedeutet, dass mehrere Methoden mit demselben Namen in derselben Klasse oder Klassenhierarchie definiert werden können. Daher muss der Compiler in der Lage sein, die Methode, auf die sich der Clientcode bezieht, statisch zu binden. Aus diesem Grund identifiziert die Methodensignatur jede Methode eindeutig .

Laut Oracle besteht die Methodensignatur aus dem Namen und den Parametertypen . Daher sind alle anderen Elemente der Methodendeklaration, wie z. B. Modifikatoren, Rückgabetyp, Parameternamen, Ausnahmeliste und Text, nicht Teil der Signatur.

Schauen wir uns die Überladung von Methoden und ihre Beziehung zu Methodensignaturen genauer an.

3. Überladen von Fehlern

Betrachten wir den folgenden Code :

public void print() { System.out.println("Signature is: print()"); } public void print(int parameter) { System.out.println("Signature is: print(int)"); }

Wie wir sehen können, wird der Code kompiliert, da die Methoden unterschiedliche Parametertyplisten haben. Tatsächlich kann der Compiler jeden Aufruf deterministisch an den einen oder anderen binden.

Testen wir nun, ob eine Überlastung zulässig ist, indem wir die folgende Methode hinzufügen:

public int print() { System.out.println("Signature is: print()"); return 0; }

Beim Kompilieren erhalten wir den Fehler "Methode ist bereits in der Klasse definiert". Dies beweist, dass der Rückgabetyp der Methode nicht Teil der Methodensignatur ist .

Versuchen wir dasselbe mit Modifikatoren:

private final void print() { System.out.println("Signature is: print()"); }

Wir sehen immer noch den gleichen Fehler "Methode ist bereits in der Klasse definiert". Daher ist die Methodensignatur nicht von Modifikatoren abhängig .

Das Überladen durch Ändern ausgelöster Ausnahmen kann getestet werden, indem Folgendes hinzugefügt wird:

public void print() throws IllegalStateException { System.out.println("Signature is: print()"); throw new IllegalStateException(); }

Wieder sehen wir den Fehler "Methode ist bereits in der Klasse definiert", der angibt, dass die Wurfdeklaration nicht Teil der Signatur sein kann .

Als letztes können wir testen, ob das Ändern der Parameternamen eine Überladung ermöglicht. Fügen wir die folgende Methode hinzu:

public void print(int anotherParameter) { System.out.println("Signature is: print(int)"); }

Wie erwartet erhalten wir den gleichen Kompilierungsfehler. Dies bedeutet, dass Parameternamen keinen Einfluss auf die Methodensignatur haben .

3. Generika und Typ Löschung

Bei generischen Parametern ändert das Löschen des Typs die effektive Signatur . Tatsächlich kann es zu einer Kollision mit einer anderen Methode kommen, bei der anstelle des generischen Tokens die Obergrenze des generischen Typs verwendet wird.

Betrachten wir den folgenden Code:

public class OverloadingErrors { public void printElement(T t) { System.out.println("Signature is: printElement(T)"); } public void printElement(Serializable o) { System.out.println("Signature is: printElement(Serializable)"); } }

Obwohl die Signaturen unterschiedlich aussehen, kann der Compiler die richtige Methode nach dem Löschen des Typs nicht statisch binden.

Wir können sehen, dass der Compiler T aufgrund der Typlöschung durch die Obergrenze Serializable ersetzt . Daher kollidiert es mit der Methode, die Serializable explizit verwendet .

Wir würden das gleiche Ergebnis mit dem Basistyp Object sehen, wenn der generische Typ keine Bindung hat.

4. Parameterlisten und Polymorphismus

Die Methodensignatur berücksichtigt die genauen Typen. Das heißt, wir können eine Methode überladen, deren Parametertyp eine Unterklasse oder Oberklasse ist.

Wir müssen jedoch besonders darauf achten, dass die statische Bindung mithilfe von Polymorphismus, Auto-Boxing und Typ-Promotion übereinstimmt .

Schauen wir uns den folgenden Code an:

public Number sum(Integer term1, Integer term2) { System.out.println("Adding integers"); return term1 + term2; } public Number sum(Number term1, Number term2) { System.out.println("Adding numbers"); return term1.doubleValue() + term2.doubleValue(); } public Number sum(Object term1, Object term2) { System.out.println("Adding objects"); return term1.hashCode() + term2.hashCode(); }

Der obige Code ist vollkommen legal und wird kompiliert. Beim Aufrufen dieser Methoden kann es zu Verwirrung kommen, da wir nicht nur die genaue Methodensignatur kennen müssen, die wir aufrufen, sondern auch, wie Java basierend auf den tatsächlichen Werten statisch bindet.

Lassen Sie uns einige Methodenaufrufe untersuchen, die an die Summe gebunden sind (Integer, Integer) :

StaticBinding obj = new StaticBinding(); obj.sum(Integer.valueOf(2), Integer.valueOf(3)); obj.sum(2, 3); obj.sum(2, 0x1);

Für den ersten Aufruf haben wir die genauen Parametertypen Integer, Integer. Beim zweiten Aufruf wird Java int automatisch in Integer für uns boxen . Zuletzt transformiert Java den Bytewert 0x1 mittels Typheraufstufung in int und verpackt ihn dann automatisch in Integer.

In ähnlicher Weise haben wir die folgenden Aufrufe, die an die Summe (Nummer, Nummer) gebunden sind :

obj.sum(2.0d, 3.0d); obj.sum(Float.valueOf(2), Float.valueOf(3));

Beim ersten Aufruf haben wir doppelte Werte, die automatisch auf Double gesetzt werden. Und dann, durch Polymorphismus, Doppelspiele Nummer. Float stimmt identisch mit der Nummer für den zweiten Anruf überein .

Let's observe the fact that both Float and Double inherit from Number and Object. However, the default binding is to Number. This is due to the fact that Java will automatically match to the nearest super-types that match a method signature.

Now let's consider the following method call:

obj.sum(2, "John");

In this example, we have an int to Integer auto-box for the first parameter. However, there is no sum(Integer, String) overload for this method name. Consequentially, Java will run through all the parameter super-types to cast from the nearest parent to Object until it finds a match. In this case, it binds to sum(Object, Object).

To change the default binding, we can use explicit parameter casting as follows:

obj.sum((Object) 2, (Object) 3); obj.sum((Number) 2, (Number) 3);

5. Vararg Parameters

Now let's turn our attention over to how varargs impact the method's effective signature and static binding.

Here we have an overloaded method using varargs:

public Number sum(Object term1, Object term2) { System.out.println("Adding objects"); return term1.hashCode() + term2.hashCode(); } public Number sum(Object term1, Object... term2) { System.out.println("Adding variable arguments: " + term2.length); int result = term1.hashCode(); for (Object o : term2) { result += o.hashCode(); } return result; }

So what are the effective signatures of the methods? We've already seen that sum(Object, Object) is the signature for the first. Variable arguments are essentially arrays, so the effective signature for the second after compilation is sum(Object, Object[]).

A tricky question is how can we choose the method binding when we have just two parameters?

Let's consider the following calls:

obj.sum(new Object(), new Object()); obj.sum(new Object(), new Object(), new Object()); obj.sum(new Object(), new Object[]{new Object()});

Obviously, the first call will bind to sum(Object, Object) and the second to sum(Object, Object[]). To force Java to call the second method with two objects, we must wrap it in an array as in the third call.

The last thing to note here is that declaring the following method will clash with the vararg version:

public Number sum(Object term1, Object[] term2) { // ... }

6. Conclusion

In this tutorial, we learned that the method signatures are comprised of the name and the parameter types' list. The modifiers, return type, parameter names, and exception list cannot differentiate between overloaded methods and, thus, are not part of the signature.

We've also looked at how type erasure and varargs hide the effective method signature and how we can override Java's static method binding.

Wie üblich sind alle in diesem Artikel gezeigten Codebeispiele auf GitHub verfügbar.