2013-03-30 19 views
100

Trong Java, Tôi vừa phát hiện ra rằng đoạn mã sau là hợp pháp:`someObject.new` làm gì trong Java?

KnockKnockServer newServer = new KnockKnockServer();      
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket); 

FYI, nhận chỉ là một lớp helper với chữ ký sau đây:

public class receiver extends Thread { /* code_inside */ } 

Tôi chưa bao giờ nhìn thấy XYZ.new ký hiệu trước đây. Nó hoạt động như thế nào? Có cách nào để mã hóa thông thường hơn không?

+7

cho bạn tham khảo, [lớp bên trong] (http://docs.oracle.com/javase /tutorial/java/javaOO/nested.html). –

+1

Ngoài ra, tôi đã tin rằng 'new' là một toán tử trong nhiều ngôn ngữ. (Tôi nghĩ rằng bạn cũng có thể quá tải 'mới' trong C + +?) Lớp bên trong của Java là một chút lạ cho tôi, mặc dù. –

+5

Không có câu hỏi ngớ ngẩn nào trên StackOverflow! –

Trả lời

120

Đó là cách để khởi tạo lớp bên trong không tĩnh từ bên ngoài phần thân của lớp chứa, như được mô tả trong Oracle docs.

Mỗi thể hiện lớp bên trong được liên kết với một thể hiện của lớp chứa của nó. Khi bạn new một lớp bên trong từ trong lớp chứa nó nó sử dụng this thể hiện của container theo mặc định:

public class Foo { 
    int val; 
    public Foo(int v) { val = v; } 

    class Bar { 
    public void printVal() { 
     // this is the val belonging to our containing instance 
     System.out.println(val); 
    } 
    } 

    public Bar createBar() { 
    return new Bar(); // equivalent of this.new Bar() 
    } 
} 

Nhưng nếu bạn muốn tạo một thể hiện của Bar bên ngoài Foo, hoặc kết hợp một đối tượng mới với một cá thể chứa khác hơn this thì bạn phải sử dụng ký hiệu tiền tố.

Foo f = new Foo(5); 
Foo.Bar b = f.new Bar(); 
b.printVal(); // prints 5 
+0

Ah Hiện tại, bạn đang dọn dẹp - Cảm ơn rất nhiều Ian! – Coffee

+18

Và, như bạn có thể nói, điều này có thể vô cùng khó hiểu. Lý tưởng nhất, các lớp bên trong nên được thực hiện chi tiết của lớp bên ngoài và không được tiếp xúc với thế giới bên ngoài. –

+10

@EricJablow thực sự, đó là một trong những bit cú pháp mà phải tồn tại để giữ cho spec nhất quán nhưng 99,9999% thời gian bạn không thực sự cần phải sử dụng nó. Nếu người ngoài thực sự cần tạo ra các cá thể Bar thì tôi sẽ cung cấp phương thức factory trên Foo thay vì cho họ sử dụng 'f.new'. –

4

Nghĩ về new receiver là một mã thông báo duy nhất. Loại giống như một tên chức năng với một không gian trong đó.

Tất nhiên, lớp KnockKnockServer không theo nghĩa đen có hàm new receiver, nhưng tôi đoán cú pháp có nghĩa là đề xuất điều đó. Nó có nghĩa là để trông giống như bạn đang gọi một chức năng mà tạo ra một trường hợp mới của KnockKnockServer.receiver bằng cách sử dụng một trường hợp cụ thể của KnockKnockServer cho bất kỳ truy cập vào các lớp bao quanh.

+0

Cảm ơn, vâng - nó giúp tôi bây giờ nghĩ về 'người nhận mới' là một mã thông báo! cảm ơn rất nhiều! – Coffee

18

Hãy nhìn vào ví dụ này:

public class Test { 

    class TestInner{ 

    } 

    public TestInner method(){ 
     return new TestInner(); 
    } 

    public static void main(String[] args) throws Exception{ 
     Test t = new Test(); 
     Test.TestInner ti = t.new TestInner(); 
    } 
} 

Sử dụng javap chúng ta có thể xem hướng dẫn tạo ra cho mã này

phương pháp chính:

public static void main(java.lang.String[]) throws java.lang.Exception; 
    Code: 
    0: new  #2; //class Test 
    3: dup 
    4: invokespecial #3; //Method "<init>":()V 
    7: astore_1 
    8: new  #4; //class Test$TestInner 
    11: dup 
    12: aload_1 
    13: dup 
    14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class; 
    17: pop 
    18: invokespecial #6; //Method Test$TestInner."<init>":(LTest;)V 
    21: astore_2 
    22: return 
} 

