Java Bài 34: Lớp Lồng – Yellow Code Books

Rate this item:

Rating: 4.8/5. From 41 votes.

Please wait…

Được chỉnh sửa ngày 22/01/2022.

Chào mừng các bạn đã đến với bài học Java số 34, bài học về lớp lồng trong Java. Đây là bài học trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.

Đọc đến tiêu đề chắc hẳn bạn đã biết nội dung mà bài học hướng đến rồi. Đó chính là kiến thức về việc khai báo một lớp ở bên trong một lớp khác. Đây không phải là quan hệ cha-con gì nhé, do đó nó không phải là kế thừa, nó chỉ là một lớp được khai báo bên trong lớp khác mà thôi. Vậy chúng ta cùng nhau tìm hiểu xem việc lồng các lớp vào với nhau là gì và tại sao lại làm như vậy nhé.

Lớp Lồng Là Gì?

Mình chỉ nhắc lại ý trên kia thôi, lớp lồng chính là việc khai báo một lớp ở bên trong một lớp khác.

Lớp mà chứa các lớp khác bên trong nó người ta gọi là Outer Class, có thể hiểu theo tiếng Việt là lớp Bao.

Còn lớp ở bên trong Outer Class thì được chia ra làm hai loại khác nhau.

  • Một là

    lớp không-static (non-static class)

    , người ta gọi loại này là

    Inner Class

    .

  • Loại còn lại là

    lớp static (static class)

    , người ta gọi loại này là

    Static Nested Class

    .

Khoan hãy nói đến vai trò của từng loại Inner ClassStatic Nested Class, chúng ta hãy đến với cú pháp khai báo của từng loại như sau.

Đây là cú pháp của một Inner Class.

class

OuterClass

{ ...

class

InnerClass

{ ... } }

Còn đây là cú pháp của một Static Nested Class.

class

OuterClass

{
...

static

class

StaticNestedClass

{ ... } }

Hai cú pháp không khác gì cả ngoài từ khóa static đúng không nào. Qua cú pháp trên, chúng ta có một số ý sau cần ghi nhớ.

  • Tuy cú pháp chỉ có một

    Outer Class

    chứa một

    Inner Class

    hoặc một

    Static Nested Class

    bên trong. Nhưng bạn nên biết rằng bên trong một

    Outer Class

    có thể chứa nhiều

    Inner Class

    , nhiều

    Static Nested Class

    , hoặc chứa nhiều cả

    Inner Class

    lẫn

    Static Nested Class

    .

  • Và tuy cú pháp chỉ nói đến

    lớp lồng

    , nhưng bạn có thể lồng interface vào trong lớp, hoặc lồng

    interface

    vào với nhau, hay lồng lớp vào trong

    interface

    đều được nhé.

  • Inner class

    lúc này được xem như một thành phần của

    Outer Class

    , chính vì vậy bạn có thể chỉ định cho nó các khả năng truy cập, như

    private

    ,

    public

    ,

    protected

    , hoặc

    default

    (tức là không có khai báo khả năng truy cập gì). Điều này khác với

    Outer Class

    hay các lớp mà bạn đã từng làm quen, đều chỉ có thể khai báo

    public

    hoặc

    default

    (khai báo này chỉ cho phép các lớp cùng trong một package mới có thể nhìn thấy nhau) mà thôi.

Thực Hành Khai Báo & Sử Dụng Inner Class

Bạn nên nhớ là ứng dụng của lớp lồng là khá nhiều, nhưng việc sử dụng chúng hay không là không bắt buộc, và nếu cần sử dụng thì cũng không khó lắm.

Bài thực hành này chúng ta hãy xem việc định nghĩa và sử dụng một Inner Class như thế nào. Xong bài thực hành này chúng ta sẽ nói cụ thể hơn về công dụng của lớp lồng sau nhé.

Code sau khai báo lớp ToaDo được đặt bên trong lớp HinhHoc. Bạn cũng có thể khai báo lớp ToaDo ở ngoài HinhHoc như đã thực hành ở bài nào đấy, nhưng việc để ToaDo vào trong HinhHoc, bạn thấy lớp này có thể sử dụng thuộc tính tenHinh của lớp bao.

public class HinhHoc {
 
    public static final float PI = 3.14f;
 
    public String tenHinh;
    public ToaDo toaDo;
 
    // Constructor
    public HinhHoc(int x, int y) {
        this.tenHinh = "Hình Học";
        this.toaDo = new ToaDo();
        this.toaDo.x = x;
        this.toaDo.y = y;
    }
 
    public class ToaDo {
 
        int x;
        int y;
 
