Lớp lồng nhau trong java (Java inner class)

Lớp lồng nhau (inner class) trong java là một lớp được khai báo trong lớp (class) hoặc interface khác.

Chúng ta sử dụng inner class để nhóm các lớp và các interface một cách logic lại với nhau ở một nơi để giúp cho code dễ đọc và dẽ bảo trì hơn.

Thêm vào đó, nó có thể truy cập tất cả các thành viên của lớp bên ngoài (outer class) bao gồm các thành viên dữ liệu private và phương thức.

Cú pháp:

class Java_Outer_class {
    // code
    class Java_Inner_class {
        // code
    }
}

Các ưu điểm của inner class trong java:

  • Inner class biển diễn cho một kiểu đặc biệt của mối quan hệ đó là nó có thể truy cập tất cả các thành viên (các thành viên dữ liệu và các phương thức) của lớp ngoài bao gồm cả private.
  • Inner class được sử dụng để giúp code dễ đọc hơn và dễ bảo trì bởi vì nó nhóm các lớp và các interface một cách logic vào trong một nơi.
  • Code được tối ưu hóa: tiết kiệm code hơn.

Các kiểu của lớp lồng nhau trong java:

Lớp lồng nhau non-static (inner class):

  • Member Inner Class: Một lớp được tạo ra bên trong một lớp và bên ngoài phương thức.
  • Annomynous Inner Class: Một lớp được tạo ra để implements interface hoặc extends class. Tên của nó được quyết định bởi trình biên dịch java.
  • Local Inner Class: Một lớp được tạo ra bên trong một phương thức.

Lớp lồng nhau Static:

  • Member Inner Class: Một lớp được tạo ra bên trong một lớp và bên ngoài phương thức.
  • Anonymous Inner Class: Một lớp được tạo ra để implements interface hoặc extends class. Tên của nó được quyết định bởi trình biên dịch java.
  • Local Inner Class: Một lớp được tạo ra bên trong một phương thức.
  • Static Nested Class: Một lớp static được tạo ra bên trong một lớp.
  • Nested Interface: Một interface được tạo ra bên trong một lớp hoặc một interface.

1. Member inner class trong java

Một lớp non-static được tạo ra bên trong một lớp nhưng ngoài một phương thức được gọi là thành viên bên trong lớp hay member inner class trong java.

Cú pháp:

class Outer {
    // code
    class Inner {
        // code
    }
}

Ví dụ:

class MemberOuterExample {
    private int data = 30;
 
    class Inner {
        void msg() {
            System.out.println("data is " + data);
        }
    }
 
    public static void main(String args[]) {
        MemberOuterExample obj = new MemberOuterExample();
        MemberOuterExample.Inner in = obj.new Inner();
        in.msg();
    }
}

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

data is 30

Hoạt động nội bộ của member inner class trong java

Trình biên dịch java tạo ra hai file .class trong trường hợp của inner class. Tên của file của lớp bên trong là “Outer$Inner”. Nếu bạn muốn tạo ra thể hiện của lớp bên trong, bạn phải tạo ra thể hiện của lớp bên ngoài. Trong trường hợp này, thể hiện của lớp bên trong được tạo ra bên trong thể hiện của lớp bên ngoài.

Code nội bộ được tạo ra bởi trình biên dịch

Trình biên dịch java tạo ra một file .class tên là MemberOuterExample$Inner trong trường hợp này. Các member inner class có tham chiếu của lớp bên ngoài MemberOuterExample đó là lý do tại sao nó có thể truy cập tất cả các thành viên dữ liệu của lớp MemberOuterExample bao gồm cả private.

import java.io.PrintStream;
 
class MemberOuterExample$Inner {
    final MemberOuterExample this$0;
 
    MemberOuterExample$Inner() {
        super();
        this$0 = MemberOuterExample.this;
    }
 
    void msg() {
        System.out.println((new StringBuilder()).append("data is ")
            .append(MemberOuterExample.access$000(MemberOuterExample.this)).toString());
    }
}

Các file .class được trình biên dịch tạo ra khi thực hiện lệnh javac:

2. Anonymous inner class trong java

Một lớp mà không có tên được gọi là lớp vô danh (anonymous inner class). Nó nên được sử dụng nếu bạn phải ghi đè phương thức của lớp hoặc interface. Anonymous inner class có thể được tạo ra bằng hai cách:

  • Class: có thể là abstract class hoặc class cụ thể.
  • Interface

2.1. Ví dụ về anonymous inner class với class

abstract class Person {
    abstract void eat();
}
 
