`
什么世道
  • 浏览: 219127 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

小叙Java变量的作用域和生存期问题

阅读更多

    到目前为止,我们使用的所有变量都是在方法main() 的后面被声明。然而,Java 允许变量在任何程序块内被声明。在前面就了解了,程序块被包括在一对大括号中。一个程序块定义了一个作用域(scope )。这样,你每次开始一个新块,你就创建了一个新的作用域。你可能从先前的编程经验知道,一个作用域决定了哪些对象对程序的其他部分是可见的,它也决定了这些对象的生存期。

    大多数其他计算机语言定义了两大类作用域:全局和局部。然而,这些传统型的作用域不适合Java 的严格的面向对象的模型。当然将一个变量定义为全局变量是可行的,但这是例外而不是规则。在Java 中2个主要的作用域是通过类和方法定义的。尽管类的作用域和方法的作用域的区别有点人为划定。因为类的作用域有若干独特的特点和属性,而且这些特点和属性不能应用到方法定义的作用域,这些差别还是很有意义的。

    方法定义的作用域以它的左大括号开始。但是,如果该方法有参数,那么它们也被包括在该方法的作用域中。因此,现在可认为它们与方法中其他变量的作用域一样。

     作为一个通用规则,在一个作用域中定义的变量对于该作用域外的程序是不可见(即访问)的。因此,当你在一个作用域中定义一个变量时,你就将该变量局部化并且保护它不被非授权访问和/或修改。实际上,作用域规则为封装提供了基础。

    作用域可以进行嵌套。例如每次当你创建一个程序块,你就创建了一个新的嵌套的作用域。这样,外面的作用域包含内部的作用域。这意味着外部作用域定义的对象对于内部作用域中的程序是可见的。但是,反过来就是错误的内部作用域定义的对象对于外部是不可见的。

 

// Demonstrate block scope.
class Scope {
public static void main(String args[]) {
int x; // known to all code within main(全局变量)
x = 10;
if(x == 10) { // start new scope
int y = 20; // known only to this block(局部变量)
// x and y both known here.
System.out.println("x and y: " + x + " " + y);
x = y * 2;
}
// y = 100; // Error! y not known here
// x is still known here.
System.out.println("x is " + x);
}
}

 

 

 

     正如注释中说明的那样,在方法main() 的开始定义了变量x,因此它对于main() 中的所有的随后的代码都是可见的。在if程序块中定义了变量y。因为一个块定义一个作用域,y 仅仅对在它的块以内的其他代码可见。这就是在它的块之外的程序行y=100; 被注释掉的原因。如果你将该行前面的注释符号去掉,编译程序时就会出现错误,因为变量y在它的程序块之外是不可见的。在if程序块中可以使用变量x,因为块(即一个嵌套作用域)中的程序可以访问被其包围作用域中定义的变量。

     变量可以在程序块内的任何地方被声明,但是只有在他们被声明以后才是合法有效的。因此,如果你在一个方法的开始定义了一个变量,那么它对于在该方法以内的所有程序都是可用的。反之,如果你在一个程序块的末尾声明了一个变量,它就没有任何用处,因为没有程序会访问它。例如,下面这个程序段就是无效的,因为变量count 在它被定义以前是不能被使用的。

 

    另一个需要记住的重要之处是:变量在其作用域内被创建,离开其作用域时被撤消。
这意味着一个变量一旦离开它的作用域,将不再保存它的值了。因此,在一个方法内定义的变量在几次调用该方法之间将不再保存它们的值。同样,在块内定义的变量在离开该块时也将丢弃它的值。因此,一个变量的生存期就被限定在它的作用域中。

     如果一个声明定义包括一个初始化,那么每次进入声明它的程序块时,该变量都要被重新初始化。例如,考虑这个程序:

 

// Demonstrate lifetime of a variable.
class LifeTime {
public static void main(String args[]) {
int x;
for(x = 0; x < 3; x++) {
int y = -1; // y is initialized each time block is entered
System.out.println("y is: " + y); // this always prints -1
y = 100;
System.out.println("y is now: " + y);
}
}
}

 

 

该程序运行的输出如下:

y is: -1
y is now: 100
y is: -1
y is now: 100
y is: -1
y is now: 100

 

    可以看到,每次进入内部的for循环,y都要被重新初始化为-1。即使它随后被赋值为100,该值还是被丢弃了。

    最后一点:尽管程序块能被嵌套,你不能将内部作用域声明的变量与其外部作用域声明的变量重名。在这一点上,Java 不同于C和C++。下面的例子企图为两个独立的变量起同样的名字。在Java 中,这是不合法的。但在C/C++ 中,它将是合法的,而且2个变量bar将是独立的。

 

// This program will not compile
class ScopeErr {
public static void main(String args[]) {
int bar = 1;
{
// creates a new scope
int bar = 2;
// Compile-time error – bar already defined!
}
}
}

 

     但是我们在现实中编写代码的时候为了使代码清楚明了,一般把方法的返回值赋值给一个变量,然后在执行后面的方法或者其他语句什么的。但是,假如这这个变量定义的作用域不对时,可能与你想要的结果风马牛不相及。然后你就开始怀疑你的逻辑是否正确。最后一步一步就陷入了僵局,无从下手。下面一谢宾斯三角形为例,简要介绍变量作用域和生存期的重要性:

 

错误的代码,定义的是全局变量

 

/**
 * 定义画三角形的方法
 * @param g:传入的的画布对象
 * @param aX:第一个点的横坐标
 * @param aY:第一个点的纵坐标
 * @param bX:第二个点的横坐标
 * @param bY:第二个点的纵坐标
 * @param cX:第三个点的横坐标
 * @param cY:第三个点的纵坐标
 */
public void DrawTri(Graphics g,int aX,int aY,int bX, int bY,int cX,int cY){
	g.drawLine(aX, aY, bX, bY);
	g.drawLine(aX, aY, cX, cY);
	g.drawLine(bX, bY, cX, cY);
}

/**
 * 定义谢宾斯基三角形递归方法
 * @param g:传入的画布对象
 * @param a1:第一个点的横坐标
 * @param a2:第二个点的纵坐标
 * @param b1:第二个点的横坐标
 * @param b2:第二个点的纵坐标
 * @param c1:第三个点的横坐标
 * @param c2:第三个点的纵坐标
 * @param count:递归次数计数器
 * @return	计数器
 */

int abX;
int abY;
int acX;
int acY;
int bcX;
int bcY;
public int Sierpinski(Graphics g,int a1,int a2,int b1, int b2,int c1,int c2,int count){
	abX = (a1 + b1)/2;
	abY = (a2 + b2)/2;
	acX = (a1 + c1)/2;
	acY = (a2 + c2)/2;
	bcX = (b1 + c1)/2;
	bcY = (b2 + c2)/2;
	DrawTri(g,abX,abY,acX, acY, bcX, bcY);
	
			
	if (count > 0){
	Sierpinski(g,abX,abY,acX, acY,a1, a2,count-1);
	Sierpinski(g,abX,abY,bcX, bcY,b1, b2,count-1);
	Sierpinski(g,acX,acY,bcX, bcY,c1, c2,count-1);
	}
	return count;
}
	
 

 

 运行结果就是 

 
  

 

下面是正确的代码,定义的局部变量 

/**
 * 定义画三角形的方法
 * @param g:传入的的画布对象
 * @param aX:第一个点的横坐标
 * @param aY:第一个点的纵坐标
 * @param bX:第二个点的横坐标
 * @param bY:第二个点的纵坐标
 * @param cX:第三个点的横坐标
 * @param cY:第三个点的纵坐标
 */
public void DrawTri(Graphics g,int aX,int aY,int bX, int bY,int cX,int cY){
	g.drawLine(aX, aY, bX, bY);
	g.drawLine(aX, aY, cX, cY);
	g.drawLine(bX, bY, cX, cY);
}

/**
 * 定义谢宾斯基三角形递归方法
 * @param g:传入的画布对象
 * @param a1:第一个点的横坐标
 * @param a2:第二个点的纵坐标
 * @param b1:第二个点的横坐标
 * @param b2:第二个点的纵坐标
 * @param c1:第三个点的横坐标
 * @param c2:第三个点的纵坐标
 * @param count:递归次数计数器
 * @return	count
 */
public int Sierpinski(Graphics g,int a1,int a2,int b1, int b2,int c1,int c2,int count){
	int abX = (a1 + b1)/2;
	int abY = (a2 + b2)/2;
	int acX = (a1 + c1)/2;
	int acY = (a2 + c2)/2;
	int bcX = (b1 + c1)/2;
	int bcY = (b2 + c2)/2;
	DrawTri(g,abX,abY,acX, acY, bcX, bcY);
	
			
	if (count > 0){
	Sierpinski(g,abX,abY,acX, acY,a1, a2,count-1);
	Sierpinski(g,abX,abY,bcX, bcY,b1, b2,count-1);
	Sierpinski(g,acX,acY,bcX, bcY,c1, c2,count-1);
	}
	return count;
}
	

 

     为什么前者得不到我们想到得到的图形,原因就是把局部变量定义为全局变量了,并不是逻辑不对,而是变量只需要在方法内作用,定义为全局变量就会产生在下一次循环时变量的值还不能自动注销,沿用上一次循环的值,不但会使程序得不到预期的结果,而且会影响程序的效率和增加计算机的内存占有。
     所以,在程序开发是一定要注意变量的生存期和作用域,在保证程序正常运行是尽量提高其高效,安全性。
  • 大小: 25.2 KB
分享到:
评论

相关推荐

    60道关于Redis的常见面试题.pdf

    - 1. 什么是 Redis?它的主要特点是什么? - 2. Redis 支持哪些数据结构?请详细描述每种数据结构的用途和特点。 - 3. 什么是缓存穿透?在使用 Redis 时,如何防止缓存穿透? - 4. 介绍 Redis 的持久化机制以及对比它们之间的区别。 - 5. 如何实现 Redis 的分布式锁?你了解的分布式锁有哪些实现方式? - 6. Redis 的数据淘汰策略有哪些?分别是如何工作的? - 7. 什么是 Redis 事务?它是如何实现的?与传统数据库事务有何不同? - 8. 如何设置 Redis 的主从复制?主从复制有什么优势和限制? - 9. Redis 支持的数据结构中,有哪些可以实现计数功能?请详细说明其使用场景。 - 10. 什么是 Redis Sentinel?它的作用是什么?如何配置和使用 Sentinel?

    2024年社交媒体广告行业分析报告.pptx

    2024年社交媒体广告行业分析报告.pptx

    网站界面设计mortal0418代码

    网站界面设计mortal0418代码

    2024年休闲椅行业分析报告.pptx

    2024年休闲椅行业分析报告.pptx

    anaconda3 -windows安装的

    anaconda3 -windows安装的

    华为客户关系管理策略解析glz.pptx

    华为客户关系管理策略解析glz.pptx

    asp.net基于三层模式实验室仪器设备管理系统源码.7z

    实验室设备仪器管理系统基于MVC思想和三层设计模式构建,前台采用bootstrap响应式框架,后台运用div+css技术,确保用户界面的友好与兼容性。在Visual Studio 2010或更高版本软件上进行程序开发,利用sqlserver2005或更先进的数据库系统提供稳定的数据支持。 该系统包含四个核心模块:实验室登陆模块、学生模块、教师模块和管理员模块。登陆模块提供用户注册和登陆功能,确保用户信息的准确与安全。学生模块提供实验课仪器设备的信息查询、借领仪器耗材、设备事故的登记等服务,满足学生在实验过程中的各种需求。 管理员模块功能丰富,包括实验室设备信息查询、设备事故记录、设备资料管理、设备损坏管理以及设备耗材借领等。管理员可以方便地查询和统计设备仪器信息,上报和处理设备事故,更新设备操作指南,管理设备损坏信息,以及处理设备耗材的借领和归还。 实验设备管理数据库是系统的核心部分,管理员可以添加、删除、更改设备信息,记录报废、维修、申请购买以及新增设备的详细信息。所有相关信息如报废表、维修表、设备购买申请表、新增设备属性表等都会在终端实时显示,确保信息的及时性和准确性。 此

    java练习题2.txt

    java练习题

    国产达梦数据库DM88.1.1.45下载链接,Linux-rh7-64位版本.zip

    国产达梦数据库DM88.1.1.45下载链接,Linux-rh7-64位版本.zip

    物联网嵌入式ESP32开发例程18-FreeRTOS操作系统之任务通知模拟事件标志组C程序代码.rar

    1、嵌入式物联网ESP32项目实战开发。例程经过精心编写,简单好用。 2、代码使用Visual Studio Code + ESP-IDF开发,C语言编程。例程在ESP32-S3上运行。若在其他型号上运行,请自行调整。 3、如果接入其他传感器,请查看发布的其他资料。 4、ESP32与模块的接线,在代码当中均有定义,请自行对照。 5、若硬件差异,请根据自身情况适当调整代码,程序仅供参考。 6、代码有注释说明,请耐心阅读。 7、技术v:349014857;

    工作汇报 年终总结2.pptx

    引言 年度工作回顾 系统进展与亮点 技术创新与应用 市场反馈与用户评价 存在问题与挑战 未来展望与计划 结束语与感谢 一、引言 简要介绍智能家居系统的重要性和发展趋势 回顾本年度的工作目标和重点 二、年度工作回顾 系统建设与维护 完成的项目与里程碑 系统稳定性与可靠性提升 团队建设与培训 团队成员构成与职责 培训与技能提升活动 合作伙伴与资源整合 与供应商、合作伙伴的合作情况 资源整合与利用 三、系统进展与亮点 功能扩展与优化 新增功能介绍与效果评估 现有功能的优化与改进 用户体验提升 界面设计与交互优化 用户反馈与改进措施 四、技术创新与应用 物联网技术的应用 传感器与通信技术的升级 大数据分析与应用 智能家居的智能化管理 自动化控制与节能策略 安全防护与预警系统 五、市场反馈与用户评价 市场反馈分析 市场需求与竞争态势 市场占有率与增长趋势 用户评价总结 用户满意度调查结果

    基于ssm+vue开发的web新闻流媒体平台源码数据库文档.zip

    基于ssm+vue开发的web新闻流媒体平台源码数据库文档.zip

    哈夫曼树与哈夫曼编码介绍.zip

    哈夫曼树与哈夫曼编码

    2024年千里明贴膏行业分析报告.pptx

    行业分析报告

    java练习题9.txt

    java练习题

    stm32c8t6超声波标准库开发 stm32c8t6超声波测距.zip

    stm32c8t6超声波标准库开发 stm32c8t6超声波测距

    学生成绩管理系统 C# 毕业设计项目用C#语言写的学生成绩管理系统, 代码有参考和学习价值, 可

    学生成绩管理系统 C# 毕业设计项目用C#语言写的学生成绩管理系统, 代码有参考和学习价值, 可用于期末项目, 以及毕业设计项目 !

    excel函数公式大全

    excel函数公式大全

    基于stm32的智能小车(遥控控制、避障、循迹)stm32f103系列单片机

    基于stm32的智能小车(遥控控制、避障、循迹)stm32f103系列单片机控制智能小车,具有三种控制方式,遥控控制、避障、循迹(内含三个工程,分别对应三种控制方式).zip

    基于Java的高校教师绩效考核系统的设计与实现【附源码】.zip

    基于Java的高校教师绩效考核系统的设计与实现【附源码】.zip

Global site tag (gtag.js) - Google Analytics