Lớp lồng ghép trong Java - Lớp lồng ghép không tĩnh và lớp lồng ghép tĩnh

Lớp có thể được định nghĩa bên trong một lớp khác, những lớp như vậy được gọi là lớp lồng ghép (nested class), lớp chứa lớp lồng ghép được gọi là lớp bên ngoài (outer class) hoặc còn gọi là lớp đóng gói (enclosing class).

Lớp lồng ghép được chia thành hai loại:

  • Lớp lồng ghép tĩnh (Static Nested Classes): được khai báo bằng từ khóa static, thường được gọi là lớp lồng ghép (Nested Classes);
  • Lớp lồng ghép không tĩnh (Non-static Nested Classes): không sử dụng từ khóa static, thường được gọi là lớp nội tại (Inner Classes);

Lớp lồng ghép là thành viên của lớp bên ngoài. Lớp nội tại (lớp lồng ghép không tĩnh) có thể truy cập các thành viên khác của lớp bên ngoài, ngay cả khi thành viên đó là private, trong khi lớp lồng ghép tĩnh chỉ có thể truy cập các thành viên tĩnh của lớp bên ngoài.

Là một thành viên của lớp bên ngoài, lớp lồng ghép có thể được khai báo với các mức truy cập: private, package access, protected, public (chú ý: lớp bên ngoài chỉ có thể được khai báo là public hoặc package scope).

Các lợi ích chính khi sử dụng lớp lồng ghép bao gồm:

  • Lớp lồng ghép có thể truy cập các trường dữ liệu và phương thức của lớp bên ngoài, ngay cả khi chúng là private;
  • Tăng tính đọc hiểu và dễ bảo trì: vì nếu một lớp chỉ dùng cho một lớp khác, việc đặt chúng cùng nhau sẽ thuận tiện hơn cho việc hiểu và bảo trì;
  • Tăng tính đóng gói: cho hai lớp A và B, nếu B cần truy cập các thành viên private của A, ta có thể đặt lớp B vào bên trong lớp A, như vậy không chỉ cho phép B truy cập các thành viên private của A mà còn ẩn lớp B khỏi bên ngoài;
  • Giảm lượng mã nguồn;

Lớp lồng ghép tĩnh có thể được truy cập thông qua tên của lớp bên ngoài. Ví dụ:

OuterClass.StaticNestedClass

Nếu muốn tạo đối tượng cho lớp lồng ghép tĩnh, sử dụng cú pháp sau:

OuterClass.StaticNestedClass nestedObject =
        new OuterClass.StaticNestedClass();

Lớp lồng ghép không tĩnh có ba loại:

  • Lớp nội tại thành viên (Member inner class);
  • Lớp nội tại cục bộ (Local inner class);
  • Lớp nội tại vô danh (Anonymous inner class);
Mô tả
Lớp nội tại thành viên Lớp được tạo ra bên trong lớp và ở bên ngoài phương thức
Lớp nội tại vô danh Lớp được tạo ra để triển khai interface hoặc mở rộng lớp, tên của nó do trình biên dịch quyết định
Lớp nội tại cục bộ Lớp được tạo ra bên trong phương thức
Lớp lồng ghép tĩnh Lớp tĩnh được tạo ra bên trong lớp
Giao diện lồng ghép Interface được tạo ra bên trong lớp hoặc interface

Một Lớp lồng ghép không tĩnh (Lớp nội tại)

Lớp lồng ghép không tĩnh cũng chính là lớp nội tại, nó có những đặc điểm sau:

  • Phải khởi tạo lớp bên ngoài trước khi khởi tạo lớp nội tại;
  • Đối tượng lớp nội tại liên kết với đối tượng lớp bên ngoài (lớp nội tại có thể truy cập các thành viên của lớp bên ngoài), nên không thể định nghĩa bất kỳ thành viên tĩnh nào trong lớp nội tại;
  • Lớp nội tại là không tĩnh;

1. Lớp nội tại thành viên

Ví dụ một

Lớp lồng ghép không tĩnh được tạo ra bên trong lớp bên ngoài và ở bên ngoài phương thức, được gọi là lớp nội tại thành viên. Nói đơn giản, lớp nội tại thành viên chỉ là một thành viên không tĩnh của lớp bên ngoài. Ví dụ:

