首页 > 其他分享 >关于“非法的前向引用(illegal forward reference)”的探究

关于“非法的前向引用(illegal forward reference)”的探究

时间:2024-02-28 16:35:54浏览次数:26  
标签:ok reference int illegal 编译 前向 引用 forward static

1.问题:

有如下代码:

public class Test {
    static {
        i = 0;// 给变量赋值可以正常编译通过
        System.out.print(i);// 编译器会提示“非法向前引用”(illegal forward reference)
    }
    static int i = 1;
}

这段代码来自于《深入理解Java虚拟机:JVM高级特性与最佳实践(第三版)》的第7章。

image

书里没有对前向引用的进一步说明,我们自己探究一下。
把这段代码放到IDEA中,System.out.print(i)直接提示有错误。
image

编译一下看看
image

编译失败,输出的信息是
java:非法前向引用

2.什么是forward reference?

forward reference可以翻译成向前引用或者前向引用。百度百科没有收录该词条,在维基百科中有该词条,但是描述很简单。
image

既然是Java编译器报错,那就去查询Java官方资料,在JLS(Java语言规范)中找到了该词的说明:
image

References to a field are sometimes restricted, even through the field is in scope. The following rules constrain forward references to a field (where the use textually precedes the field declaration) as well as self-reference (where the field is used in its own initializer).
即使该字段在范围内,对字段的引用有时也会受到限制。以下规则限制对字段的前向引用(其中使用文本在字段声明之前)以及自引用(其中字段在其自己的初始值设定项中使用)。

这一句提到了两个概念,前向引用自引用。在JLS中说前向引用就是在字段声明之前使用它,再回头看前言的例子中的代码

public class Test {
    static {
        i = 0;
        System.out.print(i);
    }
    static int i = 1;
}

i在未声明时就在static块中使用了,说明i = 0;属于前向引用。
如果注释掉System.out.print(i);这一行,程序可以正常编译通过。
将上面的代码稍微改造一下,打印i的值,看看是0还是1

public class Test {

    static {
        i = 0;// 给变量赋值可以正常编译通过
    }
    static int i = 1;

    public static void main(String[] args) {
        System.out.println(i);// 输出1
    }
}

i的值是1,符合预期。
复习一下类初始化的步骤,静态变量(类变量)和静态代码块(static{}块)按照从上到下的顺序执行。static int i = 1;i = 0;后面,所以i的值是1
再来看看Test这个类的字节码情况,使用jclasslib插件查看很方便。
image

Test类初始化方法<clinit>的字节码
iconst_0 // 把常量0压入操作数栈
putstatic #3 <com/shion/init_code/Test.i : I> // 把栈顶的值0赋值给类变量i i->0
iconst_1 // 把常量1压入操作数栈
putstatic #3 <com/shion/init_code/Test.i : I> // 把栈顶的值1赋值给类变量i i->1
return // 返回void

从字节码看到,类变量i确实被赋值了两次,第一次是0,第二次是1。难道类变量没声明也可以赋值吗?当然不是,答案已经呼之欲出了,我们来看看Test这个类的class文件,用IDEA查看反编译后的代码。
image

好家伙,原来是Java编译器的功劳。
补充:
Java允许前向引用,从JLS的说明上看,不管是类变量还是实例变量皆可,Java编译器编译时会自动处理。

3.什么情况属于非法的前向引用?

既然知道了前向引用的概念,那什么情况属于非法的前向引用呢?
还是看JLS的说明:
image

解释下什么是简单名称,就是一个单词或一个字母这种形式的名称,和它相对的就是限定名称(以.分隔的单词序列,例如java.lang.Object或者System.out)。
JLS给出了一个详细的例子来说明哪些情况属于非法的前向引用:

