Java向前引用容易出错的地方

简介:      所谓向前引用,就是在定义类、接口、方法、变量之前使用它们,例如,class MyClass{ void method() { System.out.println(myvar); } String myvar = "var value";}      myvar在method方法后定义,但method方法可以先使用该变量。

     所谓向前引用,就是在定义类、接口、方法、变量之前使用它们,例如,

class MyClass
{
    void method()
    {
        System.out.println(myvar);
    }
    String myvar = "var value";
} 

     myvar在method方法后定义,但method方法可以先使用该变量。在很多语言,如C++,是需要提前定义的,而Java已经允许了向前引用。不过在使用向前引用时可能会容易犯一些错误。例如,下面的代码。

class MyClass {
     int method() {return n; }
     int m = method();
     int n = 1;
}

     如果简单地执行下面的代码,毫无疑问会输出1.

    System.out.println(new MyClass().method());

     不过使用下面的代码输出变量m,却得到0.

    System.out.println(new MyClass().m);
    那么这是真么回事呢?

    实际上,从java编译器和runtime的工作原理可以得知。在编译java源代码时只是进行了词法、语法和语义检测,如果都通过,会生成.class文件。不过这时MyClass中的变量并没有被初始化,编译器只是将相应的初始化表达式(method()、1)记录在.class文件中。

    当runtime运行MyClass.class时,首先会进行装载成员字段,而且这种装载是按顺序执行的。并不会因为java支持向前引用,就首先初始化所有可以初始化的值。首先,runtime会先初始化m字段,这时当然会调用method方法,在method方法中利用向前引用技术使用了n。不过这时的n还没有进行初始化呢。runtime为了实现向前引用,在进行初始化所有字段之前,还需要将所有的字段添加到符号表中。以便在任何地方(但需要满足java的调用规则)都可以引用这些字段,不过由于还没有初始化这些字段,所以这时符号表中所有的字段都使用默认的值。int类型的字段默认值自然是0了。所以在初始化int m = method()时,method方法访问的n实际上是在进行正式初始化之前已经被添加到符号表中的字段n,而不是后面的int n = 1执行的结果。但将MyClass改成如下的形式,结果就完全不同了。

class MyClass {
    int method() {return n; }
    int n = 1;
    int m = method();
}

     现在执行下面的代码,会输出1.

    System.out.println(new MyClass().m);
    究其原因,是引用初始化m时调用method方法,该方法中使用的n已经是初始化完的了,而不是最初放到符号表中的值。

    综合上述,runtime在运行.class文件时,每个作用域(方法、接口、类等带语言元素都有自己的作用域)的符号表都会被至少访问两次,第一次会将所有的字段(这里只考虑类的初始化)放到符号表中,暂时不考虑初始化只,放到符号表中只是相当于一个索引,好让其他地方引用该字段时可以找到它们,例如,method方法中引用n时就会到符号表中寻找n,不过这时的n只是int类型的默认值。等到第二次访问n就是真正初始化n的时候(int n = 1)。这是将符号表中存储的字段n的值更新为实际的初始化值(1)。所以如果引用n放生在正式初始化n之前,当然输出的是0。

    那么可能有人会问,先访问一下n,再访问m,这时m的值是否为1呢?答案仍然是0。因为在创建MyClass对象时m和n的初始化工作已经完成,它们的值已成事实,除非再次设置,否则不可改变了。

   MyClass myClass = new MyClass();
   System.out.println(myClass.n);  //  输出1
   System.out.println(myClass.m);  //  仍然输出0
    对于静态成员,仍然符合这一规则。  
class MyClass {
     static int method() {return n; }
     static int m = method();  //  直接访问m,仍然会输出0
     static int n = 1;
}

     


目录
相关文章
|
6月前
|
安全 Java
从零开始学习 Java:简单易懂的入门指南之不可变集合、方法引用(二十六)
从零开始学习 Java:简单易懂的入门指南之不可变集合、方法引用(二十六)
|
5月前
|
缓存 Java 数据库连接
java面试题目 强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么?
【6月更文挑战第28天】在 Java 中,理解和正确使用各种引用类型(强引用、软引用、弱引用、幻象引用)对有效的内存管理和垃圾回收至关重要。下面我们详细解读这些引用类型的区别及其具体使用场景。
78 3
|
4月前
|
Java 运维
开发与运维引用问题之软引用又在Java特点如何解决
开发与运维引用问题之软引用又在Java特点如何解决
38 0
|
5月前
|
Java
java方法引用::
java方法引用::
|
6月前
|
存储 Java
滚雪球学Java(41):Lambda表达式和方法引用:提高代码可读性和简洁性的神器
【5月更文挑战第16天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
53 2
滚雪球学Java(41):Lambda表达式和方法引用:提高代码可读性和简洁性的神器
|
6月前
|
存储 Java 索引
Java一维数组元素的引用与操作技术详解
Java一维数组元素的引用与操作技术详解
60 1
|
6月前
|
Java
【JAVA学习之路 | 进阶篇】方法引用与构造器引用
【JAVA学习之路 | 进阶篇】方法引用与构造器引用
|
6月前
|
存储 Java 索引
Java的数组定义与引用
Java的数组定义与引用
|
6月前
|
存储 Java 索引
Java二维数组的引用与操作技术详解
Java二维数组的引用与操作技术详解
73 0
|
6月前
|
缓存 Java
【JAVA】强引用、软引用、弱引用、幻象引用有什么区别?
幻象引用:幻象引用是最弱的引用类型,几乎不影响对象的生命周期。它们主要用于在对象被回收前进行某些预处理操作,例如在对象被销毁时执行特定的清理任务。
57 0