Toàn tập kế thừa (extends) trong Java

Một trong những kiến thức khá quan trọng nhất trong lập trình Java đó chính là tính kế thừa (extends) trong Java. Đây là kiến thức bạn bắt buộc phải học và hiểu cơ chế, ý nghĩa của nó để có thể áp dụng trong ứng dụng của bạn.

Khi chúng ta nói về tính kế thừa, từ khóa thường xuyên nhất được sử dụng là extends trong java và implements trong java. Tất nhiên sẽ có sự khác biệt giữa 2 từ khóa này. Các bạn có thể xem bài viết phần biệt giữa extends và implements tại đây. Trong bài này tôi sẽ cụ thể hóa và nói kỹ về extends trong Java nhé.

Tại sao sử dụng tính kế thừa trong Java?

Chẳng hạn như Tôi có class Sach gồm 2 thuộc tính là mã và tên của sách, nhưng bây giờ tôi cũng có một class khác là SachLapTrinh, với sách lập trình thì nó cũng có hai thuộc tính là mã và tên của sách, vậy bây giờ vấn đề đặt ra là tôi phải tạo class SachLapTrinh như class Sach gồm 2 thuộc tính trên hay có cách nào để giải quyết bài toán này một cách đơn giản mà không cần tạo 2 thuộc tính trên? Câu trả lời là có. Chúng ta sẽ sử dụng tính kế thừa trong Java.

Để cụ thể và dễ hiểu, hình dùng với ví dụ trên, các bạn xem code bên dưới:

Class Sach:

package pk1;

/**
 *
 * @author PHU TRAN IT
 */
public class Sach {
    private int ma;
    private String name;

   
}

Class SachLapTrinh :

package pk1;

/**
 *
 * @author PHU TRAN IT
 */
public class SachLapTrinh extends Sach{ 
   public static void main(String[] args) {
        SachLapTrinh sLapTrinh = new SachLapTrinh();           
    }
}

Cú pháp của tính kế thừa trong Java

class lopcon extends lopcha{  
   //cac phuong thuc va cac thuoc tinh  
}

Từ khóa extends có ý nghĩa rằng bạn đang tạo một lớp mới và kế thừa từ một lớp đang tồn tại.Trong Java, một lớp mà được kế thừa được gọi là lớp cha, lớp kế kế thừa là lớp con.

Ví dụ tính kế thừa trong Java

Như vậy với ví dụ trên: Sach là lớp cha, SachLapTrinh là lớp con.

Bây giờ chúng ta cùng nhau test tính kế thừa nhé:

tính kế thừa trong Java

Mặc dù chúng ta đã kế thừa từ lớp Sach, nhưng SachLapTrinh không có những thuộc tính của lớp cha và như ví dụ trên sẽ bị lỗi. Nhìn kỹ lại code trên, các bạn thấy rằng những thuộc tính của lớp cha pham vi truy cập là private nên chỉ có phạm phi trong class Sach mới sử dụng được thôi. Cho nên chú ý thứ nhất khi kế thừa trong Java là :

Chúng ta sử dụng từ khóa extends của lớp con để có thể kế thừa các thuộc tính của lớp cha trừ các thuộc tính private của lớp cha. 

Tất nhiên các bạn để phạm vi truy cập là public tất nhiên Class SachLapTrinh sẽ kế thừa được những thuộc tính đó. Tuy nhiên vì tính bao đóng trong Java nên khuyên các bạn chúng ta nên khai báo thuộc tính là private. Java cũng khuyến cáo chúng ta như vậy và tất nhiên các lập trình viên cần phải có ý thức để thuộc tính là private.

Tiếp theo tôi sẽ viết một số phương thức ở lớp cha, cho người dùng nhập vào mã và tên sách và phương thức dùng để in.

Class Sach:
public void nhap(){
        Scanner s = new Scanner(System.in);
        System.out.println("Nhập mã : ");
        this.ma = Integer.parseInt(s.nextLine());
        System.out.println("Nhap tên : ");
        this.name = s.nextLine();
    }

    @Override
    public String toString() {
        return "Sach{" + "ma=" + ma + ", name=" + name + '}';
    }

chạy chương trình ở lớp con (class SachLapTrinh) như sau:

public class SachLapTrinh extends Sach{
  