点击查看代码
class UseBeforeDeclaration {
    static {
        x = 100;
          // ok - assignment
        int y = x + 1;
          // error - read before declaration
        int v = x = 3;
          // ok - x at left hand side of assignment
        int z = UseBeforeDeclaration.x * 2;
          // ok - not accessed via simple name

        Object o = new Object() { 
            void foo() { x++; }
              // ok - occurs in a different class
            { x++; }
              // ok - occurs in a different class
        };
    }

    {
        j = 200;
          // ok - assignment
        j = j + 1;
          // error - right hand side reads before declaration
        int k = j = j + 1;
          // error - illegal forward reference to j
        int n = j = 300;
          // ok - j at left hand side of assignment
        int h = j++;
          // error - read before declaration
        int l = this.j * 3;
          // ok - not accessed via simple name

        Object o = new Object() { 
            void foo(){ j++; }
              // ok - occurs in a different class
            { j = j + 1; }
              // ok - occurs in a different class
        };
    }

    int w = x = 3;
      // ok - x at left hand side of assignment
    int p = x;
      // ok - instance initializers may access static fields

    static int u =
        (new Object() { int bar() { return x; } }).bar();
	    // ok - occurs in a different class

    static int x;

    int m = j = 4;
      // ok - j at left hand side of assignment
    int o =
        (new Object() { int bar() { return j; } }).bar(); 
        // ok - occurs in a different class
    int j;
}

通过查询其他资料,大家总结了一句话:

通过简单名称引用的变量可以出现在左值位置,但不能出现在右值的位置

根据这条规则,再看上面的例子,int y = x + 1;这行代码中,x出现在了右值的位置。
再回头看问题里面的例子,System.out.print(i);这行代码,符合JLS里提到的

The reference appears either in a class variable initializer of C or in a static initializer of C (§8.7);
该引用出现在 C 的类变量初始值设定项(static字段)中或 C 的静态初始值设定项(static代码块)中(第 8.7 节);

4.前向引用的好处?

前向引用在语法上很容易造成误解,特别是刚接触Java编程的新人,那为什么Java还要允许它的存在呢?
以下说明来自:
前向引用 - 为什么这段代码会编译?

前向引用是一种编译技术,允许在当前编译单元中引用其他编译单元中的类型。这种技术可以提高编译速度,并允许在不同的编译单元之间进行更灵活的组织和模块化。
前向引用的优势:

  1. 提高编译速度:通过将类型声明和定义分离,可以减少编译器需要处理的代码量,从而提高编译速度。
  2. 更灵活的组织:前向引用允许在不同的编译单元之间进行更灵活的组织和模块化,这有助于提高代码的可维护性和可读性。
  3. 更好的性能:前向引用可以减少不必要的内存分配和释放,从而提高程序的性能。
    应用场景:
  4. 大型项目:在大型项目中,前向引用可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性。
  5. 模块化开发:在模块化开发中,前向引用可以帮助开发人员将不同的模块分离,从而提高代码的可读性和可维护性。
  6. 多编译单元项目:在多编译单元项目中,前向引用可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性。

5.总结

前向引用是Java语言层面允许的,Java编译器进行编译时会检查非法的前向引用,其目的是避免循环初始化和其他非正常的初始化行为。

最后再简单提一下什么是循环引用,看一下下面这个例子:

private int i = j;
private int j = i;

如果没有前面说的强制检查,那么这两句代码就会通过编译,但是很容易就能看得出来,ij并没有被真正赋值,因为两个变量都是未初始化的(Java规定所有变量在使用之前必须被初始化),而这个就是最简单的循环引用的例子。

理解前向引用等概念,可能对提高写CRUD代码的水平没有什么帮助,但是能帮助我们更好的理解这门编程语言。

参考链接:
https://stackoverflow.com/questions/14624919/illegal-forward-reference-java-issue
https://www.imooc.com/wenda/detail/557184
https://cloud.tencent.com/developer/information/前向引用 - 为什么这段代码会编译?
https://docs.oracle.com/javase/specs/jls/se14/html/jls-8.html#jls-8.3.3