public class Package1 {
    class Content{
        private int value = 11;
        public int getValue() {return value;}
    }
    class Destination{
        private String address;
        Destination(String destination){
            address = destination;
        }		
        String getAddress() {return address;}
    }	
    public void send(String target) {
        Content c = new Content();
        Destination d = new Destination(target);
        System.out.println(d.getAddress());
    }	
    public static void main(String[] args) {
        Package1 pkg = new Package1();
        pkg.send("Zhengy");
    }
}

Trong phương thức send(), việc sử dụng lớp nội tại thành viên không có gì khác biệt so với sử dụng lớp bình thường, ở đây sự khác biệt thực tế là lớp nội tại được định nghĩa lồng ghép trong Package1.

Ví dụ hai

Chúng ta thử định nghĩa một phương thức trong lớp bên ngoài, phương thức này trả về một tham chiếu đến lớp nội tại thành viên.

public class Package2 {
    class Content{
        private int value = 11;
        public int getValue() {return value;}
    }	
    class Destination{
        private String address;
        Destination(String destination){
            address = destination;
        }		
        String getAddress() {return address;}
    }		
    public Destination goTo(String location) {
        return new Destination(location);
    }	
    public Content content() {
        return new Content();
    }		
    public void send(String target) {
        Content c = new Content();
        Destination d = new Destination(target);
        System.out.println(d.getAddress());
    }	
    public static void main(String[] args) {
        Package2 pkg = new Package2();
        pkg.send("Nam Kinh");		
        Package2 pkg2 = new Package2();
        Package2.Destination dest = pkg2.goTo("Bắc Kinh");
        Package2.Content cont = pkg2.content();
    }
}

Nếu muốn tạo đối tượng của lớp nội tại thành viên từ bên ngoài lớp, phải chỉ rõ kiểu của đối tượng như trong phương thức main(): OuterClassName.InnerClassName.

Ví dụ ba

Khi tạo một đối tượng của lớp nội tại thành viên, đối tượng này sẽ có mối liên hệ với đối tượng lớp bên ngoài tạo ra nó, nên nó có thể truy cập tất cả các thành viên của đối tượng lớp bên ngoài mà không cần điều kiện đặc biệt nào. Ví dụ sau minh họa điều này:

//Giống như iterator
interface Iterator{
    boolean isEnd();    //Kiểm tra xem chuỗi đã lặp hết chưa
    Object getCurrent();  //Lấy phần tử hiện tại
    void moveNext();       //Di chuyển đến phần tử tiếp theo
}
//Lớp chuỗi (lớp bên ngoài)
public class Series {
    //Mảng để lưu trữ phần tử
    private Object[] elements;	
    //Chỉ số thêm tiếp theo
    private int currentIndex = 0;
    public Series(int size) {elements = new Object[size];}	
    //Thêm phần tử vào mảng
    public void append(Object item) {
        if(currentIndex < elements.length)
            elements[currentIndex++] = item;
    }		
    //Lớp nội tại thành viên: iterator (có thể truy cập tất cả các thành viên của lớp bên ngoài), dùng để lặp qua tất cả phần tử trong chuỗi
    private class SeriesIterator implements Iterator{
        private int position = 0;
        public boolean isEnd() {return position == elements.length;}
        public Object getCurrent() {return elements[position];}
        public void moveNext() {
            if(position < elements.length)
                position++;
        }
    }	
    //Tạo đối tượng iterator
    public Iterator iterator() {
        return new SeriesIterator();
    }	
    public static void main(String[] args) {
        Series series = new Series(10);
        for(int i=0;i<10;i++) {
            series.append(Integer.toString(i));
        }
        
        Iterator iter = series.iterator();
        while(!iter.isEnd()) {
            System.out.print(iter.getCurrent() + "  ");
            iter.moveNext();
        }		
    }
}

Kết quả đầu ra như sau:

0  1  2  3  4  5  6  7  8  9  

Ví dụ trên minh họa mẫu thiết kế "iterator".

Ví dụ bốn

Trong lớp nội tại thành viên, nếu cần tạo một tham chiếu đến đối tượng lớp bên ngoài, có thể sử dụng tên lớp bên ngoài theo sau bởi .this. Ví dụ sau minh họa cách sử dụng .this:

