La firma di un metodo include il tipo restituito in Java?

1. Panoramica

La firma del metodo è solo un sottoinsieme dell'intera definizione del metodo in Java. Pertanto, l'esatta anatomia della firma può causare confusione.

In questo tutorial impareremo gli elementi della firma del metodo e le sue implicazioni nella programmazione Java.

2. Firma del metodo

I metodi in Java supportano il sovraccarico, il che significa che più metodi con lo stesso nome possono essere definiti nella stessa classe o gerarchia di classi. Pertanto, il compilatore deve essere in grado di associare staticamente il metodo a cui fa riferimento il codice client. Per questo motivo, la firma del metodo identifica in modo univoco ciascun metodo .

Secondo Oracle, la firma del metodo è composta dal nome e dai tipi di parametro . Pertanto, tutti gli altri elementi della dichiarazione del metodo, come i modificatori, il tipo restituito, i nomi dei parametri, l'elenco delle eccezioni e il corpo non fanno parte della firma.

Diamo uno sguardo più da vicino al sovraccarico del metodo e al modo in cui si riferisce alle firme del metodo.

3. Errori di sovraccarico

Consideriamo il seguente codice :

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

Come possiamo vedere, il codice viene compilato poiché i metodi hanno elenchi di tipi di parametri diversi. In effetti, il compilatore può associare in modo deterministico qualsiasi chiamata all'una o all'altra.

Ora proviamo se è legale sovraccaricare aggiungendo il seguente metodo:

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

Quando compiliamo, otteniamo un errore "il metodo è già definito nella classe". Ciò dimostra che il tipo restituito dal metodo non fa parte della firma del metodo .

Proviamo lo stesso con i modificatori:

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

Vediamo ancora lo stesso errore "il metodo è già definito nella classe". Pertanto, la firma del metodo non dipende dai modificatori .

Il sovraccarico modificando le eccezioni generate può essere testato aggiungendo:

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

Di nuovo vediamo l'errore "il metodo è già definito nella classe", che indica che la dichiarazione di lancio non può essere parte della firma .

L'ultima cosa che possiamo verificare è se la modifica dei nomi dei parametri consente il sovraccarico. Aggiungiamo il seguente metodo:

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

Come previsto, otteniamo lo stesso errore di compilazione. Ciò significa che i nomi dei parametri non influenzano la firma del metodo .

3. Generici e cancellazione del tipo

Con parametri generici , la cancellazione del tipo cambia la firma effettiva . In effetti, potrebbe causare una collisione con un altro metodo che utilizza il limite superiore del tipo generico invece del token generico.

Consideriamo il seguente codice:

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)"); } }

Anche se le firme appaiono diverse, il compilatore non può associare staticamente il metodo corretto dopo la cancellazione del tipo.

Possiamo vedere il compilatore che sostituisce T con il limite superiore, Serializable, a causa della cancellazione del tipo. Pertanto, si scontra con il metodo che utilizza esplicitamente Serializable .

Vedremmo lo stesso risultato con il tipo di base Object quando il tipo generico non ha limiti.

4. Elenchi di parametri e polimorfismo

La firma del metodo tiene conto dei tipi esatti. Ciò significa che possiamo sovraccaricare un metodo il cui tipo di parametro è una sottoclasse o una superclasse.

Tuttavia, dobbiamo prestare particolare attenzione poiché l' associazione statica tenterà di corrispondere utilizzando il polimorfismo, l'auto-boxing e la promozione del tipo .

Diamo un'occhiata al codice seguente:

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(); }

Il codice sopra è perfettamente legale e verrà compilato. Può sorgere confusione quando si chiamano questi metodi poiché non solo abbiamo bisogno di conoscere l'esatta firma del metodo che stiamo chiamando, ma anche come Java si lega staticamente in base ai valori effettivi.

Esploriamo alcune chiamate di metodo che finiscono per essere limitate alla somma (Integer, Integer) :

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

Per la prima chiamata, abbiamo i tipi di parametri esatti Integer, Integer. Sulla seconda chiamata, Java si auto-box int per intero per noi . Infine, Java trasformerà il valore del byte 0x1 in int mediante la promozione del tipo e quindi lo inserirà automaticamente in Integer.

Allo stesso modo, abbiamo le seguenti chiamate che si legano a sum (Number, Number) :

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

Alla prima chiamata, abbiamo valori double che vengono impostati automaticamente su Double. E poi, per polimorfismo, Double corrisponde al Numero. Allo stesso modo, Float corrisponde a Number per la seconda chiamata.

Osserviamo il fatto che sia Float che Double ereditano da Number e Object. Tuttavia, l'associazione predefinita è Number . Ciò è dovuto al fatto che Java corrisponderà automaticamente ai super-tipi più vicini che corrispondono a una firma del metodo.

Consideriamo ora la seguente chiamata al metodo:

obj.sum(2, "John");

In questo esempio, abbiamo una casella automatica da int a Integer per il primo parametro. Tuttavia, non esiste alcun sovraccarico della somma (Integer, String) per questo nome di metodo. Di conseguenza, Java eseguirà tutti i super-tipi di parametri per eseguire il cast dal genitore più vicino a Object finché non trova una corrispondenza. In questo caso, si lega a 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.

Come al solito, tutti gli esempi di codice mostrati in questo articolo sono disponibili su GitHub.