为什么我在访问最终局部变量时在 Java 中有这个 InstantiationException?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2906954/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 14:21:51  来源:igfitidea点击:

Why am I having this InstantiationException in Java when accessing final local variables?

javainstantiationexception

提问by OscarRyz

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 InstantiationExceptionis thrown.

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

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

文档说: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

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

- 类对象表示抽象类、接口、数组类、原始类型或 void

- 该类没有空构造函数

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 ).

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

Is this a bug in Java?

这是Java中的错误吗?

edit

编辑

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

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

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

采纳答案by Bozho

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)

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

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

outputs 1. (ais the instance of your anonymous class)

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

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 :)

也就是说,我认为这不是实现闭包的好方法。初始化块至少被调用一次,不需要它。我假设你只是在玩,但看看lambdaj。或者等待 Java 7 :)

回答by polygenelubricants

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

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

Compiled from "InstantiationExceptionDemo.java"
class InstantiationExceptionDemo extends _{
InstantiationExceptionDemo();
  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$1of the finallocal variable version:

javap -c InstantiationExceptionDemo$1final局部变量版本的:

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

So there's your cause: the finallocal variable version needs an extra argument, the JTextFieldreference, in the constructor. It has no nullary constructor.

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

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

如果你仔细想想,这是有道理的。不然这个版本InstantiationExceptionDemo$1怎么拿到field参考呢?编译器隐藏了这个事实,即 this 作为参数提供给合成构造函数。

回答by OscarRyz

Thank you both Bozho and Polygenlubricants for the enlightening answers.

感谢 Bozho 和 Polygenlubricants 的启发性回答。

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.

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

There are two problems with this.

这有两个问题。

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

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

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

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

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 :(

也许在 Java 7 中 :(