//Sử dụng .this
public class ThisExample {
    void execute() {
        System.out.println("ThisExample.execute()");
    }	
    //Lớp nội tại thành viên
    public class Inner{
        public ThisExample outer() {
            return ThisExample.this;
        }
    }	
    public Inner inner() {
        return new Inner();
    }	
    public static void main(String[] args) {
        ThisExample te = new ThisExample();
        ThisExample.Inner tie = te.inner();
        tie.outer().execute();
    }
}

Kết quả đầu ra như sau:

ThisExample.execute()

Ví dụ năm

Muốn tạo trực tiếp đối tượng lớp nội tại thành viên, không thể trực tiếp tham chiếu tên lớp bên ngoài, mà phải sử dụng đối tượng lớp bên ngoài để tạo đối tượng lớp nội tại. Trước khi có đối tượng lớp bên ngoài, không thể tạo được đối tượng lớp nội tại thành viên, bởi vì đối tượng lớp nội tại thành viên sẽ tự động liên kết với đối tượng lớp bên ngoài tạo ra nó.

Tuy nhiên, nếu tạo lớp nội tại tĩnh, thì nó không cần tham chiếu đến đối tượng lớp bên ngoài.

//.new
public class NewExample {
    public class Inner{
        public void test(){
            System.out.print("test");
        }
    }
    public static void main(String[] args) {
        NewExample ne = new NewExample();
        NewExample.Inner nie = ne.new Inner();	
        nie.test();
    }
}

Ví dụ sáu

Khi nâng cấp lớp nội tại thành viên (được đánh dấu private) lên lớp cha của nó, đặc biệt là chuyển đổi thành một interface (public hoặc "package access"), do lớp nội tại thành viên sử dụng private để đánh dấu, nên có thể ẩn chi tiết thực hiện cụ thể, nhưng do lớp nội tại thành viên được khai báo là private, nên không thể truy cập bất kỳ phương thức mới nào vốn không thuộc giao diện công cộng, do đó mở rộng interface là vô nghĩa. Dưới đây là một ví dụ:

interface Destination{
    String readAddress();
}
interface Content{
    int getValue();
}
class Package4{
    //Lớp private, ẩn chi tiết thực hiện
    private class PackageContent implements Content{
        private int value = 11;
        public int getValue() {return value;}		
    }		
    protected class PackageDestination implements Destination{
        private String address;
        private PackageDestination(String destination) {
            address = destination;
        }		
        public String readAddress(){
            return address;
        }				
    }
    public Destination destination(String location) {
        return new PackageDestination(location);
    }	
    public Content content(){
        return new PackageContent();
    }
}
public class TestPackage {
    public static void main(String[] args) {
        Package4 pkg = new Package4();
        Content c = pkg.content();
        Destination d = pkg.destination("Bắc Kinh");
    }
}

Ví dụ bảy

Việc một lớp nội tại thành viên được lồng ghép bao nhiêu lớp không quan trọng, nó có thể truy cập minh bạch tất cả các thành viên của tất cả lớp bên ngoài mà nó được lồng vào, như sau:

class ComplexStructure{
    private void method() {}
    //Lớp nội tại thứ nhất
    class FirstLevel{
        private void subMethod() {}
        //Lớp nội tại thứ hai
        public class SecondLevel{
            void execute() {
                subMethod();
                method();
            }
        }
    }
}
public class MultiLevelAccess {
    public static void main(String[] args) {
        ComplexStructure cs = new ComplexStructure();
        ComplexStructure.FirstLevel fl = cs.new FirstLevel();
        ComplexStructure.FirstLevel.SecondLevel sl = fl.new SecondLevel();
    }
}

Cơ chế hoạt động của lớp nội tại thành viên:

Java tạo ra một tệp .class cho mỗi lớp, vì vậy trình biên dịch Java còn tạo thêm một lớp nội tại thành viên OuterClass$MemberInnerClass.class, định dạng tên tệp lớp nội tại thành viên là tên_lớp_ngoài$tên_lớp_nội_tại_thành_viên.

2. Lớp nội tại cục bộ

Lớp nội tại cục bộ (Local inner class) thường được định nghĩa bên trong một khối lệnh. Vì vậy bạn thường tìm thấy lớp nội tại cục bộ trong một phương thức.

