2010-05-25 35 views
8

Tôi đã chơi với một số mã để tạo cấu trúc "đóng như" (không hoạt động btw)Tại sao tôi có InstantiationException này trong Java khi truy cập các biến cục bộ cuối cùng?

Mọi thứ đều ổn nhưng khi tôi cố gắng truy cập biến cục bộ cuối cùng trong mã, ngoại lệ InstantiationException bị ném.

Nếu tôi xóa quyền truy cập vào biến cục bộ bằng cách xóa hoàn toàn hoặc thay đổi thuộc tính lớp đó, không có ngoại lệ nào xảy ra.

Các doc nói: InstantiationException

Ném khi một ứng dụng cố gắng để tạo ra một thể hiện của một lớp học sử dụng phương pháp newInstance trong lớp Class, nhưng đối tượng lớp được chỉ định không thể được khởi tạo. Instantiation có thể thất bại vì nhiều lý do bao gồm nhưng không giới hạn:

- đối tượng lớp đại diện cho một lớp trừu tượng, một giao diện, một lớp mảng, một loại nguyên thủy, hoặc khoảng trống

- lớp không có hàm tạo nullary

Lý do nào khác có thể gây ra sự cố này?

Đây là mã. chú thích/bỏ ghi chú thuộc tính lớp/biến cục bộ để xem hiệu ứng (dòng: 5 và 10).

import javax.swing.*; 
import java.awt.event.*; 
import java.awt.*; 
class InstantiationExceptionDemo { 
    //static JTextField field = new JTextField();// works if uncommented 

    public static void main(String [] args) { 
     JFrame frame = new JFrame(); 
     JButton button = new JButton("Click"); 
     final JTextField field = new JTextField();// fails if uncommented 

     button.addActionListener(new _(){{ 
      System.out.println("click " + field.getText()); 
     }}); 

     frame.add(field); 
     frame.add(button, BorderLayout.SOUTH); 
     frame.pack();frame.setVisible(true); 

    } 
} 
class _ implements ActionListener { 
    public void actionPerformed(ActionEvent e){ 
     try { 
      this.getClass().newInstance(); 
     } catch(InstantiationException ie){ 
      throw new RuntimeException(ie); 
     } catch(IllegalAccessException ie){ 
      throw new RuntimeException(ie); 
     } 
    } 
} 

Đây có phải là lỗi trong Java không?

chỉnh sửa

Ồ, tôi quên, stacktrace (khi ném) là:

Caused by: java.lang.InstantiationException: InstantiationExceptionDemo$1 
at java.lang.Class.newInstance0(Class.java:340) 
at java.lang.Class.newInstance(Class.java:308) 
at _.actionPerformed(InstantiationExceptionDemo.java:25) 
+0

dòng gì là ngoại lệ ném vào – Michael

+0

25:. 'This.getClass() newInstance() ' – OscarRyz

+0

@ Oscar: Tôi đang bối rối về cú pháp của bên trong vô danh Đây có phải là nhà xây dựng không? – Uri

Trả lời

8

Vâng, điều đó có ý nghĩa.

Chỉ phiên bản đầu tiên của lớp học _ mới có quyền truy cập vào biến cục bộ.trường hợp sau này không thể, trừ khi bạn cung cấp cho họ với nó (thông qua constructor arg)

Constructor[] constructor = a.getClass().getDeclaredConstructors(); 
for (Constructor c : constructors) { 
    System.out.println(c.getParameterTypes().length); 
} 

đầu ra 1. (a là thể hiện của lớp ẩn danh của bạn)

Điều đó nói rằng, tôi không nghĩ rằng đây là một cách hay để triển khai các bao đóng. Khối khởi tạo được gọi ít nhất một lần mà không cần đến nó. Tôi cho rằng bạn chỉ đang chơi xung quanh, nhưng hãy xem lambdaj. Hoặc đợi Java 7 :)

+0

+1 cho đóng đinh nó cũng – ewernli

+0

+1 Mmmhh có và không. Trong trường hợp của các lớp bên trong vô danh thường xuyên trình biên dịch tạo tham chiếu của final local mà không cần phải có một constructor hoặc một đối số trong phương thức, tôi hiểu từ vòng lặp của bạn, trình biên dịch đã tạo nó cho tôi. Có lẽ trình biên dịch nên đã đặt nó cho tôi cũng trong khối khởi tạo – OscarRyz

+0

Về việc thực hiện đóng cửa, tốt, không chỉ là "không phải là một cách tốt" bởi vì, tốt .. nó không hoạt động chút nào. Đây chỉ là một thử nghiệm. Đối với mã thực, tôi sẽ sử dụng thành ngữ đóng "được chấp nhận" cho Java, sử dụng một lớp bên trong vô danh * new ActionListener() {public void actionPerformed (ActionEvent e) {}} * – OscarRyz

6

Dưới đây là một đoạn trích của javap -c InstantiationExceptionDemo$1 của phiên bản static field:

