Override trong Java là một trong những tính năng quan trọng của lập trình hướng đối tượng. Trong bài viết này, hãy cùng Rikkei Academy tìm hiểu chi tiết phương thức ghi đè từ khái niệm, cách thức hoạt động cũng như các quy tắc và lưu ý khi sử dụng overriding method.
Override trong Java là gì?
Overriding là một tính năng trong lập trình hướng đối tượng Java, cho phép một lớp con cung cấp một định nghĩa mới cho một phương thức đã được định nghĩa trong lớp cha của nó. Quá trình này giúp lớp con mở rộng hoặc thay đổi hành vi của lớp cha mà không làm thay đổi mã nguồn của lớp cha.
Cách thức hoạt động của Override trong Java
Khi một lớp con ghi đè một phương thức của lớp cha, phương thức của lớp con sẽ được gọi thay vì phương thức của lớp cha khi đối tượng lớp con được sử dụng. Điều này cho phép lớp con thay đổi hành vi của lớp cha mà không ảnh hưởng đến các lớp khác kế thừa từ lớp cha.
Điều này được gọi là đa hình thời gian chạy (runtime polymorphism) hay còn gọi là late binding. Tức là quyết định về phương thức được gọi sẽ được thực hiện tại thời điểm chạy chương trình, dựa trên kiểu đối tượng được khởi tạo, không phải dựa trên kiểu biến tham chiếu.
Lợi ích của việc sử dụng Override trong Java
Dưới đây là một số lợi ích khi sử dụng Override trong Java:
Tính đa hình (Polymorphism)
Overriding là một trong những cách thức thực hiện tính đa hình trong Java. Đa hình là khả năng của một đối tượng để thể hiện nhiều hình dạng khác nhau. Khi bạn ghi đè một phương thức, bạn cho phép các đối tượng của lớp con thể hiện các hành vi khác nhau, tùy thuộc vào cách định nghĩa phương thức trong lớp con.
Tính kế thừa (Inheritance)
Overriding giúp tận dụng tính kế thừa trong lập trình hướng đối tượng Java. Khi một lớp con kế thừa từ một lớp cha, nó có thể ghi đè các phương thức của lớp cha để thay đổi hoặc mở rộng chức năng của lớp cha. Điều này giúp đơn giản hóa mã nguồn và giảm thiểu việc lặp lại mã.
Tính đóng gói (Encapsulation)
Overriding là một cách thức để giữ cho các phương thức được bảo vệ trong lớp cha và không bị truy cập từ bên ngoài. Các phương thức này chỉ được truy cập thông qua các phương thức công khai (public) trong lớp cha. Việc này giúp giữ cho mã nguồn an toàn và dễ bảo trì.
Dễ dàng mở rộng và bảo trì mã nguồn
Overriding cho phép các lớp con mở rộng chức năng của lớp cha bằng cách định nghĩa lại các phương thức của lớp cha. Khi cần thay đổi cách thức hoạt động của phương thức, ta chỉ cần định nghĩa lại phương thức trong lớp con mà không cần sửa đổi mã nguồn của lớp cha. Điều này giúp giảm thiểu rủi ro khi sửa đổi mã nguồn.
Quy tắc sử dụng Override trong Java
Để thực hiện overriding một cách hợp lệ trong Java, chúng ta cần tuân theo một số quy tắc nhất định. Sau đây là các quy tắc cần tuân thủ khi sử dụng overriding:
Tên phương thức
Khi ghi đè một phương thức, lớp con phải đảm bảo rằng phương thức ghi đè có cùng tên và cùng tham số với phương thức trong lớp cha.
Signature
Phương thức trong lớp con phải có cùng signature (kiểu và số lượng tham số) với phương thức trong lớp cha mà nó ghi đè.
Kiểu trả về
Kiểu trả về của phương thức trong lớp con phải là kiểu con của kiểu trả về của phương thức trong lớp cha. Nếu phương thức gốc trả về một kiểu cơ bản (primitive type), phương thức ghi đè cũng phải trả về kiểu cơ bản tương ứng. Nếu phương thức gốc trả về một kiểu đối tượng (object type), phương thức ghi đè có thể trả về kiểu đối tượng tương ứng hoặc một kiểu con của kiểu đối tượng đó.
Phạm vi truy cập
Trong Java, phạm vi truy cập (access modifier) được sử dụng để quản lý quyền truy cập đến các phương thức và thuộc tính trong một lớp.
- Phương thức ghi đè không thể có phạm vi truy cập hẹp hơn phương thức gốc: Khi ghi đè một phương thức, phương thức ghi đè không được có phạm vi truy cập hẹp hơn (more restrictive) so với phương thức của lớp cha. Ví dụ, nếu phương thức gốc có phạm vi truy cập là protected, phương thức ghi đè không thể có phạm vi truy cập là private.
- Phương thức ghi đè có thể có phạm vi truy cập rộng hơn phương thức gốc: Tuy nhiên, phương thức ghi đè có thể có phạm vi truy cập rộng hơn (less restrictive) so với phương thức của lớp cha. Ví dụ, nếu phương thức gốc có phạm vi truy cập là protected, phương thức ghi đè có thể có phạm vi truy cập là public.
Exception
Phương thức trong lớp con có thể ném ra các ngoại lệ (exception) khác so với phương thức trong lớp cha. Tuy nhiên, các ngoại lệ được ném ra trong phương thức trong lớp con phải là các ngoại lệ con của các ngoại lệ được khai báo trong phương thức gốc hoặc là các ngoại lệ không khai báo trong phương thức gốc.
Tìm hiểu thêm về Overloading trong Java
Lưu ý khi sử dụng Override trong Java
Khi sử dụng Override trong Java có bạn cần lưu ý một số vấn đề sau:
Phương thức static không thể ghi đè
Phương thức static được liên kết với lớp, không phải với đối tượng, nên nó không thể ghi đè được. Nếu trong lớp con ta định nghĩa một phương thức static trùng tên với phương thức static của lớp cha, thì chúng được coi là ẩn (hiding) nhau, không phải ghi đè.
Khi một phương thức static có cùng signature với một phương thức static trong lớp cơ sở, thì được gọi là method hiding.
Xem bảng để hiểu rõ hơn về cách thức hoạt động giữa lớp con và lớp cha
Lớp Cha Phương Thức Thể hiện | Lớp Cha Phương Thức Static | |
Lớp Con Phương Thức Thể hiện | Ghi đè | Lỗi biên dịch |
Lớp Con Phương Thức Static | Lỗi biên dịch | Hiding |
Phương thức final không thể ghi đè
Nếu một phương thức được khai báo là final, nó không thể được ghi đè trong lớp con. Tính chất final ngăn chặn việc thay đổi hành vi của phương thức trong các lớp con. Nếu ta cố gắng ghi đè một phương thức final thì chương trình sẽ báo lỗi biên dịch.
Phương thức private không thể ghi đè
Phương thức private chỉ có thể được truy cập từ bên trong lớp đó, nên không có ý nghĩa khi ghi đè phương thức private. Nếu ta cố gắng ghi đè một phương thức private thì chương trình sẽ báo lỗi biên dịch.
Overriding và xử lý ngoại lệ (Exception-Handling)
Khi ghi đè một phương thức, chúng ta cũng cần lưu ý đến việc xử lý ngoại lệ. Cụ thể, phương thức ghi đè không được phép ném ra ngoại lệ mới (checked exception) mà phương thức gốc không ném ra. Tuy nhiên, nếu phương thức gốc ném ra một hoặc nhiều ngoại lệ, phương thức ghi đè có thể không ném ra ngoại lệ nào hoặc chỉ ném ra một số ngoại lệ giống như phương thức gốc.
import java.io.IOException; class Animal { public void makeSound() throws IOException { System.out.println(“The animal makes a sound”); } } class Dog extends Animal { public void makeSound() { System.out.println(“The dog barks”); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); try { myDog.makeSound(); } catch (IOException e) { e.printStackTrace(); } // Output: The dog barks } } |
Phương thức makeSound() của lớp Animal ném ra ngoại lệ IOException. Tuy nhiên, phương thức makeSound() của lớp Dog không ném ra ngoại lệ nào. Việc ghi đè hợp lệ và kết quả in ra màn hình là “The dog barks”.
Overriding và phương thức trừu tượng (Abstract Method)
Trong Java, một phương thức trừu tượng là phương thức được khai báo với từ khóa abstract và không có phần thân (method body). Lớp chứa phương thức trừu tượng cũng phải được khai báo là trừu tượng (abstract class). Khi một lớp kế thừa từ lớp trừu tượng, nó phải cung cấp định nghĩa cho tất cả các phương thức trừu tượng của lớp cha. Việc này gọi là ghi đè phương thức trừu tượng.
abstract class Animal {
public abstract void makeSound(); } class Dog extends Animal { public void makeSound() { System.out.println(“The dog barks”); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); myDog.makeSound(); // Output: The dog barks } } |
Lớp Animal là một lớp trừu tượng chứa phương thức trừu tượng makeSound(). Lớp Dog kế thừa từ lớp Animal và cung cấp định nghĩa cho phương thức makeSound(). Kết quả, khi gọi phương thức makeSound() trên đối tượng myDog, kết quả in ra màn hình là “The dog barks”.
Overriding và phương thức đồng bộ hóa (Synchronized) / strictfp
Phương thức đồng bộ hóa (synchronized) và phương thức strictfp là hai tính chất đặc biệt của phương thức trong Java. Khi ghi đè một phương thức, chúng ta cần lưu ý đến các quy tắc sau:
- Nếu phương thức gốc được đánh dấu là synchronized trong Java hay không thì phương thức ghi đè cũng có thể được đánh dấu là synchronized hoặc không.
- Nếu phương thức gốc được đánh dấu là strictfp, phương thức ghi đè phải được đánh dấu là strictfp. Nếu phương thức gốc không được đánh dấu là strictfp, phương thức ghi đè có thể được đánh dấu là strictfp hoặc không.
Ví dụ về overriding phương thức đồng bộ hóa và strictfp
class Animal {
public synchronized void makeSound() { System.out.println(“The animal makes a sound”); } public strictfp double calculateArea(double side) { return side * side; } } class Dog extends Animal { public synchronized void makeSound() { System.out.println(“The dog barks”); } public strictfp double calculateArea(double side) { return side * side * 0.5; } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); myDog.makeSound(); // Output: The dog barks System.out.println(myDog.calculateArea(5)); // Output: 12.5 } } |
Lớp Animal có hai phương thức: makeSound() được đánh dấu là synchronized và calculateArea() được đánh dấu là strictfp. Lớp Dog kế thừa từ lớp Animal và ghi đè cả hai phương thức này, cũng đánh dấu chúng là synchronized và strictfp. Kết quả, khi gọi phương thức makeSound() và calculateArea() trên đối tượng myDog, kết quả in ra màn hình là “The dog barks” và “12.5”.
Ví dụ về Override trong Java
Để hiểu hơn cách hoạt động của overriding, chúng ta cùng xem một số ví dụ sau:
Ví dụ cơ bản về overriding
class Animal {
public void makeSound() { System.out.println(“The animal makes a sound”); } } class Dog extends Animal { public void makeSound() { System.out.println(“The dog barks”); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); myDog.makeSound(); // Output: The dog barks } } |
Ví dụ Overriding với từ khóa super
Từ khóa “super” được sử dụng để truy cập đến phương thức hoặc thuộc tính của lớp cha trong lớp con. Khi ghi đè một phương thức của lớp cha bằng phương thức trong lớp con, ta có thể sử dụng từ khóa “super” để truy cập đến phương thức gốc và thực hiện một số thao tác bổ sung.
class Animal {
public void makeSound() { System.out.println(“The animal makes a sound”); } } class Dog extends Animal { public void makeSound() { super.makeSound(); // Call the superclass’ method System.out.println(“The dog barks”); } } public class Main { public static void main(String[] args) { Dog myDog = new Dog(); myDog.makeSound(); // Output: // The animal makes a sound // The dog barks } } |
Ví dụ về overriding và phạm vi truy cập
class Animal {
protected void makeSound() { System.out.println(“The animal makes a sound”); } } class Dog extends Animal { public void makeSound() { // Rộng hơn so với phương thức gốc System.out.println(“The dog barks”); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); myDog.makeSound(); // Output: The dog barks } } |
Kết luận
Như vậy, trong bài viết này, chúng ta đã tìm hiểu về cơ chế Override trong Java. Hy vọng rằng bài viết này đã giúp các bạn hiểu rõ hơn về cơ chế Override trong Java và áp dụng nó một cách hiệu quả trong việc phát triển các ứng dụng lớn và phức tạp.
Nếu bạn đang muốn tìm hiểu khóa học lập trình Java, tham khảo ngay Rikkei Academy! Với lộ trình tinh gọn, bám sát thực tế công việc và phương pháp đào tạo tiên tiến giúp bạn nhanh chóng trở thành lập trình viên chỉ trong 6 tháng! Đăng ký để nhận tư vấn miễn phí ngay hôm nay!