Inner class constructor:

Test$TestInner(Test); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: putfield  #1; //Field this$0:LTest; 
    5: aload_0 
    6: invokespecial #2; //Method java/lang/Object."<init>":()V 
    9: return 

} 

Mọi thứ đều đơn giản - khi gọi hàm tạo TestInner, java chuyển thể hiện Kiểm thử làm đối số đầu tiên chính: 12. Không nhìn vào TestInner đó nên có một constructor không có đối số. TestInner trong lượt của nó chỉ lưu tham chiếu đến đối tượng cha, Kiểm tra $ TestInner: 2. Khi bạn gọi hàm tạo lớp bên trong từ một phương thức thể hiện, tham chiếu đến đối tượng cha được tự động chuyển đi, do đó bạn không phải chỉ định nó. Trên thực tế nó vượt qua mọi thời gian, nhưng khi gọi từ bên ngoài nó phải được thông qua một cách rõ ràng.

t.new TestInner(); - chỉ là một cách để xác định các tham số ẩn đầu tiên TestInner constructor, không phải là một loại

() phương pháp bằng:

public TestInner method(){ 
    return this.new TestInner(); 
} 

TestInner bằng:

class TestInner{ 
    private Test this$0; 

    TestInner(Test parent){ 
     this.this$0 = parent; 
    } 
} 
+1

Cảm ơn bạn rất nhiều! – Coffee

7

Khi lớp bên trong được thêm vào Java trong ver sion 1.1 của ngôn ngữ ban đầu được định nghĩa là một biến đổi thành mã tương thích 1.0. Nếu bạn xem xét một ví dụ về sự biến đổi này, tôi nghĩ nó sẽ làm cho nó rõ ràng hơn rất nhiều về cách một lớp bên trong thực sự hoạt động.

xem xét mã từ Ian Roberts' câu trả lời:

public class Foo { 
    int val; 
    public Foo(int v) { val = v; } 

    class Bar { 
    public void printVal() { 
     System.out.println(val); 
    } 
    } 

    public Bar createBar() { 
    return new Bar(); 
    } 
} 

Khi chuyển đến 1.0 mã tương thích, mà bên trong lớp Bar sẽ trở thành một cái gì đó như thế này:

class Foo$Bar { 
    private Foo this$0; 

    Foo$Bar(Foo outerThis) { 
    this.this$0 = outerThis; 
    } 

    public void printVal() { 
    System.out.println(this$0.val); 
    } 
} 

Tên lớp bên trong là tiền tố với tên lớp bên ngoài để làm cho nó độc đáo. Thành viên riêng tư this$0 bị ẩn được thêm vào giữ bản sao của bên ngoài this. Và một hàm tạo ẩn được tạo ra để khởi tạo thành viên đó.

Và nếu bạn nhìn vào các phương pháp createBar, nó sẽ được chuyển đổi thành một cái gì đó như thế này:

public Foo$Bar createBar() { 
    return new Foo$Bar(this); 
} 

Vì vậy, chúng ta hãy xem những gì sẽ xảy ra khi bạn thực hiện đoạn mã sau.

Foo f = new Foo(5); 
Foo.Bar b = f.createBar();        
b.printVal(); 