class TestAnonymousInner {
    public static void main(String args[]) {
        Person p = new Person() {
            void eat() {
                System.out.println("nice fruits");
            }
        };
        p.eat();
    }
}

Kết quả thực thi chương trình trên:

nice fruits

Hoạt động nội bộ của đoạn code trên:

Person p = new Person() {
    void eat() {
        System.out.println("nice fruits");
    }
};
  • Một lớp được tạo ra nhưng tên của nó được quyết định bởi trình biên dịch mà extends lớp Person và cung cấp việc cài đặt phương thức eat().
  • Một đối tượng của lớp Anonymous được tạo ra mà được gọi bằng biến tham chiếu p của kiểu Person.

2.2. Lớp nội bộ được tạo ra bởi trình biên dịch:

import java.io.PrintStream;
 
static class TestAnonymousInner$1 extends Person {
    TestAnonymousInner$1() {
    }
 
    void eat() {
        System.out.println("nice fruits");
    }
}

2.3. Ví dụ về anonymous inner class với interface

interface Eatable {
    void eat();
}
 
class TestAnnonymousInner1 {
    public static void main(String args[]) {
        Eatable e = new Eatable() {
            public void eat() {
                System.out.println("nice fruits");
            }
        };
        e.eat();
    }
}

Kết quả khi thực thi chương trình trên:

nice fruits

Hoạt động nội bộ của đoạn code trên:

Eatable e = new Eatable() {
    public void eat() {
        System.out.println("nice fruits");
    }
};

Một lớp được tạo ra nhưng tên của nó được quyết định bởi trình biên dịch mà extends interface Eatable và cung cấp việc cài đặt phương thức eat().
Một đối tượng của lớp Anonymous được tạo ra mà được gọi bằng biến tham chiếu e của kiểu Eatable.

Lớp nội bộ được tạo ra bởi trình biên dịch:

import java.io.PrintStream;
 
static class TestAnonymousInner1$1 implements Eatable {
    TestAnonymousInner1$1() {
    }
 
    void eat() {
        System.out.println("nice fruits");
    }
}

3. Local inner class trong java

Một lớp được tạo ra bên trong một phương thức được gọi là local inner class. Nếu bạn muốn gọi các phương thức của lớp được khai báo bên trong một phương thức, bạn phải tạo ra thể hiện của lớp này bên trong phương thức chứa nó.

public class localInner1 {
    private int data = 30;// instance variable
 
    void display() {
        class Local {
            void msg() {
                System.out.println(data);
            }
        }
        Local l = new Local();
        l.msg();
    }
 
    public static void main(String args[]) {
        localInner1 obj = new localInner1();
        obj.display();
    }
}

Kết quả khi thực thi chương trình trên:

....30

Lớp nội được tạo ra bởi trình biên dịch:

Trong trường hợp này, trình biên dịch sẽ tạo ra một lớp có tên LocalInner1$1Local.class có tham chiếu của lớp bên ngoài (LocalInner1).

import java.io.PrintStream;
 
class LocalInner1$Local {
    final LocalInner1 this$0;
 
    LocalInner1$Local() {
        super();
        this$0 = Simple.this;
    }
 
    void msg() {
        System.out.println(LocalInner1.access$000(LocalInner1.this));
    }
}

Quy tắc cho biến local: Biến local không thể là private, public hoặc protected.

Các quy tắc cho Local Inner class

  • Local Inner class không thể được gọi từ một phương thức bên ngoài.
  • Local Inner class không thể truy cập biến local non-final từ JDK 1.7 trở vể trước. Kể từ JDK 1.8, có thể truy cập vào các biến local non-final trong lớp local inner class.
class localInner2 {
    private int data = 30; // instance variable
 
    void display() {
        int value = 50; // biến local phải là final từ jdk 1.7 trở về trước
        class Local {
            void msg() {
                System.out.println(value);
            }
        }
        Local l = new Local();
        l.msg();
    }
 
    public static void main(String args[]) {
        localInner2 obj = new localInner2();
        obj.display();
    }
}

4. Static nested class trong java

Một lớp static được tạo bên trong một lớp được gọi là lớp static lồng hay static nested class. Nó không thể truy cập các thành viên và phương thức non-static. Nó có thể được truy cập bởi tên lớp bên ngoài.

  • Nó có thể truy cập các thành viên dữ liệu tĩnh (static data members) của lớp ngoài bao gồm cả private.
  • Static nested class không thể truy cập thành viên dữ liệu hoặc phương thức non-static (instance).

