为什么我在访问最终局部变量时会出现 Java 中的 InstantiationException?

发布于 2024-09-02 12:09:43 字数 1918 浏览 2 评论 0原文

我正在使用一些代码来制作“类似闭包”的构造(顺便说一句,不起作用)

一切看起来都很好,但是当我尝试访问代码中的最终局部变量时,抛出异常 InstantiationException

如果我通过完全删除局部变量或使其成为类属性来删除对局部变量的访问,则不会发生异常。

该文档说: InstantiationException

当应用程序尝试使用类 Class 中的 newInstance 方法创建类的实例,但指定的类对象无法实例化时抛出。实例化可能会因多种原因而失败,包括但不限于:

- 类对象表示抽象类、接口、数组类、基本类型或 void

- 该类没有无效构造函数

还有什么其他原因可能导致此问题?

这是代码。注释/取消注释类属性/局部变量以查看效果(第 5 行和第 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 );
        }
    }
}

这是 Java 中的错误吗?

编辑

哦,我忘了,堆栈跟踪(抛出时)是:

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)

I was playing with some code to make a "closure like" construct ( not working btw )

Everything looked fine but when I tried to access a final local variable in the code, the exception InstantiationException is thrown.

If I remove the access to the local variable either by removing it altogether or by making it class attribute instead, no exception happens.

The doc says: InstantiationException

Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated. The instantiation can fail for a variety of reasons including but not limited to:

- the class object represents an abstract class, an interface, an array class, a primitive type, or void

- the class has no nullary constructor

What other reason could have caused this problem?

Here's the code. comment/uncomment the class attribute / local variable to see the effect (lines:5 and 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 );
        }
    }
}

Is this a bug in Java?

edit

Oh, I forgot, the stacktrace ( when thrown ) is:

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

苏佲洛 2024-09-09 12:09:43

嗯,这是有道理的。

只有 _ 类的第一个实例有权访问局部变量。后续实例不能,除非你为它们提供它(通过构造函数arg)

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

输出1。(a是你的匿名类的实例)

也就是说,我不认为这是一个好方法来实施关闭。初始化块至少被调用一次,而不需要它。我假设您只是玩玩,但请看一下 lambdaj。或者等待 Java 7 :)

Well, that makes sense.

Only your first instance of the _ class has access to the local variable. Subsequent instances can't, unless you provide them with it (via constructor arg)

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

outputs 1. (a is the instance of your anonymous class)

That said, I don't think this is a good way to implement closures. The initializer block is called at least once, without the need of it. I assume you are just playing around, but take a look at lambdaj. Or wait for Java 7 :)

浅听莫相离 2024-09-09 12:09:43

以下是 static field 版本的 javap -c InstantiationExceptionDemo$1 的摘录:

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;

这是 javap -c InstantiationExceptionDemo$1 Final 局部变量版本:

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

这就是您的原因:final 局部变量版本在构造函数中需要一个额外的参数,即 JTextField 引用。它没有无效构造函数。

如果你仔细想想,这是有道理的。否则,这个版本的 InstantiationExceptionDemo$1 将如何获取 field 引用?编译器隐藏了这样一个事实:这是作为合成构造函数的参数给出的。

Here's an excerpt of the javap -c InstantiationExceptionDemo$1 of the static field version:

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;

And here's the javap -c InstantiationExceptionDemo$1 of the final local variable version:

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

So there's your cause: the final local variable version needs an extra argument, the JTextField reference, in the constructor. It has no nullary constructor.

This makes sense if you think about it. Else, how is this version of InstantiationExceptionDemo$1 going to get the field reference? The compiler hides the fact that this is given as a parameter to the synthetic constructor.

|煩躁 2024-09-09 12:09:43

感谢 Bozho 和 Polygenlubricants 提供的启发性答案。

所以,原因是(用我自己的话说)

当使用局部最终变量时,编译器会使用匿名内部类使用的字段创建一个构造函数并调用它。它还用值“注入”字段。

因此,我所做的就是修改我的创建,以使用反射加载具有正确值的正确构造函数。

这是生成的代码:

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 );
        }
    }
}

当然,正如 Bozho 指出的那样,这不是创建闭包的好方法(不是一种方法,但不是一个好方法)。

这有两个问题。

1.- 初始化块在声明时被调用。

2.- 无法获取实际代码的参数(即actionPerformed 中的actioneEvent )

如果我们可以延迟初始化块的执行,这将是一个很好的(就语法而言)闭包替代方案。

也许在 Java 7 中:(

Thank you both Bozho and Polygenlubricants for the enlightening answers.

So, the reason is ( in my own words )

When using a local final variable, the compiler creates a constructor with the fields used by the anonymous inner class and invokes it. It also "inject" field with the values.

So, what I did, was to modify my creation to load the right constructor with the correct values using reflection.

This is the resulting code:

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 );
        }
    }
}

Of course, as Bozho points out, this is not a good way ( not it is a way, but not a good one ) to create closures.

There are two problems with this.

1.- The initializer block is invoked when it is declared.

2.- There is no way get the parameters of the actual code ( ie actioneEvent in actionPerformed )

If we could just delay the execution of the initializer block this would make a nice ( in terms of syntax ) closure alternative.

Perhaps in Java 7 :(

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文