当前位置: 首页 > 知识库问答 >
问题:

为什么尝试打印未初始化的变量并不总是导致错误消息

桑思远
2023-03-14

有些人可能会发现它类似于SO问题Java最终变量会有默认值吗?但是这个答案并没有完全解决这个问题,因为这个问题没有直接在实例初始化器块中打印x的值。

当我试图在实例初始化器块内直接打印x时,问题就出现了,同时在块结束前给x分配了一个值:

class HelloWorld {

    final int x;

    {
        System.out.println(x);
        x = 7;
        System.out.println(x);    
    }

    HelloWorld() {
        System.out.println("hi");
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

这将给出一个编译时错误,说明变量x可能尚未初始化。

$ javac HelloWorld.java
HelloWorld.java:6: error: variable x might not have been initialized
        System.out.println(x);
                           ^
1 error

我不是直接打印,而是调用一个函数来打印:

class HelloWorld {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    HelloWorld() {
        System.out.println("hi");
    }

    void printX() {
        System.out.println(x);
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

这将正确编译并提供输出

0
7
hi

这两种情况在概念上有什么不同?

共有3个答案

栾英资
2023-03-14

不同之处在于,在第一种情况下,您从初始化块调用System.out.println,因此在构造函数之前调用的块。在第一行

System.out.println(x);

变量x尚未初始化,因此出现编译错误。

但是在第二种情况下,你调用实例方法,它不知道变量是否已经初始化,所以你没有编译错误,你可以看到默认值为x

吕俊才
2023-03-14

阅读JLS,答案似乎在第16.2.2节:

一个空白的最终成员字段V在块(§14.2)之前,即在V范围内的任何方法的主体之前,并且在声明任何方法之前,肯定被分配(并且不肯定被未分配)在V范围内声明的类。

这意味着当调用方法时,在调用它之前,最终字段被分配给它的默认值0,因此当您在方法中引用它时,它会成功编译并打印值0。

但是,当您访问方法外部的字段时,它被认为是未赋值的,因此会出现编译错误。以下代码也不会编译:

public class Main {
    final int x;
    {
        method();
        System.out.println(x);
        x = 7;
    }
    void method() { }
    public static void main(String[] args) { }
}

因为:

  • V被分配在块的任何其他语句S之前,如果V被分配在块中紧接在S之前的语句之后。

由于最后一个字段x在方法调用之前是未分配的,因此在方法调用之后仍然是未分配的。

JLS中的此注释也与此相关:

请注意,没有规则允许我们得出结论,在作为任何构造函数、方法、实例初始化器或静态初始化器的主体的块之前,V肯定是未分配的。我们可以非正式地得出结论,在C中声明的任何构造函数、方法、实例初始化器或静态初始化器的主体块之前,V不是绝对未分配的,但是没有必要显式地声明这样的规则。

曹均
2023-03-14

在JLS中,§8.3.3。在字段初始化期间转发引用,它声明在以下情况下存在编译时错误:

即使这些实例变量在作用域中,但在使用后以文本形式显示其声明的实例变量的使用有时会受到限制。具体而言,如果以下所有条件均为真,则为编译时错误:

>

  • 类或接口C中实例变量的声明在使用实例变量后以文本形式出现;

    在C的实例变量初始值设定项或C的实例初始值设定项中使用一个简单的名称;

    使用不在作业的左侧;

    C是最里面的类或包含使用的接口。

    以下规则附带了几个示例,其中最接近您的是:

    class Z {
        static int peek() { return j; }
        static int i = peek();
        static int j = 1;
    }
    class Test {
        public static void main(String[] args) {
            System.out.println(Z.i);
        }
    }
    

    通过方法访问[静态或实例变量]不是以这种方式检查的,所以上面的代码产生输出0,因为i的变量初始化器使用类方法peek()来访问的值变量j之前的变量j已经被其变量初始化器初始化,此时它仍然具有默认值(§4.12.5变量的初始值)。

    总之,第二个示例编译并执行得很好,因为编译器在调用printX()时不会检查x变量是否已初始化,并且当printX()实际在运行时发生时,x变量将被分配其默认值(0)。

  •  类似资料:
    • 问题内容: 当我尝试在实例初始值设定项块内直接打印x,而在块末尾之前为x赋值时,就会出现问题: 情况1 这给出了编译时错误,指出变量x可能尚未初始化。 情况二 我不是直接打印,而是调用一个函数来打印: 这样可以正确编译并提供输出 两种情况在概念上有什么区别? 问题答案: 在JLS中,第8.3.3节。字段初始化期间的正向引用,它指出在以下情况下存在编译时错误: 有时会限制使用声明之后以文本形式出现的

    • *编辑:好的,在修复了try catch错误后,我在< code>catch {..打印时。*,基本上,当我说我想再玩一次时,它会继续游戏,但它也会打印第一个< code>catch,然后在第23行要求输入。 嗨,这是我的第一篇文章,所以如果我在论坛上的代码格式错误,我会编辑它。 现在我正在用java eclipse编写一个游戏,其中cpu生成一个数字,用户必须猜测它。我大部分时间都在使用扫描仪类

    • 问题内容: 我的Java程序中出现以下错误: Java变量未初始化错误…错误:变量nam和r未初始化位置类子级 但是并且已经初始化,但是我仍然遇到相同的错误。 问题答案: 不要获取 默认值 ,应在使用它们之前对其进行初始化,并使用main中的默认值进行初始化,这样就可以了。 顺便说一句,考虑为您的类和变量命名是有意义的。

    • 为什么未初始化的变量打印一个奇怪的负值?

    • 我有以下Java代码: 但是,当我运行它时,它会抛出以下错误: 我在do之前初始化了变量。while 循环,并在 try. 中设置值。捕获循环。似乎尚未设置该变量。抱歉,如果这是一个相当基本的问题,但我似乎无法弄清楚。

    • 我正在尝试设置DBMS\U元数据的输出。将\u DDL获取到SQLPlus中的myRET\u VARbind变量。 我尝试了以下脚本。sql: 但我得到: SP2-1504:无法打印未初始化的LOB变量“RET\u VAL” 我还尝试用一个/块来围绕RET\u VAL的赋值: 但是,当我尝试在SQLPlus中运行它时,会出现以下错误: 第5行的错误:ORA-06550:第5行,第1列:PLS-00