Kaedah Overloading dan Overriding di Java

1. Gambaran keseluruhan

Metode overloading dan overriding adalah konsep utama dari bahasa pengaturcaraan Java, dan dengan demikian, mereka pantas melihat secara mendalam.

Dalam artikel ini, kita akan mempelajari asas-asas konsep ini dan melihat dalam situasi apa yang berguna.

2. Kaedah Overloading

Overloading kaedah adalah mekanisme yang kuat yang membolehkan kita menentukan API kelas yang padu. Untuk lebih memahami mengapa kaedah overloading adalah ciri yang sangat berharga, mari kita lihat contoh ringkas.

Anggaplah kita telah menulis kelas utiliti naif yang menerapkan kaedah yang berlainan untuk mengalikan dua nombor, tiga nombor dan seterusnya.

Sekiranya kita telah memberikan kaedah nama yang mengelirukan atau samar-samar, seperti multiply2 () , multiply3 () , multiply4 (), maka itu akan menjadi API kelas yang tidak dirancang dengan baik. Di sinilah kaedah overloading dimainkan.

Sederhananya, kita dapat menerapkan kaedah overloading dengan dua cara yang berbeza:

  • melaksanakan dua atau lebih kaedah yang mempunyai nama yang sama tetapi mengambil pelbagai argumen
  • melaksanakan dua atau lebih kaedah yang mempunyai nama yang sama tetapi mengambil hujah dari pelbagai jenis

2.1. Bilangan Hujah yang berbeza

The Multiplier menunjukkan kelas, secara ringkas, bagaimana untuk beban yang berganda () kaedah dengan hanya menentukan dua perlaksanaan yang mengambil nombor yang berlainan hujah:

public class Multiplier { public int multiply(int a, int b) { return a * b; } public int multiply(int a, int b, int c) { return a * b * c; } }

2.2. Hujah Berbagai Jenis

Begitu juga, kita dapat membebani kaedah multiply () dengan membuatnya menerima argumen dari pelbagai jenis:

public class Multiplier { public int multiply(int a, int b) { return a * b; } public double multiply(double a, double b) { return a * b; } } 

Selain itu, adalah sah untuk menentukan kelas Multiplier dengan kedua-dua jenis kaedah overloading:

public class Multiplier { public int multiply(int a, int b) { return a * b; } public int multiply(int a, int b, int c) { return a * b * c; } public double multiply(double a, double b) { return a * b; } } 

Namun, perlu diperhatikan bahawa tidak mungkin ada dua pelaksanaan kaedah yang hanya berbeza dalam jenis pengembaliannya .

Untuk memahami sebabnya - mari pertimbangkan contoh berikut:

public int multiply(int a, int b) { return a * b; } public double multiply(int a, int b) { return a * b; }

Dalam kes ini, kod itu tidak akan dikompilasi kerana kaedah ambiguiti panggilan - penyusun tidak akan mengetahui pelaksanaan multiply () untuk memanggil.

2.3. Jenis Promosi

Satu ciri kemas yang disediakan oleh kaedah overloading adalah apa yang disebut promosi jenis, yang juga memperluas penukaran primitif .

Secara sederhana, satu jenis yang diberikan secara tersirat dipromosikan ke yang lain apabila tidak ada padanan antara jenis argumen yang diteruskan ke kaedah yang terlalu banyak dengan pelaksanaan metode tertentu.

Untuk memahami dengan lebih jelas bagaimana promosi jenis berfungsi, pertimbangkan pelaksanaan kaedah multiply () berikut :

public double multiply(int a, long b) { return a * b; } public int multiply(int a, int b, int c) { return a * b * c; } 

Sekarang, memanggil kaedah dengan dua argumen int akan menghasilkan argumen kedua dipromosikan menjadi panjang , kerana dalam hal ini tidak ada implementasi metode yang sesuai dengan dua argumen int .