Giống như biến cục bộ, phạm vi của lớp nội tại cục bộ bị giới hạn bởi phương thức. Nó có thể truy cập tất cả các thành viên của lớp bên ngoài, và tất cả các biến cục bộ trong phương thức nơi nó nằm.

Nếu bạn muốn gọi phương thức trong lớp nội tại cục bộ, bạn phải khởi tạo lớp nội tại cục bộ trong phương thức cục bộ trước.

Một số quy tắc của lớp nội tại cục bộ:

  • Không thể gọi lớp nội tại cục bộ từ bên ngoài phương thức;
  • Lớp nội tại cục bộ không thể được khai báo là private, public, protected;
  • Trong JDK1.7 trở xuống, lớp nội tại cục bộ không thể truy cập biến cục bộ không phải final, nhưng từ JDK1.8 trở đi có thể truy cập biến cục bộ không phải final;

Ví dụ một

Định nghĩa một lớp nội tại cục bộ trong một phương thức:

public class Package5 {
    public Destination destination(String location) {
        class LocalDestination implements Destination{
            private String address;
            private LocalDestination(String destination) {
                address = destination;
            }			
            public String readAddress(){
                return address;
            }
        }
        return new LocalDestination(location);
    } 	
    public static void main(String[] args) {
        Package5 pkg = new Package5();
        Destination d = pkg.destination("Bắc Kinh");
    } 
}

Ví dụ hai

Chúng ta thử định nghĩa một lớp nội tại cục bộ trong một phạm vi:

public class Package6 {
    private void trackingInternal(boolean flag) {
        if(flag) {
            class TrackingInfo{
                private String id;
                TrackingInfo(String identifier){
                    id = identifier;
                }
                String getId() {
                    return id;
                }
            }
            TrackingInfo ti = new TrackingInfo("info");
            String s = ti.getId();
        }
    }	
    public void track() {
        trackingInternal(true);
    }	
    public static void main(String[] args) {
        Package6 pkg = new Package6();
        pkg.track();
    }
}

Lớp TrackingInfo được lồng trong phạm vi câu lệnh if, điều này không có nghĩa là việc tạo lớp có điều kiện, nó thực tế được biên dịch cùng với các lớp khác. Tuy nhiên, bên ngoài phạm vi định nghĩa TrackingInfo, nó không khả dụng, ngoài điều đó ra, nó giống như lớp bình thường.

Cơ chế hoạt động của lớp nội tại cục bộ:

Java tạo ra một tệp .class cho mỗi lớp, vì vậy trình biên dịch Java còn tạo thêm một tệp có tên localInner$Local.class.

3. Lớp nội tại vô danh

Trong Java, lớp nội tại không có tên được gọi là lớp nội tại vô danh, nó được sử dụng khi chúng ta cần ghi đè phương thức trong lớp hoặc interface. Khi sử dụng lớp nội tại vô danh, việc khai báo và khởi tạo sẽ diễn ra đồng thời.

Lớp vô danh có thể được tạo theo hai cách sau:

  • Lớp (có thể là lớp trừu tượng hoặc lớp khác);
  • Interface;

Ví dụ một

Dưới đây sẽ tạo một lớp vô danh, lớp này là lớp triển khai interface Content:

//Lớp nội tại vô danh

public class Package7 {
    //
    public Content content() {
        //Trả về một đối tượng lớp vô danh, lớp này là triển khai của interface Content
        return new Content() {
            private int value = 11;
            public int getValue() {
                return value;
            }
        };		
    }
    
    public static void main(String[] args) {
        Package7 pkg = new Package7();
        Content c = pkg.content();
    }

}

Tham chiếu được trả về bởi biểu thức new được tự động nâng cấp lên tham chiếu kiểu Content. Cú pháp lớp nội tại vô danh trong đoạn mã trên là hình thức rút gọn của đoạn mã sau:

//Lớp nội tại vô danh

public class Package7 {
    
//	public Content content() {
//		//Trả về một đối tượng lớp vô danh, lớp này là triển khai của interface Content
//		return new Content() {
//			private int value = 11;
//			public int getValue() {
//				return value;
//			}
//		};		
//	}
	
    class PackageContent implements Content{
        private int value = 11;
        public int getValue() {
            return value;
        }
    }	
    public Content content() {
        return new PackageContent();
    }		
    public static void main(String[] args) {
        Package7 pkg = new Package7();
        Content c = pkg.content();
    }
}