        public void xuatThongTin() {
            System.out.println("Hình: " + tenHinh);
            System.out.println("Tọa độ: x = " + x + "; y = " + y);
        }
    }
}

Việc lớp lồng sử dụng được thuộc tính của lớp bao sẽ được mình nói rõ hơn ở mục bên dưới các bài thực hành.

Giờ thì bạn tiếp tục xem ở phương thức main() chúng ta khai báo lớp HinhHoc và gọi đến xuatThongTin() của ToaDo như sau, chuyện gì sẽ xảy ra?

public class MainClass {
 
    public static void main(String[] args) {
        HinhHoc hinhHoc = new HinhHoc(10, 20);
        hinhHoc.toaDo.xuatThongTin();
    }
 
}

Chúng ta sẽ nhận được thông tin sau.

Kết quả in ra consoleKết quả in ra consoleKết quả in ra console

Với code trên của phương thức main(), bạn có thể thấy chúng ta khai báo đối tượng của lớp HinhHoc, rồi dùng đến biến toaDo của nó, trên kia viết như này hinhHoc.toaDo.xuatThongTin(). Bạn cũng có thể khai báo đối tượng riêng của lớp ToaDo để dùng cho những lần sau, như sau.

public class MainClass {
 
    public static void main(String[] args) {
        HinhHoc hinhHoc = new HinhHoc(10, 20);
        HinhHoc.ToaDo toaDo = hinhHoc.new ToaDo();
        toaDo.xuatThongTin();
    }
 
}

Bạn có thấy cách khai báo lớp ToaDo như code trên đây tuy lạ mà quen đúng không nào. Nhớ lại, ở các bài đầu tiên của OOP, khi mà mình chưa hướng dẫn các bạn có thể tạo nhiều lớp bên trong một project Java, mình đã phải dùng đến lớp lồng, bạn xem code ở bài này. Khi đó cũng có bạn nhanh trí nhìn ra vấn đề và hỏi, đến bây giờ mình mới có dịp được nhắc lại.

Gợi nhớ lại câu hỏi liên quan đến lớp lồng đã xuất hiện trước đóGợi nhớ lại câu hỏi liên quan đến lớp lồng đã xuất hiện trước đóGợi nhớ lại câu hỏi liên quan đến lớp lồng đã xuất hiện trước đó

Bạn có thể hiểu lúc này ToaDo được xem như là một thành viên của HinhHoc. Chính vì vậy bạn chỉ có thể khởi tạo độc lập ToaDo thông qua đối tượng của HinhHoc là hinhHoc mà thôi.

Khi bạn thực thi dòng code trên kia của main(), bạn sẽ nhận được kết quả hơi khác chút như sau. Bạn có thể tự giải đáp tại sao kết quả lại khác như vậy không?

Kết quả của lần sửa cách dùng ToaDoKết quả của lần sửa cách dùng ToaDoKết quả của lần sửa cách dùng ToaDo

Thực Hành Khai Báo & Sử Dụng Static Nested Class

Mình cũng sẽ sử dụng kịch bản từ bài thực hành trên, nhưng thay lớp ToaDo bên trong HinhHoc bằng một Static Nested Class. Bạn xem có gì khác biệt không nhé.

public class HinhHoc {
 
    public static final float PI = 3.14f;
 
    public static String tenHinh;
    public ToaDo toaDo;
 
    // Constructor
    public HinhHoc(int x, int y) {
        this.tenHinh = "Hình Học";
        this.toaDo = new ToaDo();
        this.toaDo.x = x;
        this.toaDo.y = y;
    }
 
    public static class ToaDo {
 
        int x;
        int y;
 
        public void xuatThongTin() {
            System.out.println("Hình: " + tenHinh);
            System.out.println("Tọa độ: x = " + x + "; y = " + y);
        }
    }
}

Bạn có thể thấy, vì ToaDo được định nghĩa là một lớp static, nên nó chỉ có thể truy xuất đến các thuộc tính và phương thức static của lớp bao. Điều này giống với phương thức static mà bạn đã học, khi đó phương thức static cũng chỉ có thể gọi đến các thuộc tính được khai báo static mà thôi.

Với việc khai báo ToaDo như thế này, bạn vẫn gọi đến và sử dụng thông qua đối tượng bao hinhHoc một cách bình thường.

public class MainClass {
 
    public static void main(String[] args) {
        HinhHoc hinhHoc = new HinhHoc(10, 20);
        hinhHoc.toaDo.xuatThongTin();
    }
 
}

Hoặc bạn có thể khai báo độc lập trực tiếp ToaDo hơi khác với khi khai báo ToaDo là Inner Class như trên.