4.1. Ví dụ về static nested class với thành viên dữ liệu tĩnh (static data members)

class TestOuter1 {
    static int data = 30;
 
    static class Inner {
        void msg() {
            System.out.println("data is " + data);
        }
    }
 
    public static void main(String args[]) {
        TestOuter1.Inner obj = new TestOuter1.Inner();
        obj.msg();
    }
}

Kết quả thực thi chương trình trên:

data is 30

Trong ví dụ này, chúng ta cần tạo ra thể hiện lớp static lồng nhau, bởi vì nó có phương thức instance msg(). Nhưng không cần phải tạo ra đối tượng lớp ngoài (Outer class) vì lớp lồng bên trong là static, các phương thức hoặc các lớp có thể được truy cập mà không cần tạo đối tượng.

Lớp nội bộ được trình biên dịch tạo ra

import java.io.PrintStream;
 
static class TestOuter1$Inner {
    TestOuter1$Inner() {
    }
 
    void msg() {
        System.out.println((new StringBuilder()).append("data is ")
                .append(TestOuter1.data).toString());
    }
}

4.2. Ví dụ về static nested class với phương thức tĩnh (static method)

Nếu bạn có thành viên static bên trong lớp static lồng nhau, bạn không cần tạo ra thể hiện của lớp tĩnh lồng nhau.

class TestOuter2 {
    static int data = 30;
 
    static class Inner {
        static void msg() {
            System.out.println("data is " + data);
        }
    }
 
    public static void main(String args[]) {
        TestOuter2.Inner.msg(); // Không cần tạo ra thể hiện của static nested class
    }
}

5. Interface lồng nhau (nested interface) trong java

Một interface được khai báo trong một interface hoặc lớp khác được gọi là interface lồng nhau hay nested interface.

Các interface lồng nhau được sử dụng để nhóm các interface liên quan để chúng có thể dễ dàng được bảo trì. Interface lồng nhau phải được bao bọc bên ngoài bởi interface hoặc lớp. Nó không thể truy cập trực tiếp.

Một số điểm cần lưu ý:

  • Interface lồng nhau (nested interface) phải public nếu nó được khai báo bên trong interface, nhưng nó có thể có bất kỳ access modifier nào nếu khai báo trong lớp.
  • Interface lồng nhau (nested interface) được khai báo static một cách hiển nhiên.

5.1. Nested interface bên trong interface

Cú pháp:

interface interface_name {
    // code
    interface nested_interface_name {
        // code
    }
}

Ví dụ:

Trong ví dụ này, chúng ta sẽ học cách khai báo interface lồng nhau và cách chúng ta có thể truy cập nó.

interface Showable {
    void show();
  
    interface Message {
        void msg();
    }
}
  
class TestNestedInterface1 implements Showable.Message {
    public void msg() {
        System.out.println("Hello nested interface");
    }
  
    public static void main(String args[]) {
        Showable.Message message = new TestNestedInterface1(); // upcasting
        message.msg();
    }
}

Kết quả thực thi chương trình trên:

hello nested interface

Như bạn thấy trong ví dụ trên, chúng ta truy cập interface Message bằng interface Showable bên ngoài của nó vì nó không thể được truy cập trực tiếp. Trong khuôn khổ collection, hệ thống sun microsystem đã cung cấp một nested interface là Entry. Entry là subinterface của Map, nghĩa là được truy cập bởi Map.Entry.

Code nội bộ được tạo ra bởi trình biên dịch java cho interface lồng nhau Message:

public static interface Showable$Message {
    public abstract void msg();
}

5.2. Nested interface bên trong class

Cú pháp:

class class_name {
    // code
    interface nested_interface_name {
        // code
    }
}

Ví dụ:

Hãy xem chúng ta có thể định nghĩa một interface bên trong lớp như thế nào và chúng ta có thể truy cập nó như thế nào.

class A {
    interface Message {
        void msg();
    }
}
  
public class TestNestedInterface2 implements A.Message {
    public void msg() {
        System.out.println("Hello nested interface");
    }
  
    public static void main(String args[]) {
        A.Message message = new TestNestedInterface2();// upcasting
        message.msg();
    }
}

Kết quả thực thi chương trình trên:

hello nested interface

Chúng ta có thể định nghĩa một lớp bên trong interface?

Có, Nếu chúng ta xác định một lớp bên trong interface, trình biên dịch java tạo ra một lớp static lồng nhau. Hãy xem chúng ta có thể định nghĩa một lớp trong giao diện như thế nào:

interface M {
class A {
}
}