2011-08-16 11 views
14

Tôi đang cố gắng tạo các phiên bản của các lớp ẩn danh bằng cách sử dụng sự phản chiếu. Nhưng tôi đã nhìn thấy hành vi kỳ lạ trong thời gian tức thời.Xây dựng năng động của sự nhầm lẫn lớp ẩn danh

Xin vui lòng, nhìn vào những mảnh vỡ tương tự của mã

public class HideAndSeek { 

    @SuppressWarnings("unchecked") 
    public static void main(String[] args) throws IllegalAccessException, InstantiationException{ 

     final String finalString = "I'm final :)"; 

     Object object2 = new Object(){ 

      { 
       System.out.println("Instance initializing block"); 
       System.out.println(finalString); 
      }   

      private void hiddenMethod() { 
       System.out.println("Use reflection to find me :)"); 
      } 
     }; 

     Object tmp = object2.getClass().newInstance(); 
    } 

} 

Mã này hoạt động tốt, và sản lượng dự kiến ​​

Instance initializing block 
I'm final :) 
Instance initializing block 
I'm final :) 

Sau này tôi đã quyết định thay đổi mã theo cách đơn giản (chỉ đã thêm java.util.Calendar)

import java.util.Calendar; 

    public class HideAndSeek { 

     @SuppressWarnings("unchecked") 
     public static void main(String[] args) throws IllegalAccessException, InstantiationException{ 

      final String finalString = "I'm final :)"; 

      final Calendar calendar = Calendar.getInstance(); 
      System.out.println(calendar.getTime().toString()); //works well 

      Object object2 = new Object(){ 

       { 
        System.out.println("Instance initializing block"); 
        System.out.println(finalString); 

        //simply added this line 
        System.out.println(calendar.getTime().toString()); 
       }   

       private void hiddenMethod() { 
        System.out.println("Use reflection to find me :)"); 
       } 
      }; 

      Object tmp = object2.getClass().newInstance(); 
     } 

    } 

Và đây là kết quả đầu ra mà tôi có:

Wed Aug 17 02:08:47 EEST 2011 
Instance initializing block 
I'm final :) 
Wed Aug 17 02:08:47 EEST 2011 
Exception in thread "main" java.lang.InstantiationException: HideAndSeek$1 
    at java.lang.Class.newInstance0(Unknown Source) 
    at java.lang.Class.newInstance(Unknown Source) 
    at HideAndSeek.main(HideAndSeek.java:29) 

Như bạn có thể thấy - trường hợp mới chưa được tạo.

Ai đó có thể giải thích cho tôi, lý do của những thay đổi đó?

Cảm ơn

+0

Điều gì xảy ra nếu Lịch instnace không kết thúc? – SJuan76

+0

@ SJuan76. ... thì bạn sẽ không thể truyền nó vào lớp bên trong vô danh. –

Trả lời

17

Đây là câu hỏi rất đơn giản với câu trả lời rất phức tạp. Hãy chịu với tôi khi tôi cố giải thích nó.

Nhìn vào mã nguồn nơi ngoại lệ xảy ra trong Class (Tôi không chắc chắn lý do tại sao stack trace của bạn không cung cấp cho các số dòng trong Class):

try 
{ 
    Class[] empty = {}; 
    final Constructor<T> c = getConstructor0(empty, Member.DECLARED); 
    // removed some code that was not relevant 
} 
catch (NoSuchMethodException e) 
{ 
    throw new InstantiationException(getName()); 
} 

bạn thấy rằng NoSuchMethodException đang được được gọi lại là InstantiationException. Điều này có nghĩa là không có một hàm tạo no-arg cho kiểu lớp là object2.

Đầu tiên, loại nào là object2? Với mã

System.out.println("object2 class: " + object2.getClass()); 

chúng ta thấy rằng

object2 lớp: lớp junk.NewMain $ 1

đó là chính xác (tôi chạy mẫu mã trong gói rác, lớp NewMain).

Sau đó, các nhà thầu của junk.NewMain$1 là gì?

Class obj2Class = object2.getClass(); 
try 
{ 
    Constructor[] ctors = obj2Class.getDeclaredConstructors(); 
    for (Constructor cc : ctors) 
    { 
    System.out.println("my ctor is " + cc.toString()); 
    } 
} 
catch (Exception ex) 
{ 
    ex.printStackTrace(); 
} 

mà cho chúng ta

ctor của tôi là junk.NewMain $ 1 (java.util.Calendar)

Vì vậy, lớp ẩn danh của bạn đang tìm kiếm một Calendar để được thông qua trong. Điều này sau đó sẽ phù hợp với bạn:

Object newObj = ctors[0].newInstance(Calendar.getInstance()); 

Nếu bạn có thứ gì đó thích e này:

final String finalString = "I'm final :)"; 
final Integer finalInteger = new Integer(30); 
final Calendar calendar = Calendar.getInstance(); 
Object object2 = new Object() 
{ 
    { 
    System.out.println("Instance initializing block"); 
    System.out.println(finalString); 
    System.out.println("My integer is " + finalInteger); 
    System.out.println(calendar.getTime().toString()); 
    } 
    private void hiddenMethod() 
    { 
    System.out.println("Use reflection to find me :)"); 
    } 
}; 