public class MainClass {
 
    public static void main(String[] args) {
        HinhHoc.ToaDo toaDo = new HinhHoc.ToaDo();
        toaDo.xuatThongTin();
    }
 
}

Bạn thử tự thực thi ứng dụng để xem console in ra gì nhé.

Khi Nào Nên Sử Dụng Lớp Lồng & Công Dụng Của Nó?

Dựa trên những gì chúng ta đã làm quen với lớp lồng trên đây, chúng ta đã có đủ trải nghiệm để tổng hợp lại vài công dụng của nó.

  • Nếu bạn có một lớp

    A

    chỉ dùng đến một lớp

    B

    nào đó. Tức là

    B

    không được ai dùng đến cả ngoại trừ

    A

    thôi. Thì bạn có thể xem xét tổ chức theo cách

    B sẽ là lớp lồng vào trong lớp A

    . Lợi ích của việc này thì bạn có thể xem các ý dưới đây.

  • Việc lồng các lớp vào nhau giúp tăng tính gói gém dữ liệu (

    encapsulation

    ). Chẳng hạn với việc lớp

    B

    nằm trong lớp

    A

    , thì bạn có thể khai báo

    B

    private

    , khi đó ngoài

    A

    ra không có bất kỳ lớp nào khác có thể biết đến sự tồn tại của

    B

    và các giá trị của nó, có thể nói rằng bạn đã “giấu”

    B

    khỏi thế giới, ngoại trừ

    A

    .

  • Ở chiều ngược lại. Lớp lồng 

    B

     có thể truy cập đến tất cả các thành viên (thuộc tính và phương thức) của lớp bao

    A

    , ngay cả khi các thành viên của

    được khai báo là

    private

    .

  • Tất nhiên, khi lồng các lớp vào nhau như vậy, code của bạn sẽ dễ đọc hơn (vì không phải tìm và mở quá nhiều lớp), dẫn đến việc bảo trì cũng dễ dàng hơn.

Bài Tập Số 1

Chúng ta thử trắc nghiệm xem đã hiểu bài chưa thông qua bài tập này.

Giả sử có định nghĩa lớp bao và lớp lồng như sau.

public class OuterClass {
 
    public void show() {
        InnerClass innerClass = new InnerClass();
        innerClass.show();
    }
 
    public class InnerClass {
 
        public void show() {
            System.out.println("Đây là inner class");
        }
    }
}

Bạn thử trả lời xem kết quả in ra console sẽ như thế nào với đoạn code ở phương thức main() như sau.

public class MainClass {
 
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.show();
 
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
        innerClass.show();
    }
 
}

Hãy tham khảo đáp án bên dưới để so sánh với câu trả lời của bạn.

Đây là inner class
Đây là inner class

Bài Tập Số 2

Tương tự với bài tập số 1. Giả sử mình có định nghĩa lớp bao và lớp lồng như sau.

public class NgayThangNam {
 
    public int ngay, thang, nam;
 
    public class GioPhutGiay {
 
        public int gio, phut, giay;
 
        public void xuatThongTin() {
            System.out.println("Ngày: " + ngay + "/" + thang + "/" + nam);
            System.out.println("Giờ: " + gio + ":" + phut + ":" + giay);
        }
    }
}

Và rồi ở phương thức main() mình gọi đến chúng như sau.

public class MainClass {
 
    public static void main(String[] args) {
        NgayThangNam date = new NgayThangNam();
        date.ngay = 31;
        date.thang = 10;
        date.nam = 2017;
 
        NgayThangNam.GioPhutGiay time = date.new GioPhutGiay();
        time.gio = 10;
        time.phut = 15;
        time.giay = 30;
 
        time.xuatThongTin();
    }
 
}

Bạn thử trả lời kết quả in ra console là gì nhé. Và đây là đáp án.

Ngày: 31/10/2017
Giờ: 10:15:30

Kết Luận

Kiến thức về lớp lồng chỉ có vậy thôi. Bạn sẽ quen với việc sử dụng lớp lồng này khi dùng nhiều đến ngôn ngữ Java, hoặc khi bạn đọc code của người khác, hay khi qua lập trình với Android. Tuy nhiên, cũng còn một tí kiến thức liên quan đến lớp lồng này nữa, mình sẽ gộp chung kiến thức còn thiếu này vào bài học về lớp Vô danh (Anonymous) luôn nhé.

Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài viết nếu thấy thích.
– Comment bên dưới mỗi bài viết nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
– Ủng hộ blog theo hướng dẫn ở thanh bên phải để blog ngày càng phát triển hơn.