Ghi đè phương thức
Trong một lớp con có chứa các phương thức có cùng tên, cùng kiểu dữ liệu trả về và danh sách các tham số với một phương thức nào đó trong lớp cha thì lớp con đó đã ghi đè (override) phương thức của lớp cha.
Ghi đè phương thức là một cách thay đổi cách hoạt động của một phương thức trong lớp cha. Phương thức ghi đè có thể trả về kiểu dữ liệu được kế thừa từ kiểu dữ liệu trả về của phương thức trong lớp cha, kiểu dữ liệu được trả về kiểu này còn được gọi là kiểu hiệp biến (covariant).
Khi ghi đè một phương thức thì chúng ta dùng annotation @Override
trước phương thức đó để thông báo cho trình biên dịch biết là chúng ta đang ghi đè phương thức. Nếu trình biên dịch không tìm ra phương thức bị ghi đè trong lớp cha thì sẽ báo lỗi.
Che giấu phương thức
Chúng ta đã biết là phương thức tĩnh (static) là các phương thức dùng chung trong các lớp từ lớp cha tới lớp con. Tuy nhiên khi chúng ta khai báo lại một phương thức tĩnh trùng tên, kiểu dữ liệu trả về và danh sách tham số trong lớp con thì phương thức trong lớp con sẽ che giấu (hide) phương thức đó trong lớp cha.
Sự khác nhau
Sự khác nhau giữa ghi đè phương thức bình thường và che giấu phương thức tĩnh là:
- Phương thức ghi đè sẽ chỉ có thể được gọi từ lớp con.
- Phương thức che giấu hay bị che giấu sẽ được gọi tùy vào việc nó được gọi từ lớp cha hay lớp con.
Chẳng hạn chúng ta có đoạn code định nghĩa lớp Animal
như sau:
public class Animal { public static void staticEx() { System.out.println("This is a static method"); } public void ex() { System.out.println("This is a normal method"); } }
Lớp này chứa một phương thức bình thường là ex()
và một phương thức tĩnh là staticEx()
.
Tiếp theo chúng ta định nghĩa lớp Cat
kế thừa lại như sau:
public class Cat extends Animal { public static void staticEx() { System.out.println("This is Cat class's static method"); } public void ex() { System.out.println("This is Cat class's normal method"); } public static void main(String[] args) { Cat cat = new Cat(); Animal animal = cat; Animal.staticEx(); animal.ex(); } }
Lớp Cat
sẽ ghi đè phương thức ex()
và che giấu phương thức staticEx()
của lớp Animal
. Trong hàm main()
chúng ta tạo 2 đối tượng Animal
và Cat,
đối tượng animal
tham chiếu thẳng tới đối tượng cat
.
This is a static method This is Cat class's normal method
Chúng ta gọi phương tức staticEx()
của lớp Animal
.Sau đó gọi phương thức ex()
từ đối tượng animal
, do animal
là đối tượng tham chiếu từ lớp Cat
nên phương thức ex()
được gọi sẽ là phương thức trong lớp Cat
.
Phương thức Interface
Trong interface thì các phương thức mặc định và phương thức trừu tượng được thừa kế giống như phương thức bình thường. Tuy nhiên nếu ở các lớp cha của một lớp hay interface định nghĩa nhiều phương thức mặc định có cùng tên thì trình biên dịch sẽ dựa vào một số quy tắc để giải quyết các xung đột này. Các quy tắc đó như sau:
1. Phương thức bình thường sẽ được ưu tiên sử dụng so với phương thức mặc định. Ví dụ:
public class Horse { public String print() { return "I am a horse."; } }
public interface Flyer { default public String print() { return "I am a fly."; } }
public interface Mythical { default public String print() { return "I am a mythical creature."; } }
public class Pegasus extends Horse implements Flyer, Mythical { public static void main(String[] args) { Pegasus pega = new Pegasus(); System.out.println(pega.print()); } }
Đối tượng pega
sẽ gọi phương thức print()
từ lớp Horse
thay vì từ 2 interface.
I am a horse.
2. Các phương thức đã bị ghi đè sẽ bị bỏ qua.
Trường hợp này áp dụng với các kiểu dữ liệu có chung kiểu dữ liệu cha. Ví dụ:
public interface Animal { default public String print() { return "I am an animal."; } }
public interface Chicken extends Animal { default public String print() { return "I am a chicken."; } }
public interface Dog extends Animal { }
public class Dragon implements Chicken, Dog { public static void main (String[] args) { Dragon dragon = new Dragon(); System.out.println(dragon.print()); } }
Đối tượng dragon
sẽ gọi phương thức print()
từ interface Chicken
.
Nếu có nhiều phương thức mặc định trùng tên với nhau hoặc trùng tên với phương thức trừu tượng thì trình biên dịch sẽ báo lỗi. Cách duy nhất để mất lỗi là ghi đè trong các lớp/interface con.
Giả sử chúng ta có 2 interface như sau:
public interface OperateCar { default public int startEngine(int key) { System.out.println("Starting OperateCar..."); } }
public interface FlyCar { default public int startEngine(int key) { System.out.println("Starting FlyCar..."); } }
Một lớp có implement cả 2 interface trên đều phải ghi đè phương thức startEngine()
. Bên trong phương thức ghi đè chúng ta có thể gọi lại phương thức của lớp cha ở bất cứ interface nào với từ khóa super
.
public class FlyingCar implements OperateCar, FlyCar { public int startEngine(int key) { FlyCar.super.startEngine(key); OperateCar.super.startEngine(key); } }
Từ khóa super
tham chiếu trực tiếp đến interface cha (của từng interface OperateCar
hay FlyCar
).
Khi một lớp kế thừa lại một lớp khác có chứa phương thức trùng tên với một phương thức trong interface thì phương thức đó có thể ghi đè phương thức trong interface, do đó chúng ta không cần phải ghi đè lại khi implement interface đó. Ví dụ:
public interface Mammal { String print(); }
public class Horse { public String print() { return "I am a horse."; } }
public class Mustang extends Horse implements Mammal { public static void main(String[] args) { Mustang mustang = new Mustang(); System.out.println(mustang.print()); } }
Trong đoạn code trên, nếu chúng ta không kế thừa lớp Horse
thì chúng ta phải override lại phương thức print()
từ interface Mammal
trong lớp Mustang
, nhưng do chúng ta đã kế thừa lớp Horse
, do đó phương thức print()
trong lớp Horse
đã override lại phương thức print()
của interface Mammal
nên chúng ta không cần phải override một lần nữa.
Tuy nhiên có một lưu ý là các phương thức static sẽ không bao giờ được override theo cách này.