关于“非法的前向引用(illegal forward reference)”的探究
1.问题:
有如下代码:
public class Test {
static {
i = 0;// 给变量赋值可以正常编译通过
System.out.print(i);// 编译器会提示“非法向前引用”(illegal forward reference)
}
static int i = 1;
}
这段代码来自于《深入理解Java虚拟机:JVM高级特性与最佳实践(第三版)》的第7章。
书里没有对前向引用的进一步说明,我们自己探究一下。
把这段代码放到IDEA中,System.out.print(i)
直接提示有错误。
编译一下看看
编译失败,输出的信息是
java:非法前向引用
2.什么是forward reference?
forward reference可以翻译成向前引用或者前向引用。百度百科没有收录该词条,在维基百科中有该词条,但是描述很简单。
既然是Java编译器报错,那就去查询Java官方资料,在JLS(Java语言规范)中找到了该词的说明:
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插件查看很方便。
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查看反编译后的代码。
好家伙,原来是Java编译器的功劳。
补充:
Java允许前向引用,从JLS的说明上看,不管是类变量还是实例变量皆可,Java编译器编译时会自动处理。
3.什么情况属于非法的前向引用?
既然知道了前向引用的概念,那什么情况属于非法的前向引用呢?
还是看JLS的说明:
解释下什么是简单名称,就是一个单词或一个字母这种形式的名称,和它相对的就是限定名称(以.分隔的单词序列,例如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还要允许它的存在呢?
以下说明来自:
前向引用 - 为什么这段代码会编译?
前向引用是一种编译技术,允许在当前编译单元中引用其他编译单元中的类型。这种技术可以提高编译速度,并允许在不同的编译单元之间进行更灵活的组织和模块化。
前向引用的优势:
- 提高编译速度:通过将类型声明和定义分离,可以减少编译器需要处理的代码量,从而提高编译速度。
- 更灵活的组织:前向引用允许在不同的编译单元之间进行更灵活的组织和模块化,这有助于提高代码的可维护性和可读性。
- 更好的性能:前向引用可以减少不必要的内存分配和释放,从而提高程序的性能。
应用场景:- 大型项目:在大型项目中,前向引用可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性。
- 模块化开发:在模块化开发中,前向引用可以帮助开发人员将不同的模块分离,从而提高代码的可读性和可维护性。
- 多编译单元项目:在多编译单元项目中,前向引用可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性。
5.总结
前向引用是Java语言层面允许的,Java编译器进行编译时会检查非法的前向引用,其目的是避免循环初始化和其他非正常的初始化行为。
最后再简单提一下什么是循环引用,看一下下面这个例子:
private int i = j;
private int j = i;
如果没有前面说的强制检查,那么这两句代码就会通过编译,但是很容易就能看得出来,i和j并没有被真正赋值,因为两个变量都是未初始化的(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
关于“非法的前向引用(illegal forward reference)”的探究的更多相关文章
- java 报错非法的前向引用
今天在看<thinking in java>的时候,第四章提到了非法的前向引用,于是自己试了一下,书中的例子倒是一下就明白了,但是自己写的一个却怎么也不明白,于是上网问了一位前辈,终于明白 ...
- wpf staticresource 是不允许向前引用(forward reference)的
不允许向前引用(forward reference)在C/C++中中很常见,即在语法上,未定义变量.类之前,不能使用. 没想到wpf中的wpf staticresource也遵循这种规则.资源字典中, ...
- 前向传播算法(Forward propagation)与反向传播算法(Back propagation)
虽然学深度学习有一段时间了,但是对于一些算法的具体实现还是模糊不清,用了很久也不是很了解.因此特意先对深度学习中的相关基础概念做一下总结.先看看前向传播算法(Forward propagation)与 ...
- Python小白学习之路(十二)—【前向引用】【风湿理论】
前向引用 风湿理论(函数即变量) 理论总是很抽象,我个人理解: 代码从上到下执行,一旦遇到定义的函数体,内存便为其开辟空间,并用该函数的名字作为一个标识但是该函数体内具体是什么内容,这个时候并不着急去 ...
- 018--python 函数参数、变量、前向引用、递归
目录 一.python函数的定义 二.函数参数 三.全局变量和局部变量 四.前向引用 五.递归 一.python函数的定义 python函数是对程序逻辑进行结构化或过程化的一种方法 1 python中 ...
- C++类的组合、前向引用声明
3.5类的组合 Part1.应用背景 对于复杂的问题,往往可以逐步划分为一系列稍微简单的子问题. 解决复杂问题的有效方法是将其层层分解为简单的问题组合,首先解决简单问题复杂问题也就迎刃而解了. 在面向 ...
- (原)编译caffe时提示未定义的引用(undefined reference to)
转载请注明出处: http://www.cnblogs.com/darkknightzh/p/5864715.html 参考网址: https://github.com/BVLC/caffe/issu ...
- C++11标准之右值引用(rvalue reference)
1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了Copy Elision.RVO(包 ...
- 理解Java中的弱引用(Weak Reference)
本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...
- Java中引用类 strong reference .SoftReference 、 WeakReference 和 PhantomReference的区别
当在 Java 2 平台中首次引入 java.lang.ref 包,其中包含 SoftReference . WeakReference 和 PhantomReference 三个引用类,引用类的 ...
随机推荐
- [转帖]INTEL MLC(Memory Latency Checker)介绍
https://zhuanlan.zhihu.com/p/359823092 在定位机器性能问题的时候,有时会觉得机器莫名其妙地跑的慢,怎么也看不出来问题.CPU频率也正常,程序热点也没问题,可就是慢 ...
- [转帖]gdb进阶调试技巧
https://www.jianshu.com/p/9bdaa0644dba 整理一下在linux下C/C++用gdb工具debug一些提高效率的操作.基本的gdb操作就不在这里赘述了. 打印各种变量 ...
- [转帖]Native Memory Tracking 详解(4):使用 NMT 协助排查内存问题案例
https://www.modb.pro/db/557714 从前面几篇文章,我们了解了 NMT 的基础知识以及 NMT 追踪区域分析的相关内容,本篇文章将为大家介绍一下使用 NMT 协助排查内存问题 ...
- [转帖]磁盘负载指标 %iowait, await, %util 的正确理解
说明 %iowait, await, %util 是用来衡量硬盘负载的三个指标, 但是这几个指标通常容易被误解, 实际上, 这三个指标单纯的高, 并不一定能说明相应的磁盘有问题或者有瓶颈, 而是需要结 ...
- NLP涉及技术原理和应用简单讲解【二】:paddle(分布式训练、AMP自动混合精度训练、模型量化、模型性能分析)
参考链接: https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/06_distributed_training/cluster_q ...
- 码云gitee创建仓库并用git上传文件
相关文章链接: 码云(gitee)配置SSH密钥 码云gitee创建仓库并用git上传文件 git 上传错误This oplation equires one of the flowi vrsions ...
- 5.12 汇编语言:仿写While循环语句
循环语句(While)一种基本控制结构,它允许程序在条件为真的情况下重复执行一段代码块,直到条件为假为止.循环语句在处理需要重复执行的任务时非常有用,它可以让程序更加高效地处理大量数据或者重复性操作. ...
- 遥感图像处理笔记之【Automatic Flood Detection from Satellite Images Using Deep Learning】
遥感图像处理学习(7) 前言 遥感系列第7篇.遥感图像处理方向的学习者可以参考或者复刻 本文初编辑于2023年12月29日 2024年1月24日搬运至本人博客园平台 文章标题:Automatic Fl ...
- Kubernetes 漫游:Controller Manager
Controller Manager Controller Manager 是控制平面的一个重要组件,负责维护 Kubernetes 集群的整体状态. 流程: 在集群中 Controller Mana ...
- NC15532 Happy Running
题目链接 题目 题目描述 Happy Running, an application for runners, is very popular in CHD. In order to lose wei ...