Mari lihat ujian unit cepat untuk menunjukkan promosi jenis:

@Test public void whenCalledMultiplyAndNoMatching_thenTypePromotion() { assertThat(multiplier.multiply(10, 10)).isEqualTo(100.0); }

Sebaliknya, jika kita memanggil metode dengan implementasi yang sesuai, promosi jenis tidak akan berlaku:

@Test public void whenCalledMultiplyAndMatching_thenNoTypePromotion() { assertThat(multiplier.multiply(10, 10, 10)).isEqualTo(1000); }

Berikut adalah ringkasan peraturan promosi jenis yang berlaku untuk kaedah overloading:

  • bait boleh dipromosikan menjadi pendek, int, panjang, terapung, atau berganda
  • pendek boleh dipromosikan menjadi int, long, float, atau double
  • char boleh dipromosikan menjadi int, long, float, atau double
  • int boleh dipromosikan menjadi panjang, terapung, atau berganda
  • panjang boleh dipromosikan untuk float atau double
  • apungan boleh dipromosikan menjadi dua kali ganda

2.4. Pengikat Statik

Keupayaan untuk mengaitkan panggilan kaedah tertentu ke badan kaedah dikenali sebagai mengikat.

Sekiranya kaedah overloading, pengikatan dilakukan secara statik pada waktu kompilasi, oleh itu disebut pengikatan statik.

Penyusun dapat mengatur pengikatan dengan berkesan pada waktu kompilasi dengan hanya memeriksa tandatangan kaedah.

3. Kaedah Mengatasi

Kaedah penggantian membolehkan kami menyediakan pelaksanaan yang terperinci dalam subkelas untuk kaedah yang ditentukan dalam kelas asas.

Walaupun penggantian kaedah adalah ciri yang kuat - mengingat itu adalah konsekuensi logik menggunakan warisan, salah satu tonggak terbesar OOP - kapan dan di mana menggunakannya harus dianalisis dengan teliti, berdasarkan setiap kasus .

Mari kita lihat sekarang bagaimana menggunakan kaedah penggantian dengan membuat hubungan sederhana (berdasarkan "adalah" warisan).

Inilah kelas asas:

public class Vehicle { public String accelerate(long mph) { return "The vehicle accelerates at : " + mph + " MPH."; } public String stop() { return "The vehicle has stopped."; } public String run() { return "The vehicle is running."; } }

Dan inilah subkelas yang dibuat:

public class Car extends Vehicle { @Override public String accelerate(long mph) { return "The car accelerates at : " + mph + " MPH."; } }

Dalam hierarki di atas, kami hanya mengesampingkan kaedah accelerate () untuk memberikan pelaksanaan yang lebih halus untuk subjenis Kereta.

Di sini, jelas untuk melihat bahawa jika aplikasi menggunakan contoh kelas Kenderaan , maka aplikasi itu dapat berfungsi dengan contoh Car juga , kerana kedua-dua pelaksanaan kaedah accelerate () mempunyai tanda tangan yang sama dan jenis pengembalian yang sama.

Mari tulis beberapa ujian unit untuk memeriksa kelas Kenderaan dan Kereta :

@Test public void whenCalledAccelerate_thenOneAssertion() { assertThat(vehicle.accelerate(100)) .isEqualTo("The vehicle accelerates at : 100 MPH."); } @Test public void whenCalledRun_thenOneAssertion() { assertThat(vehicle.run()) .isEqualTo("The vehicle is running."); } @Test public void whenCalledStop_thenOneAssertion() { assertThat(vehicle.stop()) .isEqualTo("The vehicle has stopped."); } @Test public void whenCalledAccelerate_thenOneAssertion() { assertThat(car.accelerate(80)) .isEqualTo("The car accelerates at : 80 MPH."); } @Test public void whenCalledRun_thenOneAssertion() { assertThat(car.run()) .isEqualTo("The vehicle is running."); } @Test public void whenCalledStop_thenOneAssertion() { assertThat(car.stop()) .isEqualTo("The vehicle has stopped."); } 