Ví dụ hai

Trong ví dụ một về lớp nội tại vô danh, sử dụng hàm tạo mặc định, nhưng nếu lớp cơ sở cần hàm tạo có tham số, chúng ta chỉ cần truyền tham số phù hợp cho hàm tạo lớp cơ sở, dưới đây là một ví dụ:

class Wrapper{
    private int value;
    //Cần một tham số
    public Wrapper(int x) {
        value = x;
        System.out.println("Hàm tạo lớp cơ sở:" + x);
    }	
    public int getValue() {
        return value;
    }
}
public class Package8 {
    public Wrapper wrapper(int x) {
        //Một lớp vô danh triển khai lớp Wrapper, và truyền x cho hàm tạo lớp cơ sở
        return new Wrapper(x) {
            public int getValue() {
                return super.getValue() * 47;
            }
        };
    }	
    public static void main(String[] args) {
        Package8 pkg = new Package8();
        Wrapper w = pkg.wrapper(10);
    }
}

Wrapper là một lớp có triển khai cụ thể. Chúng ta truyền x vào new Wrapper(x), giá trị x cuối cùng sẽ được truyền cho hàm tạo của lớp cơ sở Wrapper.

Ví dụ ba

Nếu định nghĩa trường trong lớp vô danh, còn có thể thực hiện khởi tạo cho chúng, như sau:

//Khởi tạo trường trong lớp vô danh
public class Package9 {
    //destination dùng để khởi tạo trường lớp vô danh, phải khai báo là final
    public Destination destination(final String destination) {
        return new Destination() {
            private String address = destination;
            public String readAddress(){
                return address;
            }
        };
    }	
    public static void main(String[] args) {
        Package9 pkg = new Package9();
        Destination d = pkg.destination("Bắc Kinh");
    }	

Nếu định nghĩa một lớp nội tại vô danh và mong muốn sử dụng một đối tượng được định nghĩa bên ngoài nó, trình biên dịch sẽ yêu cầu tham chiếu tham số phải là final, giống như ví dụ trên.

Ví dụ bốn

Nếu chỉ muốn gán giá trị cho một trường trong lớp vô danh, ví dụ ba là tác dụng tốt. Nhưng nếu muốn làm hành vi giống như hàm tạo thì sao? Tuy nhiên, hàm vô danh không thể có hàm tạo có tên, nhưng có thể thông qua khởi tạo thể hiện để đạt được hiệu ứng tạo hàm tạo cho lớp nội tại vô danh, như sau:

//Thông qua khởi tạo thể hiện, đạt được hiệu ứng tạo hàm tạo cho lớp nội tại vô danh

abstract class BaseClass{
    public BaseClass(int i) {
        System.out.println("Hàm tạo Base, i = " + i);
    }
    public abstract void execute(); 
} 
public class AnonymousConstructorExample {		
    //price phải là final, vì nó được sử dụng trực tiếp trong lớp vô danh
    public static BaseClass getBaseClass(int i, final float price) {
        //i được truyền cho hàm tạo lớp cơ sở của lớp vô danh, không yêu cầu là final, vì nó không được sử dụng trực tiếp trong lớp vô danh
        return new BaseClass(i) {		
            private int cost;
            //Khởi tạo thể hiện lớp nội tại
            {
                cost = Math.round(price); 
                System.out.println("Trong khởi tạo thể hiện: " + cost);				
            }
            public void execute() {
                System.out.println("Trong execute vô danh()");
            }			
        };
    }	
    public static void main(String[] args) {
        BaseClass base = getBaseClass(47, 101.395F);
        base.execute();
    }
}

Kết quả đầu ra như sau:

Base constructor, i = 47
Inside instance initializer:101
In anonymous f()

Trong ví dụ trên, i không yêu cầu là final, vì i được truyền cho hàm tạo lớp cơ sở của lớp vô danh, nó sẽ không được sử dụng trực tiếp trong lớp vô danh. Trong khi đó price phải là final, vì nó được sử dụng trực tiếp trong lớp vô danh.

So với kế thừa thông thường, lớp nội tại vô danh có một số hạn chế, lớp nội tại vô danh có thể mở rộng lớp hoặc mở rộng interface, nhưng không thể làm cả hai. Hơn nữa, nếu triển khai interface, chỉ có thể triển khai một interface duy nhất.

Ví dụ năm

Chúng ta hãy xem một ví dụ phương pháp nhà máy:

//Phương pháp nhà máy

interface Service{
    void operation1();
    void operation2();
}
interface ServiceFactory{
    Service getService();
}
class Implementation1 implements Service{
    private Implementation1(){}
    public void operation1() {
        System.out.println("Implementation1 operation1");
    }	
    public void operation2() {
        System.out.println("Implementation1 operation2");
    }	
    public static ServiceFactory factory = new ServiceFactory() {
        public Service getService() {
            return new Implementation1();
        }
    } ;
}
class Implementation2 implements Service{
    private Implementation2(){}
    public void operation1() {
        System.out.println("Implementation2 operation1");
    }	
    public void operation2() {
        System.out.println("Implementation2 operation2");
    }	
    public static ServiceFactory factory = new ServiceFactory() {
        public Service getService() {
            return new Implementation2();
        }
    } ;
}
public class FactoryExample {
    public static void serviceConsumer(ServiceFactory factory) {
        Service s = factory.getService();
        s.operation1();
        s.operation2();
    }	
    public static void main(String[] args) {
        serviceConsumer(Implementation1.factory);
        serviceConsumer(Implementation2.factory);		
    }
}

Trong ví dụ trên, các hàm tạo của Implementation1 và Implementation2 đều là private, và lớp nhà máy được viết dưới dạng lớp nội tại vô danh. Ngoài ra, trong thực tế, thường chỉ cần một đối tượng nhà máy duy nhất, do đó factory được khai báo là static, điều này cho thấy một lớp nhiều nhất chỉ tạo một thể hiện duy nhất.

Hai Kế thừa lớp nội tại thành viên

1. Kế thừa lớp nội tại thành viên

Trước đây chúng ta đã giới thiệu rằng lớp nội tại thành viên có thể sử dụng các thành viên của lớp bên ngoài, bởi vì khi một đối tượng lớp bên ngoài tạo một đối tượng lớp nội tại thành viên, đối tượng lớp nội tại thành viên sẽ bí mật nắm giữ một tham chiếu đến đối tượng lớp bên ngoài, khi truy cập các thành viên lớp bên ngoài, sẽ sử dụng tham chiếu đó để chọn thành viên lớp bên ngoài (trình biên dịch sẽ xử lý chi tiết này cho chúng ta).

Khi xây dựng đối tượng lớp nội tại thành viên, cần một tham chiếu đến đối tượng lớp bên ngoài của nó, nếu trình biên dịch không thể truy cập tham chiếu này sẽ báo lỗi. Do đó, nếu có một lớp con kế thừa từ lớp nội tại thành viên, khi tạo một đối tượng lớp con, trước tiên sẽ gọi hàm tạo lớp nội tại thành viên để tạo một đối tượng lớp nội tại thành viên, nhưng đối tượng lớp nội tại thành viên này không tồn tại tham chiếu đến đối tượng lớp bên ngoài của nó, sẽ báo lỗi. Để giải quyết vấn đề này, phải truyền cho hàm tạo lớp con một tham chiếu đến đối tượng lớp bên ngoài, và trong dòng đầu tiên của hàm tạo sử dụng cú pháp sau:

enclosingClassReference.super(params) //enclosingClassReference là tham chiếu đến đối tượng lớp bên ngoài, super(params) là hàm tạo lớp nội tại thành viên

Như sau:

//Kế thừa lớp nội tại thành viên

class WithInnerClass{
    private String name = "Lâm An";
    private int age = 25;
    private boolean gender = false;
    