tiên chúng ta khởi tạo một thể hiện của Foo và intialise các val thành viên để 5 (tức là f.val = 5).

Tiếp theo chúng ta gọi f.createBar(), mà instantiates một thể hiện của Foo$Bar và khởi sự this$0 thành viên với giá trị của this trôi qua từ createBar (ví dụ: b.this$0 = f).

Cuối cùng chúng ta gọi là b.printVal() mà cố gắng để in b.this$0.val đó là f.val đó là 5.

Bây giờ đó là một instantiation thường xuyên của một lớp bên trong. Hãy xem điều gì sẽ xảy ra khi khởi tạo Bar từ bên ngoài Foo.

Foo f = new Foo(5); 
Foo.Bar b = f.new Bar(); 
b.printVal(); 

Áp dụng chuyển đổi 1.0 của chúng tôi một lần nữa, rằng dòng thứ hai sẽ trở thành một cái gì đó như thế này:

Foo$Bar b = new Foo$Bar(f); 

này là gần như giống với f.createBar() gọi. Một lần nữa, chúng tôi đang tạo một thể hiện của Foo$Bar và khởi tạo thành viên this$0 thành f. Vì vậy, một lần nữa, b.this$0 = f.

Và một lần nữa khi bạn gọi b.printVal(), bạn đang in b.thi$0.val đó là f.val đó là 5.

Điều quan trọng cần nhớ là các lớp bên trong có thành viên ẩn giữ một bản sao của this từ lớp bên ngoài. Khi bạn khởi tạo một lớp bên trong từ bên trong lớp bên ngoài, nó được khởi tạo ngầm với giá trị hiện tại là this. Khi bạn khởi tạo lớp bên trong từ bên ngoài lớp bên ngoài, bạn chỉ định rõ ràng thể hiện của lớp bên ngoài để sử dụng, thông qua tiền tố trên từ khóa new.

1

Shadowing

Nếu tuyên bố của một loại (chẳng hạn như một biến thành viên hoặc một tên tham số) trong một phạm vi cụ thể (chẳng hạn như một lớp bên trong hoặc một định nghĩa phương pháp) có tên giống như một tuyên bố khác trong phạm vi kèm theo, sau đó khai báo đổ bóng việc khai báo phạm vi kèm theo. Bạn không thể tham khảo một tuyên bố bóng tối bởi tên của nó một mình. Ví dụ sau đây, ShadowTest, chứng tỏ điều này:

public class ShadowTest { 

    public int x = 0; 

    class FirstLevel { 

     public int x = 1; 

     void methodInFirstLevel(int x) { 
      System.out.println("x = " + x); 
      System.out.println("this.x = " + this.x); 
      System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 
     } 
    } 

    public static void main(String... args) { 
     ShadowTest st = new ShadowTest(); 
     ShadowTest.FirstLevel fl = st.new FirstLevel(); 
     fl.methodInFirstLevel(23); 
    } 
} 

Sau đây là sản phẩm của ví dụ này:

x = 23 
this.x = 1 
ShadowTest.this.x = 0 

Ví dụ này xác định ba biến tên là x: Biến thành viên của ShadowTest lớp, biến thành viên của lớp bên trong FirstLevel và tham số trong phương thức methodInFirstLevel. Biến x được định nghĩa như một tham số của phương thức methodInFirstLevel đổ bóng biến của lớp bên trong FirstLevel. Do đó, khi bạn sử dụng biến x trong phương thức methodInFirstLevel, nó tham chiếu đến tham số phương thức. Để tham khảo các biến thành viên của lớp FirstLevel bên trong, sử dụng từ khóa này để đại diện cho phạm vi kèm theo:

System.out.println("this.x = " + this.x); 

Tham khảo các biến thành viên đó gửi kèm theo phạm vi lớn hơn bởi tên lớp mà họ thuộc về. Ví dụ, tuyên bố sau truy cập các biến thành viên của lớp ShadowTest từ phương pháp methodInFirstLevel:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 

Refer to the docs