Inheritance (kế thừa) trong Java là một khái niệm cốt lõi trong lập trình hướng đối tượng. Kế thừa được sử dụng khi chúng ta có quan hệ is-a giữu các object. Nó cho phép chúng ta mang những đặc tính của class cha sang class con. Tính năng này giúp tích kiệm thời gian cũng như dữ liệu dư thừa, lượng code giảm. Kế thừa được thực thi bằng cách sử dụng từ khóa extends
Kế thừa có tính chất bắc cầu, nghĩa là Sedan extends Car
và Car extends Vehicle
thì cũng có nghĩa là Sedan
sẽ kế thừa từ class Vehicle
. Lúc này thì Vehicle
trở thành superclass của cả Car
và Sedan
.
Kế thừa được sử dụng rộng rãi trong các ứng dụng Java, ví dụ chúng ta có thể mở rộng thêm class Exception để tạo một class Exception cụ thể cho ứng dụng mà sẽ chứa nhiều thông tin hơn là chỉ có các mã lỗi. Ví dụ NullPointerException
.
Ví dụ
Tất cả các class trong Java được ngầm định extends
từ class java.lang.Object
. Do đó class Object
nằm ở cấp cao nhất của hệ thống phân cấp kế thừa trong Java.
Chúng ta hãy cùng xem ví dụ sau đây về kế thừa trong Java
Superclass: Animal
package com.example.inheritance; public class Animal { private boolean vegetarian; private String eats; private int noOfLegs; public Animal(){} public Animal(boolean veg, String food, int legs){ this.vegetarian = veg; this.eats = food; this.noOfLegs = legs; } public boolean isVegetarian() { return vegetarian; } public void setVegetarian(boolean vegetarian) { this.vegetarian = vegetarian; } public String getEats() { return eats; } public void setEats(String eats) { this.eats = eats; } public int getNoOfLegs() { return noOfLegs; } public void setNoOfLegs(int noOfLegs) { this.noOfLegs = noOfLegs; } }
Ở đây, Animal
là class cơ sở. Hãy tạo một class Cat
kế thừa từ class Animal
Subclass: Cat
package com.example.inheritance; public class Cat extends Animal{ private String color; public Cat(boolean veg, String food, int legs) { super(veg, food, legs); this.color="White"; } public Cat(boolean veg, String food, int legs, String color){ super(veg, food, legs); this.color=color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
Chú ý rằng sử dụng từ khóa extends
để thực thi kế thừa trong Java
Viết chương trình test
Hãy viết một class test đơn giản để tạo Cat object và sử dụng một vài phương thức của nó
package com.example.inheritance; public class AnimalInheritanceTest { public static void main(String[] args) { Cat cat = new Cat(false, "milk", 4, "black"); System.out.println("Cat is Vegetarian?" + cat.isVegetarian()); System.out.println("Cat eats " + cat.getEats()); System.out.println("Cat has " + cat.getNoOfLegs() + " legs."); System.out.println("Cat color is " + cat.getColor()); } }
Kết quả
Cat is Vegetarian?false Cat eats milk Cat has 4 legs. Car color is black
Class Cat
không có phương thức getEats()
nhưng vẫn chạy bởi vì nó kế thừa từ class Animal
Ưu điểm của Inheritance
- Khả năng sử dụng lại code là lợi ích quan trọng nhất khi sử dụng kế thừa bởi vì subclass kế thừa các biến và phương thức của superclass
- Các thành phần Private của superclass sẽ không thể truy cập trực tiếp bởi subclass. Ví dụ biến
noOfLegs
không thể truy cập từ classCat
, nhưng nó có thể được truy cập một cách gián tiếp sử dụng các phương thứcgetter
vàsetter
- Các thành phần của superclass với các truy cập mặc định chỉ có thể truy cập được bởi subclass nếu chúng có cùng chung package
- Constructor của superclass không được kế thừa bởi subclass
- Nếu superclass không có constructor mặc định thì subclass cũng cần có một định nghĩa constructor rõ ràng, nếu không nó sẽ đưa ra một Exception khi biên dịch. Trong trường hợp này, trong constructor của subclass bắt buộc phải gọi constructor của superclass và nó nên là câu lệnh đầu tiên ở trong constructor của subclass.
- Java không hỗ trợ nhiều kế thừa, một subclass chỉ có thể
extends
từ một class.Animal
ngầm kế thừa từ classObject
vàCat
được kế thừa từ classAnimal
, nhưng do tính bắc cầu của kế thừa trong Java, nên classCat
cũng kế thừa từ classObject
- Chúng ta có thể tạo một instance của subclass sau đó gán nó cho một biến của superclass, đây được gọi là upcasting, Sau đây là ví dụ của upcasting
Cat c = new Cat(); //subclass instance Animal a = c; //upcasting, nó hoạt động tốt vì Cat cũng là một Animal
- Khi một instance của superclass được gán cho một biến của subclass thì được gọi là downcasting, chúng ta cần chỉ định kiểu rõ ràng cho subclass, sau đây là ví dụ về downcasting
Cat c = new Cat(); Animal a = c; Cat c1 = (Cat) a; //chỉ định kiểu rõ ràng subclass, chạy tốt bởi vì"c" chắc chắn là kiểu của Cat
Chú ý rằng trình biên dịch sẽ không thông báo thậm trí khi chúng ta đang thực hiện sai khi dùng chỉ định kiểu rõ ràng. Sau đây là một vài trường hợp sẽ bị lỗi trong thời gian chạy.
Dog d = new Dog(); Animal a = d; Cat c1 = (Cat) a; //ClassCastException khi chạy Animal a1 = new Animal(); Cat c2 = (Cat) a1; //ClassCastException bởi vì a1 chắc chắn kiểu Animal khi chạy
- Chúng ta có thể viết đè các phương thức của superclass trong subclass. Tuy nhiên chúng ta nên chú thích các phương thức bị ghi đè bằng @Override annotation. Trình biên dịch sẽ không biết được rằng chúng ta đang ghi đè một phương thức. Nếu có một vài thứ thay đổi nào trong phương thức của superclass, chúng ta nên nhận được lỗi khi chạy biên dịch hơn là nhận được kết quả không mong muốn khi chạy. Chúng ta có thể gọi các phương thức superclass và truy cập các biến của superclass sử dụng từ khóa super. Nó rất là hữu ích khi chúng ta có cùng tên các phương thức và biến trong subclass nhưng chúng ta muốn truy cập biến và phương thức của superclass. Nó cũng được sử dụng khi các constructor được định nghĩa trong superclass và subclass, chúng ta phải gọi chính xác constructor của subclass.
- Chúng ta có thể sử dụng chỉ thị
instanceof
để kiểm tra kế thừa giữa các object, hãy cùng xem ví dụ bên dưới đâyCat c = new Cat(); Dog d = new Dog(); Animal an = c; boolean flag = c instanceof Cat; //trường hợp thông thường, trả về true flag = c instanceof Animal; // trả về true vì c cũng là is-an Animal flag = an instanceof Cat; //trả về true vì a kiểu của Cat khi chạy flag = a instanceof Dog; //trả về false với lý do rõ ràng.
- Chúng ta không thể
extends
final class trong Java - Nếu bạn sẽ không sử dụng superclass trong code ví dụ superclass của bạn chỉ là một cơ sở để giúp sử dụng lại code thì bạn nên đặt nó trong một abstract class để tránh những khởi tạo không cần thiết bởi các class phía client. Nó cũng hạn chế tạo instance của class cơ sở.