    public static void main(String[] args) {
        SachLapTrinh lapTrinh = new SachLapTrinh();
        lapTrinh.nhap();
        System.out.println(lapTrinh.toString());
           
    }
}

và tất nhiên kết quả sẽ như mong đợi:

kế thừa trong java

 

Như vậy, các bạn thấy kế thừa thật tuyệt vời đúng không nào, class SachLapTrinh chả code gì cả nhưng vẫn có những hàm như nhap(), toString(). Qúa đó , ta thấy được rằng :

Chúng ta sử dụng từ khóa extends của lớp con để có thể kế thừa các phương thức của lớp cha.

Vậy khi làm như trên, có những gì bất cập nào? Chúng ta cùng suy nghĩ. Giả sử như ở class SachLapTrinh tôi có thêm một thuộc tính nữa là số trang (sotrang) của sách.

package pk1;
public class SachLapTrinh extends Sach{
    private int soTrang; 
public static void main(String[] args) {
 SachLapTrinh lapTrinh = new SachLapTrinh();
 lapTrinh.nhap();
 System.out.println(lapTrinh.toString());
 
 }
}

và tất nhiên khi chạy chương trình thì chúng ta không thể nhập được số trang.Vậy chúng ta sẽ làm thế nào? Ta sẽ viết thêm hàm nhập và hàm in cho class SachLapTrinh, đơn giản vì class Sach là lớp cha, no không thể biết là class con cần thêm những phương thức gì, những thuộc tính gì để khai báo.Cách xử lý như sau:

public class SachLapTrinh extends Sach{
    private int soTrang;
    
     public void nhap(){
        super.nhap();
        Scanner s = new Scanner(System.in);
        System.out.println("Nhập số trang : ");
        this.soTrang = Integer.parseInt(s.nextLine());
       
    }
  
    public String toString() {
        return super.toString() + "- số trang : "+soTrang;
    }
    
    public static void main(String[] args) {
        SachLapTrinh lapTrinh = new SachLapTrinh();
        lapTrinh.nhap();
        System.out.println(lapTrinh.toString());
           
    }
}

Ở code trên, các bạn thấy rằng chúng ta viết lại y hệt phương thức của class cha, nhưng thây vì phải nhập lại mã , tên sách, thì tôi sẽ dùng từ khóa super để gọi phương thức của lớp cha. Khi chúng ta viết lại phương thức cùng tên,cùng phạm vi truy cập và cùng kiểu dữ liệu trả về của lớp cha. Vậy khi gọi phương thức nhap() cũng như toString() thì nó sẽ chạy phương thức của lớp con.Và loại bỏ phương thức của thằng cha đi.

Với khái niệm trên chúng ta gọi đây là ghì đè trong java hay còn gọi là override.Nếu Bạn nào chưa tìm hiểu về vấn đề này thì có thể tìm hiểu tại đây. 

Thông thường thì khi sử dụng kế thừa (extends) thì chúng ta sẽ sử dụng ghì đè.

Câu hỏi :   Vì sao phải ghì đè? 

  1. Để class con đảm bảo đầy đủ các chức năng để  nó hoạt động tốt.
  2. Để đồng bộ code.

Kết quả chạy chương trình trên:

kế thừa trong java

Vậy theo các bạn phương thức setter và getter trong java có ghì đè được không? Qúa đơn giản, tất nhiên là được vì nó là một hàm bình thường. Tuy nhiên bây giờ theo các bạn, hàm đặc biệt (hàm khởi tạo hay constructor) trong java có ghì đè được không?

Trước khi trả lời câu hỏi này,  gợi ý các bạn như sau:

Constructor là hàm dùng để tạo ra object từ một class nào đấy. (đương nhiên class nào thì đi kèm với constructor của nó). 

Chắc các bạn có thể dễ dàng đoán ra được rồi với gợi ý của mình rồi chứ? Không nhé.

Nếu class SachLapTrinh ghì đè constructor của lớp Sach thì nó quá vô nghĩa và chả có tác dụng gì.

Câu hỏi:hàm đặc biệt (hàm khởi tạo hay constructor) trong java có ghì đè được không?

Trả lời :Không, nhưng lại có. (trả lời thế nhà tuyển dụng mới sợ nhé).

Giải thích như sau:

Khi tạo ra object lớp con, thì tất nhiên object lớp cha cũng được khởi tạo.Vì sao? các bạn suy nghĩ lại xem ở ví dụ trên, nếu không tạo ra object lớp cha thì lớp con sao lấy được mã, tên?

Nhìn hình minh họa trên các bạn có thể hiểu.Để tạo object Sach thì gọi constructor của class Sach.

Vậy các bạn để ý rằng ở lớp con (class SachLapTrinh) chúng ta chưa hề gọi constructor của lớp cha(Sach). Thậm chí rằng ở class con chúng ta chưa hề viết constructor.Nhưng tại sao nó vẫn chạy. Tại vì:

Nguyên tắc :

Trong constructor của class con khi extends phải gọi constructor của lớp cha, nếu không có constructor  thì hệ thống sẽ tự tạo constructor mặc định và gọi constructor của lớp cha.

public SachLapTrinh() {
        super(); // nếu không dùng super thì hệ thống cũng tự gọi.
    }

để chứng minh cho vấn đề trên, ở constructor lớp cha, tôi sẽ in ra dòng chữ như sau:

 public Sach() {
        System.out.println("Khoi tao doi tuong lop cha");
    }

Vậy khi chạy:

 

Vậy như câu trả lời của tôi, không kế thừa nhưng lại kế thừa là ở chỗ đấy. constructor  của lớp con phải bắt buộc gọi constructor  của lớp cha. Nếu không gọi thì mặc định hệ thống sẽ tư gọi.

Kết luận: super cũng dùng để gọi constructor của lớp cha.

Nhưng lại có một trường hợp trơ trêu là constructor của lớp cha không có, hoặc tôi cố tình thay phạm vi truy cập thành private thì điều gì xảy ra? Nó sẽ bị lỗi ngay, vì mặc định hệ thống sẽ gọi constructor của lớp cha, mà constructor lại không có nên đơn nhiên nó bị lỗi.(Các bạn thử thay vào trong code nhé, tôi lười chụp lại ảnh quá).

Vậy chúng ta sẽ xử lý như thế nào? 

  1. Các bạn phải tự gọi constructor của lơp cha, một constructor  nào đấy nhưng là public nhé. Ví dụ constructor  có tham số chẳn hạn.
 public SachLapTrinh() {
        super(1,"tên sách lập trình"); // tự gọi constructor của lớp cha.
   }

2. Thông thường thì chúng ta không làm như thế này, mà sẽ làm như sau:

public SachLapTrinh(int ma,String ten) {
        super(ma,ten);
    }

làm cách thứ hai thì nhìn vào code rất trong sang và đồng bộ.

Tôi nói thêm như vậy để các bạn hiểu bản chất, chứ thật ra chả ai viết ra constructor mà để private cả ^^.


Chưa hết , giả sử như class của lớp cha, không có constructor nào thì điều gì lại xảy ra tiếp theo?

Cái này người ta gọi là cấm kế thừa. Một trong những cách cấm kế thừa nữa là ở class các bạn muốn cấm kế thừa thêm vào từ khóa final.

public final class  Sach {}

Vì sao phải cấm kế thừa? Vì một số trường hợp chúng ta đã viết một số class rất hoản chỉnh rồi và cho người khác sử dụng, tất nhiên đã hoàn chỉnh rồi thì thằng nào muốn dùng thì copy class sử dụng, chứ nếu cho nó kế thừa thì uổng công sức bỏ ra viết quá nhỉ. @@.

Lưu ý: Khi kế thừa các bạn phải có ý thức kế thừa chính xác, nghĩa là ví dụ class ConMeo thì không thể extends từ class SinhVat được. Tất nhiên các bạn  code thì nó vẫn chạy nhưng sai về mặt ý nghĩa. Chúng ta là lập trình viên thì phải ý thức được điều đấy.


Đôi lời…

Bài viết trên là những kinh nghiệm của tôi, cũng như trong thực tế các bạn sẽ gặp. Hy vọng các bạn sẽ hiểu tất cả những gì được gọi là kế thừa trong java hay sang chảnh thì extends ^^.

Hẹn các bạn ở một bài viết khác.

0 0 đánh giá
Đánh giá bài viết
Theo dõi
Thông báo của
guest
0 Góp ý
Phản hồi nội tuyến
Xem tất cả bình luận
x