Trong bài viết này, chúng ta sẽ cùng tìm hiểu Java Inner Class.
Lớp lồng nhau không tĩnh (inner class) trong Java được định nghĩa bên trong phần thân của một lớp khác. Inner class trong Java có thể được khai báo với các mức truy cập private, public, protected hoặc default (mặc định), trong khi outer class (lớp bên ngoài) chỉ có thể được khai báo với mức truy cập public hoặc default. Các lớp lồng nhau (nested classes) trong Java được chia thành hai loại.

1. Lớp lồng nhau tĩnh (static nested class)
Nếu lớp lồng nhau được khai báo là static, thì nó được gọi là static nested class. Các static nested class chỉ có thể truy cập các thành viên static của lớp bên ngoài. Static nested class giống như bất kỳ lớp cấp cao nhất nào khác và chỉ được lồng vào nhau để thuận tiện cho việc đóng gói. Bạn có thể tạo một đối tượng của static nested class bằng câu lệnh sau:
OuterClass.StaticNestedClass nestedObject =
new OuterClass.StaticNestedClass();
2. Lớp lồng nhau không tĩnh (Inner class)
Bất kỳ lớp lồng nhau nào không có từ khóa static đều được gọi là inner class trong Java. Inner class trong Java có liên kết với đối tượng của lớp bên ngoài và có thể truy cập tất cả các biến và phương thức của lớp bao ngoài. Vì inner class gắn liền với instance (thể hiện) của lớp bên ngoài nên ta không thể khai báo biến static bên trong chúng. Đối tượng của inner class là một phần của đối tượng lớp bên ngoài, do đó để tạo một instance của inner class, trước tiên ta cần tạo một instance của lớp ngoài. Inner class trong Java có thể được khởi tạo theo cách sau;
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
Có hai dạng đặc biệt của Java inner class.
Local Inner Class (Lớp lồng nhau cục bộ)
Nếu một lớp được định nghĩa bên trong phần thân của một phương thức, thì lớp đó được gọi là local inner class. Vì local inner class không gắn với đối tượng (object), nên không thể sử dụng các modifier như private, public hoặc protected cho nó. Các modifier hợp lệ duy nhất là abstract hoặc final. Local inner class có thể truy cập tất cả các thành viên của lớp bao ngoài, cũng như các biến cục bộ final trong phạm vi phương thức nơi nó được khai báo. Ngoài ra, nó cũng có thể truy cập các biến cục bộ không phải final, nhưng không được phép thay đổi giá trị của các biến này. Vì vậy, nếu bạn chỉ in giá trị của một biến cục bộ không final, thì được phép. Nhưng nếu bạn cố gắng thay đổi giá trị của biến đó bên trong local inner class, chương trình sẽ báo lỗi biên dịch. Local inner class có thể được định nghĩa như sau:
package com.journaldev.innerclasses;
public class MainClass {
private String s_main_class;
public void print() {
String s_print_method = "";
// local inner class inside the method
class Logger {
// able to access enclosing class variables
String name = s_main_class;
// able to access non-final method variables
String name1 = s_print_method;
public void foo() {
String name1 = s_print_method;
// Below code will throw compile time error:
// Local variable s_print_method defined in an enclosing scope must be final or effectively final
// s_print_method= ":";
}
}
// instantiate local inner class in the method to use
Logger logger = new Logger();
}
}
Chúng ta cũng có thể định nghĩa một local inner class bên trong bất kỳ khối mã nào, chẳng hạn như khối static, khối if-else, v.v. Tuy nhiên, trong trường hợp này, phạm vi (scope) của lớp sẽ rất hạn chế, chỉ tồn tại trong khối mà nó được khai báo.
public class MainClass {
static {
class Foo {
}
Foo f = new Foo();
}
public void bar() {
if(1 < 2) {
class Test {
}
Test t1 = new Test();
}
// Below will throw error because of the scope of the class
//Test t = new Test();
//Foo f = new Foo();
}
}
Lớp lồng nhau ẩn danh (Anonymous Inner Class)
Một local inner class không có tên được gọi là anonymous inner class . Lớp này được định nghĩa và khởi tạo chỉ trong một câu lệnh duy nhất. Nó luôn kế thừa một lớp hoặc triển khai một interface. Vì anonymous classkhông có tên nên không thể định nghĩa constructor (hàm khởi tạo) cho nó. Lớp ẩn danh chỉ có thể truy cập tại đúng vị trí mà nó được định nghĩa. Việc định nghĩa một lớp ẩn danh hơi khó mô tả bằng lời, ta sẽ thấy cách sử dụng thực tế của nó trong chương trình thử nghiệm bên dưới.
Dưới đây là một lớp Java cho thấy cách định nghĩa java inner class, static nested class, local inner class, và anonymous inner class. OuterClass.java
package com.journaldev.nested;
import java.io.File;
import java.io.FilenameFilter;
public class OuterClass {
private static String name = "OuterClass";
private int i;
protected int j;
int k;
public int l;
//OuterClass constructor
public OuterClass(int i, int j, int k, int l) {
this.i = i;
this.j = j;
this.k = k;
this.l = l;
}
public int getI() {
return this.i;
}
//static nested class, can access OuterClass static variables/methods
static class StaticNestedClass {
private int a;
protected int b;
int c;
public int d;
public int getA() {
return this.a;
}
public String getName() {
return name;
}
}
//inner class, non-static and can access all the variables/methods of the outer class
class InnerClass {
private int w;
protected int x;
int y;
public int z;
public int getW() {
return this.w;
}
public void setValues() {
this.w = i;
this.x = j;
this.y = k;
this.z = l;
}
@Override
public String toString() {
return "w=" + w + ":x=" + x + ":y=" + y + ":z=" + z;
}
public String getName() {
return name;
}
}
//local inner class
public void print(String initial) {
//local inner class inside the method
class Logger {
String name;
public Logger(String name) {
this.name = name;
}
public void log(String str) {
System.out.println(this.name + ": " + str);
}
}
Logger logger = new Logger(initial);
logger.log(name);
logger.log("" + this.i);
logger.log("" + this.j);
logger.log("" + this.k);
logger.log("" + this.l);
}
//anonymous inner class
public String[] getFilesInDir(String dir, final String ext) {
File file = new File(dir);
//anonymous inner class implementing FilenameFilter interface
String[] filesList = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(ext);
}
});
return filesList;
}
}
Dưới đây là chương trình thử nghiệm cho thấy cách khởi tạo và sử dụng inner class trong Java: InnerClassTest.java
package com.journaldev.nested;
import java.util.Arrays;
//nested classes can be used in import for easy instantiation
import com.journaldev.nested.OuterClass.InnerClass;
import com.journaldev.nested.OuterClass.StaticNestedClass;
public class InnerClassTest {
public static void main(String[] args) {
OuterClass outer = new OuterClass(1,2,3,4);
//static nested classes example
StaticNestedClass staticNestedClass = new StaticNestedClass();
StaticNestedClass staticNestedClass1 = new StaticNestedClass();
System.out.println(staticNestedClass.getName());
staticNestedClass.d=10;
System.out.println(staticNestedClass.d);
System.out.println(staticNestedClass1.d);
//inner class example
InnerClass innerClass = outer.new InnerClass();
System.out.println(innerClass.getName());
System.out.println(innerClass);
innerClass.setValues();
System.out.println(innerClass);
//calling method using local inner class
outer.print("Outer");
//calling method using anonymous inner class
System.out.println(Arrays.toString(outer.getFilesInDir("src/com/journaldev/nested", ".java")));
System.out.println(Arrays.toString(outer.getFilesInDir("bin/com/journaldev/nested", ".class")));
}
}
Dưới đây là kết quả đầu ra của chương trình ví dụ về Java inner class ở trên.
OuterClass
10
0
OuterClass
w=0:x=0:y=0:z=0
w=1:x=2:y=3:z=4
Outer: OuterClass
Outer: 1
Outer: 2
Outer: 3
Outer: 4
[NestedClassTest.java, OuterClass.java]
[NestedClassTest.class, OuterClass$1.class, OuterClass$1Logger.class, OuterClass$InnerClass.class, OuterClass$StaticNestedClass.class, OuterClass.class]
Lưu ý rằng khi biên dịch OuterClass, các file class riêng biệt sẽ được tạo ra cho inner class, local inner class và static nested class.
Lợi ích của Java inner class
- Nếu một lớp chỉ hữu ích đối với duy nhất một lớp khác, thì việc lồng nó bên trong và giữ chúng cùng nhau là hợp lý. Điều này giúp tổ chức đóng gói mã nguồn một cách gọn gàng hơn.
- Java inner class hỗ trợ tính đóng gói (encapsulation). Lưu ý rằng inner class có thể truy cập các thành viên private của lớp bên ngoài, đồng thời chúng ta cũng có thể ẩn inner class khỏi thế giới bên ngoài.
- Việc giữ các lớp nhỏ nằm bên trong lớp cấp cao giúp mã nguồn gần gũi hơn với nơi nó được sử dụng, từ đó làm cho mã dễ đọc hơn và dễ bảo trì hơn.
Vậy là chúng ta đã tìm hiểu xong về Java inner class.