Adakah Tanda Tangan Kaedah Termasuk Jenis Pengembalian di Java?

1. Gambaran keseluruhan

Tanda tangan kaedah hanyalah subset dari keseluruhan definisi kaedah di Java. Oleh itu, anatomi tandatangan yang tepat boleh menyebabkan kekeliruan.

Dalam tutorial ini, kita akan mempelajari unsur-unsur tanda tangan kaedah dan implikasinya dalam pengaturcaraan Java.

2. Tandatangan Kaedah

Kaedah di Java menyokong kelebihan beban, yang bermaksud bahawa banyak kaedah dengan nama yang sama dapat ditentukan dalam kelas atau hierarki kelas yang sama. Oleh itu, penyusun mesti dapat mengikat secara statik kaedah yang dirujuk oleh kod pelanggan. Atas sebab ini, tandatangan kaedah mengenal pasti setiap kaedah secara unik .

Menurut Oracle, tandatangan kaedah terdiri daripada jenis nama dan parameter . Oleh itu, semua elemen lain dari deklarasi kaedah, seperti pengubah, jenis pengembalian, nama parameter, senarai pengecualian, dan isi badan bukan merupakan sebahagian daripada tandatangan.

Mari kita perhatikan lebih dekat kaedah overloading dan bagaimana ia berkaitan dengan tandatangan kaedah.

3. Kesalahan Beban Berlebihan

Mari pertimbangkan kod berikut :

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

Seperti yang kita lihat, kod itu disusun kerana kaedahnya mempunyai senarai jenis parameter yang berbeza. Akibatnya, penyusun dapat secara pasti menentukan sebarang panggilan ke satu atau yang lain.

Sekarang mari kita menguji sama ada undang-undang berlebihan dengan menambahkan kaedah berikut:

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

Semasa kami menyusun, kami mendapat ralat "kaedah sudah ditentukan dalam kelas". Itu membuktikan kaedah pengembalian kaedah bukan sebahagian daripada tandatangan kaedah .

Mari cuba sama dengan pengubah:

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

Kami masih melihat ralat yang sama "kaedah sudah ditentukan dalam kelas". Oleh itu, tandatangan kaedah tidak bergantung pada pengubah .

Beban berlebihan dengan mengubah pengecualian dilemparkan dapat diuji dengan menambahkan:

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

Sekali lagi kita melihat kesalahan "kaedah sudah ditentukan dalam kelas", menunjukkan deklarasi lemparan tidak boleh menjadi sebahagian daripada tandatangan .

Perkara terakhir yang dapat kita uji ialah sama ada menukar nama parameter membolehkan beban berlebihan. Mari tambahkan kaedah berikut:

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

Seperti yang dijangkakan, kami mendapat ralat kompilasi yang sama. Ini bermaksud bahawa nama parameter tidak mempengaruhi tanda tangan kaedah .

3. Penghapusan Generik dan Jenis

Dengan parameter generik , penghapusan jenis mengubah tandatangan berkesan . Akibatnya, ia boleh menyebabkan perlanggaran dengan kaedah lain yang menggunakan batas atas jenis generik dan bukannya token generik.

Mari pertimbangkan kod berikut:

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

Walaupun tandatangan kelihatan berbeza, penyusun tidak dapat secara statik mengikat kaedah yang betul setelah penghapusan jenis.

Kita dapat melihat penyusun menggantikan T dengan batas atas, Serializable, kerana penghapusan jenis. Oleh itu, ia bertentangan dengan kaedah secara eksplisit menggunakan Serializable .

Kita akan melihat hasil yang sama dengan Objek jenis asas apabila jenis generik tidak mempunyai batas.

4. Senarai Parameter dan Polimorfisme

Tandatangan kaedah mengambil kira jenis yang tepat. Ini bermaksud kita dapat membebani kaedah yang jenis parameternya adalah subkelas atau superclass.

Walau bagaimanapun, kita mesti memberi perhatian khusus kerana pengikatan statik akan berupaya menyamai menggunakan polimorfisme, auto-tinju, dan promosi jenis .

Mari lihat kod berikut:

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

Kod di atas adalah sah dan akan disusun. Kekeliruan mungkin timbul ketika memanggil kaedah ini kerana kita tidak hanya perlu mengetahui tanda tangan kaedah yang tepat yang kita panggil tetapi juga bagaimana Java secara statik mengikat berdasarkan nilai sebenarnya.

Mari kita terokai beberapa panggilan kaedah yang berakhir dengan jumlah (Integer, Integer) :

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

Untuk panggilan pertama, kami mempunyai jenis parameter tepat Integer, Integer. Pada panggilan kedua, Java akan auto-kotak int untuk Integer untuk kita . Terakhir, Java akan mengubah nilai byte 0x1 menjadi int melalui promosi jenis dan kemudian auto-box ke Integer.

Begitu juga, kami mempunyai panggilan berikut yang mengikat jumlah (Nombor, Nombor) :

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

Pada panggilan pertama, kami mempunyai nilai ganda yang dikotak secara automatik menjadi Double. Dan kemudian, dengan cara polimorfisme, Ganda sepadan dengan Nombor. Secara serupa, Float sepadan dengan Nombor untuk panggilan kedua.

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.

Seperti biasa, semua contoh kod yang ditunjukkan dalam artikel ini terdapat di GitHub.