sau đó kêu gọi của Mẹ để newInstance sẽ không hoạt động vì không có đủ lý lẽ trong ctor, bởi vì bây giờ nó muốn:

ctor của tôi là junk.NewMain $ 1 (java. lang.Integer, java.util.Calendar)

Nếu tôi sau đó nhanh chóng rằng với

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance()); 

một peek bên trong bằng cách sử dụng trình gỡ lỗi cho thấy rằng finalInteger là 25 và không phải là giá trị cuối cùng 30.

Mọi thứ hơi phức tạp vì bạn đang làm tất cả những điều trên trong ngữ cảnh tĩnh. Nếu bạn mất tất cả các mã của bạn ở trên và di chuyển nó vào một phương pháp không tĩnh như vậy (hãy nhớ, lớp học của tôi là junk.NewMain):

public static void main(String[] args) 
{ 
    NewMain nm = new NewMain(); 
    nm.doIt(); 
} 

public void doIt() 
{ 
    final String finalString = "I'm final :)"; 
    // etc etc 
} 

bạn sẽ tìm thấy ctor cho lớp bên trong của bạn bây giờ (loại bỏ thêm Integer tài liệu tham khảo của tôi):

ctor của tôi là junk.NewMain $ 1 (junk.NewMain, java.util.Calendar)

Các Java Language Specification, phần 15.9.3 giải thích nó theo cách này:

Nếu C là một lớp vô danh, và các lớp cha trực tiếp của C, S, là một lớp bên trong, sau đó:

  • Nếu S là một lớp địa phương và S xảy ra trong một bối cảnh tĩnh, sau đó các đối số trong danh sách đối số, nếu có, là các đối số cho hàm tạo, theo thứ tự chúng xuất hiện trong biểu thức.
  • Nếu không, trường hợp kèm theo ngay lập tức của i đối với S là đối số đầu tiên cho hàm tạo, tiếp theo là đối số trong danh sách đối số của biểu thức tạo kiểu , nếu có, theo thứ tự chúng xuất hiện trong cách diễn đạt.

Tại sao trình tạo ẩn danh lại lấy đối số?

Vì bạn không thể tạo hàm tạo cho lớp bên trong vô danh, khối khởi tạo thể hiện phục vụ mục đích đó (hãy nhớ, bạn chỉ có một thể hiện của lớp bên trong ẩn danh đó). VM không có kiến ​​thức về lớp bên trong vì trình biên dịch tách tất cả mọi thứ ra thành các lớp riêng lẻ (ví dụ: junk.NewMain $ 1). Ctor cho lớp đó chứa nội dung của trình khởi tạo thể hiện.

này được explayed bởi JLS 15.9.5.1 Anonymous Constructors:

... các nhà xây dựng vô danh có một tham số chính thức cho mỗi tranh luận thực tế để biểu hiện sự sáng tạo lớp dụ trong đó C là tuyên bố.

Trình khởi chạy thể hiện của bạn có tham chiếu đến đối tượng Calendar. Trình biên dịch sẽ lấy giá trị thời gian chạy đó vào lớp bên trong của bạn như thế nào (được tạo ra như một lớp cho VM), ngoại trừ thông qua hàm tạo?

Cuối cùng (yay), câu trả lời cho câu hỏi đốt cuối cùng. Tại sao nhà xây dựng không yêu cầu String? Bit cuối cùng của JLS 3.10.5 giải thích rằng:

Strings tính bằng biểu thức hằng số được tính tại thời gian biên dịch và sau đó đối xử như thể chúng là literals.

Nói cách khác, giá trị String của bạn được biết tại thời gian biên dịch vì nó là chữ để nó không cần phải là một phần của hàm tạo ẩn danh. Để chứng minh trường hợp này, chúng tôi sẽ kiểm tra câu lệnh tiếp theo trong JLS 3.10.5:

Các chuỗi được tính bằng cách nối vào thời gian chạy mới được tạo và do đó khác biệt.

Thay đổi mã của bạn thusly:

String str1 = "I'm"; 
String str2 = " final!"; 
final String finalString = str1 + str2 

và bạn sẽ tìm ctor của bạn bây giờ là (trong bối cảnh không tĩnh):

ctor của tôi là junk.NewMain $ 1 (junk.NewMain, java.lang.String, java.util.Calendar)

Phew. Tôi hy vọng điều này có ý nghĩa và hữu ích. Tôi đã học được rất nhiều, đó là chắc chắn!

+0

bravo! Nghiên cứu độc đáo. –

+0

+37 (nếu có thể): câu trả lời tuyệt vời! –

+0

@Paul Cảm ơn bạn đã trả lời rất nhiều thông tin :) – stemm

4

Vì trong trường hợp thứ hai không có hàm tạo mặc định nữa.

Trong trường hợp đầu tiên, chuỗi cuối cùng được gạch chân vì nó là hằng số.

Trong trường hợp thứ hai, lớp bên trong ẩn danh phải chấp nhận cá thể của Lịch vào hàm tạo của nó để nắm bắt trạng thái. Bạn có thể dễ dàng xác nhận rằng thực hiện việc này:

Object tmp = object2.getClass().getDeclaredConstructor(Calendar.class).newInstance(calendar);