标签:ok,reference,int,illegal,编译,前向,引用,forward,static
From: https://www.cnblogs.com/shionsun/p/18040918

相关文章

  • C++ STL 容器 forward_list类型
    C++STL容器forward_list类型介绍std::forward_list是C++标准模板库(STL)中的一个单向链表容器。与std::list不同,std::forward_list只允许从头部到尾部的单向迭代,不支持反向迭代。因此,std::forward_list在某些操作上可能比std::list更高效,尤其是在插入和删除元素时......
  • lazarus3.0 /fpc3.3.1编译某些控件会出现:Error: Forward declaration not solved xxx
    最近用lazarus3.0/fpc3.3.1时发现原来在lazarus2.2.6/fpc3.2.2能编译安装的控件出现类似下面的提示codebot.text.xml.pas(129,10)Error:Forwarddeclarationnotsolved"NewDocument:IDocument;"解决方法:本例子参照DocumentCreate:IDocument,在实现部分编写过程。{$i......
  • [Rust] Reference Types in Rust
    LearnhowtocreatereferencesinRustusingtheborrow-operator & andwhentheyareuseful.Foramorethoroughexplanationofreferencesandtheircharacteristics,checkoutthisblogpost:https://blog.thoughtram.io/references-in-rust/letname:St......
  • Unity中的SerializeReference使用简介
    Unity默认可以序列化值类型,Serializable属性修饰的类型,派生自UnityEngine.Object的类型,通常这些类型已经足以供日常使用了.但是有时我们希望在编辑器面板上序列化一个接口或者抽象类,则需要用到SerializeReference属性.假定我们有一个接口IEatable,并实现了两个类Brea......
  • [969] Add a spatial reference (a coordinate reference system, CRS) to a GeoDataF
    Toaddaspatialreference(acoordinatereferencesystem,CRS)toaGeoDataFrameinGeoPandas,youcansetthecrsattributeoftheGeoDataFrametothedesiredCRS.Here'showyoucandoit:importgeopandasasgpdfromshapely.geometryimportPoint......
  • 已解决java.lang.IllegalAccessException异常的正确解决方法,亲测有效!!!
    已解决java.lang.IllegalAccessException异常的正确解决方法,亲测有效!!!文章目录问题分析与报错原因解决思路解决方法总结 ----------------------------------------------------------------------------------------------------------------问题分析与报错原因java.lang.IllegalA......
  • Caused by: java.lang.IllegalStateException: A unix domain socket connection requ
    Causedby:java.lang.IllegalStateException:Aunixdomainsocketconnectionrequiresepollorkqueueandneitherisavailable出现这个错误,首先确保自己的操作系统是否支持epoll,或者kqueue。如果支持。请导入netty的大库,lettuce中好像缺失了一部分,我怀疑是这是怀疑,......
  • Reference only code reading
    Referenceonlycodereading代码逻辑梳理Analysereference-onlycodeincontrolnetextensionofsdwebui.ControlNetHookTheentrypointinnercontrolnet_main_entrylookslike:#defcontrolnet_main_entry():self.latest_network=UnetHook(lowvram=is_low_v......
  • Invalid character ( found Illegal strategy name: 'Vivado Synthesis Defaults(2)'
    出现问题的操作步骤:Tools---settings---啥也没改,点击ok或者apply就会弹出这个错误,如下图: 解决方法: 在4数字标的输入框里输入1,回车之后这个问题就消失了。可能是不知道啥时候动点了什么,导致了这个问题,比较像是Vivado的Bug。本来都打算重装vivado了,还好随便试了一下解决了。......
  • tomcat启动时报错:Caused by: java.lang.IllegalArgumentException: AJP连接器配置secr
    31-Jan-202414:01:13.812信息[main]org.apache.coyote.AbstractProtocol.start开始协议处理句柄["http-nio-8080"]31-Jan-202414:01:13.818严重[main]org.apache.catalina.core.StandardService.startInternalFailedtostartconnector[Connector[AJP/1.3-8009]]......