Compiled from "InstantiationExceptionDemo.java" 
class InstantiationExceptionDemo$1 extends _{ 
InstantiationExceptionDemo$1(); 
    Code: 
    0: aload_0 
    1: invokespecial #8; //Method _."<init>":()V 
    4: getstatic  #10; //Field InstantiationExceptionDemo.field: 
          //Ljavax/swing/JTextField; 

Và đây là số javap -c InstantiationExceptionDemo$1 của final phiên bản biến cục bộ:

Compiled from "InstantiationExceptionDemo.java" 
class InstantiationExceptionDemo$1 extends _{ 
InstantiationExceptionDemo$1(javax.swing.JTextField); 
    Code: 
    0: aload_0 
    1: invokespecial #8; //Method _."<init>":()V 
    4: aload_1 

Vì vậy, có nguyên nhân của bạn: phiên bản biến final địa phương cần một cuộc tranh cãi thêm, các JTextField tham khảo, trong constructor. Nó không có hàm tạo null.

Điều này có ý nghĩa nếu bạn nghĩ về điều đó. Khác, phiên bản InstantiationExceptionDemo$1 này sẽ nhận tham chiếu field như thế nào? Trình biên dịch giấu một thực tế rằng điều này được đưa ra như một tham số cho hàm tạo tổng hợp.

+0

+1 Tôi không biết thực sự biến cục bộ cuối cùng được "inlined" trong các lớp ẩn danh như thế nào. Theo những gì bạn viết, chúng được truyền vào trong hàm tạo cú pháp, đúng không? Vì vậy, các yêu cầu phản xạ cần sau đó cũng vượt qua một tham số nhiều hơn nữa. Một câu trả lời tuyệt vời. – ewernli

+0

+1 Tôi không biết trình biên dịch đã làm điều đó. Tôi có thể đánh dấu hai câu trả lời là được chấp nhận.Tôi biết phải làm gì, tôi sẽ upvote một câu trả lời của bạn: P;) – OscarRyz

1

Cảm ơn cả Bozho và Polygenlubricants về các câu trả lời khai sáng.

Vì vậy, lý do là (nói cách của riêng tôi)

Khi sử dụng một biến thức địa phương, trình biên dịch tạo một constructor với các lĩnh vực được sử dụng bởi các lớp bên trong vô danh và gọi nó. Nó cũng "tiêm" trường với các giá trị.

Vì vậy, những gì tôi đã làm, là sửa đổi sáng tạo của tôi để tải hàm tạo đúng với các giá trị chính xác khi sử dụng sự phản chiếu.

Đây là mã kết quả:

import javax.swing.*; 
import java.awt.event.*; 
import java.awt.*; 
import java.lang.reflect.*; 

class InstantiationExceptionDemo { 

    public static void main(String [] args) { 

     JFrame frame = new JFrame(); 
     final JButton reverse = new JButton("Reverse"); 
     final JButton swap = new JButton("Swap"); 

     final JTextField fieldOne = new JTextField(20); 
     final JTextField fieldTwo = new JTextField(20); 

     // reverse the string in field one 
     reverse.addActionListener(new _(){{ 
      StringBuilder toReverse = new StringBuilder(); 
      toReverse.append(fieldOne.getText()); 
      toReverse.reverse(); 
      fieldOne.setText(toReverse.toString()); 

      //fieldOne.setText(new StringBuilder(fieldOne.getText()).reverse().toString()); 
     }}); 

     // swap the fields 
     swap.addActionListener(new _(){{ 
      String temp = fieldOne.getText(); 
      fieldOne.setText(fieldTwo.getText()); 
      fieldTwo.setText(temp ); 
     }}); 

     // scaffolding 
     frame.add(new JPanel(){{ 
      add(fieldOne); 
      add(fieldTwo); 
     }}); 
     frame.add(new JPanel(){{ 
      add(reverse); 
      add(swap); 
     }}, BorderLayout.SOUTH); 
     frame.pack();frame.setVisible(true); 

    } 
} 
abstract class _ implements ActionListener { 
    public _(){} 

    public void actionPerformed(ActionEvent e){ 
     invokeBlock(); 
    } 

    private void invokeBlock(){ 
    // does actually invoke the block but with a trick 
    // it creates another instance of this same class 
    // which will be immediately discarded because there are no more 
    // references to it. 
     try { 
      // fields declared by the compiler in the anonymous inner class 
      Field[] fields = this.getClass().getDeclaredFields(); 
      Class[] types= new Class[fields.length]; 
      Object[] values = new Object[fields.length]; 
      int i = 0; 
      for(Field f : fields){ 
       types[i] = f.getType(); 
       values[i] = f.get(this); 
       i++; 
      } 
      // this constructor was added by the compiler 
      Constructor constructor = getClass().getDeclaredConstructor(types); 
      constructor.newInstance(values); 

     } catch(InstantiationException ie){ 
      throw new RuntimeException(ie); 
     } catch(IllegalAccessException ie){ 
      throw new RuntimeException(ie); 
     }catch(InvocationTargetException ie){ 
      throw new RuntimeException(ie);   
     } catch(NoSuchMethodException nsme){ 
      throw new RuntimeException(nsme); 
     } 
    } 
} 

Tất nhiên, như Bozho chỉ ra, đây không phải là một cách tốt (không nó là một cách, nhưng không phải là một trong những tốt) để tạo ra đóng cửa.

Có hai vấn đề với điều này.

1.- Khối khởi tạo được gọi khi được khai báo.

2. Không có cách nào có được các thông số của mã thực tế (tức là actioneEvent trong actionPerformed)

Nếu chúng ta chỉ có thể trì hoãn việc thực hiện của khối khởi tạo này sẽ làm cho một đẹp (về mặt cú pháp) đóng cửa thay thế.

Có lẽ trong Java 7 :(

Các vấn đề liên quan