Sekarang, mari kita lihat beberapa ujian unit yang menunjukkan bagaimana kaedah run () dan stop () , yang tidak diganti, mengembalikan nilai yang sama untuk Kereta dan Kenderaan :

@Test public void givenVehicleCarInstances_whenCalledRun_thenEqual() { assertThat(vehicle.run()).isEqualTo(car.run()); } @Test public void givenVehicleCarInstances_whenCalledStop_thenEqual() { assertThat(vehicle.stop()).isEqualTo(car.stop()); }

Dalam kes kami, kami mempunyai akses ke kod sumber untuk kedua kelas, jadi kami dapat dengan jelas melihat bahawa memanggil kaedah mempercepat () pada contoh Kendaraan dasar dan memanggil mempercepat () pada contoh Car akan mengembalikan nilai yang berbeda untuk argumen yang sama.

Oleh itu, ujian berikut menunjukkan bahawa kaedah diganti digunakan untuk contoh Car :

@Test public void whenCalledAccelerateWithSameArgument_thenNotEqual() { assertThat(vehicle.accelerate(100)) .isNotEqualTo(car.accelerate(100)); }

3.1. Ketik Substitutabiliti

Prinsip teras dalam OOP adalah prinsip penggantian jenis, yang berkait rapat dengan Prinsip Penggantian Liskov (LSP).

Ringkasnya, LSP menyatakan bahawa jika aplikasi berfungsi dengan jenis dasar yang ditentukan, maka aplikasi itu juga harus berfungsi dengan subtipenya . Dengan cara itu, penggantian jenis dijaga dengan betul.

Masalah terbesar dengan kaedah penggantian adalah bahawa beberapa implementasi kaedah khusus dalam kelas yang diturunkan mungkin tidak sepenuhnya mematuhi LSP dan oleh itu gagal mengekalkan penggantian jenis.

Sudah tentu, adalah sah untuk membuat kaedah yang diganti untuk menerima argumen dari pelbagai jenis dan mengembalikan jenis yang berbeza juga, tetapi dengan mematuhi peraturan ini:

  • Sekiranya kaedah di kelas asas mengambil argumen dari jenis tertentu, kaedah yang diganti harus menggunakan jenis yang sama atau supertype (aka kaedah kontravariant argumen)
  • Sekiranya kaedah dalam kelas asas mengembalikan kekosongan , kaedah yang diganti harus mengembalikan kekosongan
  • Sekiranya kaedah dalam kelas asas mengembalikan primitif, kaedah yang diganti harus mengembalikan primitif yang sama
  • Sekiranya kaedah dalam kelas asas mengembalikan jenis tertentu, kaedah yang diganti harus mengembalikan jenis atau subtipe yang sama (aka jenis kovarian pengembalian)
  • Sekiranya kaedah dalam kelas asas membuang pengecualian, kaedah yang diganti mesti membuang pengecualian yang sama atau subjenis pengecualian kelas asas

3.2. Pengikat Dinamik

Memandangkan kaedah penggantian hanya dapat dilaksanakan dengan pewarisan, di mana terdapat hierarki jenis dan subtipe dasar, penyusun tidak dapat menentukan pada waktu kompilasi metode apa yang harus dipanggil, kerana kelas dasar dan subkelas menentukan kaedah yang sama.

Akibatnya, penyusun perlu memeriksa jenis objek untuk mengetahui kaedah apa yang harus digunakan.

Oleh kerana pemeriksaan ini berlaku pada waktu runtime, penggantian kaedah adalah contoh khas pengikatan dinamik.

4. Kesimpulan

Dalam tutorial ini, kami belajar bagaimana menerapkan kaedah overloading dan kaedah overriding, dan kami meneroka beberapa situasi khas di mana ia berguna.

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