    WithInnerClass(boolean gender){
        this.gender = gender;
        System.out.println("Hàm tạo lớp bên ngoài: WithInnerClass()");
    }	
    //Lớp nội tại thành viên
    class InnerClass{
        //Hàm tạo khởi tạo thành viên lớp bên ngoài
        InnerClass(String name, int age){
            WithInnerClass.this.name = name;
            WithInnerClass.this.age = age;
            System.out.println("Hàm tạo lớp nội tại: InnerClass():" + WithInnerClass.this.name + " " + WithInnerClass.this.age + " " + WithInnerClass.this.gender);
        }
    }
}

//Lớp con của lớp nội tại thành viên
public class ExtendInner extends WithInnerClass.InnerClass {
    //Hàm tạo phải truyền tham chiếu đến đối tượng lớp bên ngoài
    ExtendInner(WithInnerClass wic, String name, int age){		
        //Gọi hàm tạo lớp nội tại thành viên
        wic.super(name, age);			
    }		
    public static void main(String[] args) {
        //Tạo một đối tượng lớp bên ngoài
        WithInnerClass wic = new WithInnerClass(true);
        ExtendInner ei = new ExtendInner(wic, "Trịnh Dương", 23);
    }
}

Kết quả đầu ra:

External class constructor: WithInnerClass()
Inner class constructor: InnerClass():Trịnh Dương 23 true

Ba Lớp lồng ghép tĩnh

Thực chất lớp lồng ghép tĩnh là một lớp cấp cao được đóng gói trong một lớp cấp cao khác, hành vi của nó giống như lớp cấp cao, nó được đóng gói trong lớp cấp cao chủ yếu để thuận tiện cho việc đóng gói.

  • Lớp lồng ghép tĩnh là thành viên tĩnh của lớp bên ngoài, nó có thể truy cập trực tiếp chính nó bằng TênLớpBênNgoài.LớpLồngGhépTĩnh;
  • Nó có thể truy cập các thành viên tĩnh và thành viên tĩnh riêng tư của lớp bên ngoài;
  • Giống như các lớp khác, lớp lồng ghép tĩnh không thể truy cập các thành viên không tĩnh;

Ví dụ một

Trong điều kiện bình thường, không thể đặt bất kỳ mã nào bên trong interface, nhưng lớp lồng ghép có thể là một phần của interface. Và bất kỳ kiểu nào (trường, phương thức) được đặt trong interface đều tự động là public và static. Vì lớp trong interface là static, nên nó là lớp lồng ghép tĩnh. Như sau:

//Lớp bên trong interface: tự động là public và static
public interface ClassInInterfaceExample {
    void greeting();	
    //Lớp lồng ghép tĩnh
    class ExampleTest implements ClassInInterfaceExample{
        public void greeting() {
            System.out.println("Hello!");
        }
    }	
    public static void main(String[] args) {
        new ExampleTest().greeting();
    }
}

Chúng ta định nghĩa một lớp lồng ghép tĩnh Test trong interface ClassInInterfaceExample, lớp lồng ghép tĩnh này triển khai interface bên ngoài.

Ví dụ hai

class OuterClassExample{  
    // Thành viên dữ liệu tĩnh của lớp bên ngoài
    static int data = 30;  
    // Lớp lồng ghép tĩnh trong lớp bên ngoài
    static class StaticNestedClassExample {  
        // Phương thức thể hiện trong lớp lồng ghép tĩnh
        void getData() {System.out.println("data is " + data);}  
    }
    public static void main(String args[]){  
        // Khởi tạo lớp lồng ghép tĩnh (tạo đối tượng)
        OuterClassExample.StaticNestedClassExample obj = new OuterClassExample.StaticNestedClassExample();  
        obj.getData();  
    }  
}  

Trong ví dụ trên, bạn cần tạo một thể hiện lớp lồng ghép tĩnh trước, bởi vì bạn cần truy cập phương thức thể hiện getData() của nó. Nhưng bạn không cần khởi tạo lớp bên ngoài, vì lớp lồng ghép tĩnh là tĩnh so với lớp bên ngoài, có thể truy cập trực tiếp bằng TênLớpBênNgoài.TênLớpLồngGhépTĩnh.

Ví dụ ba

Nếu lớp lồng ghép tĩnh có thành viên tĩnh, thì không cần khởi tạo lớp lồng ghép tĩnh có thể truy cập trực tiếp.

class OuterClassExample2{  
    static int data = 10;  
    static class StaticNestedClassExample{  
        // Phương thức tĩnh của lớp lồng ghép tĩnh
        static void getData() {System.out.println("data is " + data);}  
    }  
    public static void main(String args[]){  
        // Không cần tạo thể hiện có thể truy cập trực tiếp
        OuterClassExample2.StaticNestedClassExample.getData();
    }  
}  

Thẻ: Java nested-class inner-class static-nested-class anonymous-inner-class

Đăng vào ngày 6 tháng 10 lúc 14:46