第4章 最常用的编程元素—常量与变量
本章视频教学录像:1 小时22分钟
Java语言强大灵活,与C++语言语法有很多相似之处。要想熟练使用Java语言,就必须从了解Java语言基础开始,“千里之行,始于足下”。本章讲解Java中常量和变量的声明与应用、变量的命名规则以及作用范围等。本章内容是接下来章节的基础,初学者应该认真学习。
本章要点(已掌握的在方框中打钩)
□ 掌握常量和变量的声明方法
□ 掌握变量的命名规则
□ 掌握变量的作用范围
□ 掌握常量和变量的应用技巧
4.1 常量
本节视频教学录像:15分钟
一般来说,所有的程序设计语言都需要定义常量(Constant)。在Java开发语言平台中也不例外。所谓常量,就是固定不变的量,其一旦被定义并赋初值后,它的值就不能再被改变。
4.1.1 声明常量
在Java语言中,主要是利用关键字final来进行定义常量的,声明常量的语法为:
final数据类型 常量名称[=值];
常量名称通常使用大写字母,例如PI、YEAR等,但这并不是硬性要求,仅是一个习惯而已,在这里建议读者养成良好的编码习惯。值得注意的是,虽然Java中有关键词const,但目前并没有被Java正式启用。const是C++中定义常量的关键字。
常量标识符和前面讲到的变量标识符规定一样,可由任意顺序的大小写字母、数字、下划线(_)和美元符号($)等组成,标识符不能以数字开头,亦不能是Java中的保留关键字。
此外,在定义常量时,需要注意如下两点。
⑴ 必须要在常量声明时对其进行初始化,否则会出现编译错误。常量一旦初始化后,就无法再次对这个常量进行赋值。
⑵ final关键字不仅可用来修饰基本数据类型的常量,还可以用来修饰后续章节讲到的“对象引用”或者方法。
4.1.2 常量应用示例
当常量作为一个类的成员变量时,需要给常量赋初值,否则编译器会“不答应”的。
【范例4-1】 声明一个常量用于成员变量(TestFinal4-1.java)。
01 //@Description:Java中定义常量 02 public class TestFinal4_1 03 { 04 static final int YEAR=365; //定义一个静态常量 05 public static void main(String[]args) 06 { 07 System.out.println("两年是:"+2*YEAR+"天"); 08 } 09 }
【运行结果】
保存并运行程序,结果如下图所示。
提示
static final int YEAR = 365,这一句前的static是Java的关键字。只有加了static才能被main函数引用。有关static关键字使用的知识将在第14章第7节中进行详细介绍。
4.2 变量
本节视频教学录像:42分钟
变量是利用声明的方式,将内存中的某个内存块保留下来以供程序使用,其内的值是可变的。可声明的变量数据类型有整型(int)、字符型(char)、浮点型(float或double),也可以是其他的数据类型(如用户自定义的数据类型——类)。在英语中,数据类型的“type”和类的“class”本身就是一组同义词,所以二者在地位上是对等的。
4.2.1 声明变量
声明变量有两个作用。
⑴ 指定在内存中分配空间大小。
变量在声明时,可以同时给予初始化(即赋予初始值)。
⑵ 规定这个变量所能接受的运算。
例如,整数数据类型int只能接受加、减、乘、除 (+,-,*,/)等运算符。
虽然,我们还没有正式讲到“类”的概念,其实在本质上,“类”就是在诸如整型、浮点型等基本数据“不够用”时,用户自定义的一种数据类型(User-Difined Type,UDT)。那么,如果张三是属于“Human”这个类所定义的变量,在一般情况,他就不能使用另一个类“Cow”中的“吃草”——EatGrass()这个操作。也就是说,正是有了类型的区分,各个不同类型的变量就可以根据其类型所规定的操作范围“各司其职”。
因此,任何一个变量在声明时必须给予它一个类型,而在相同的作用范围内,变量还必须有个独一无二的名称,如下图所示。
下面先来看一个简单的实例,以便了解Java里变量与常量之间的关系。在下面的程序里声明了两种Java经常使用到的变量,分别为整型变量num与字符变量ch。为它们赋值后,再把它们的值分别在控制台上显示。
【范例4-2】声明两个变量,一个是整型,另一个是字符型(TestJavaIntChar4 2.java)。
01 //Description:声明整型变量和字符变量 02 public class TestJavaIntChar4_2 03 { 04 public static void main(String[]args) 05 { 06 int num=3;//声明一整型变量num,赋值为3 07 char ch='z';//声明一字符变量ch,赋值为z11 08 System.out.println(num+"是整数!"); //输出num的值 09 System.out.println(ch+"是字符!"); //输出ch的值14 10 } 11 }
【运行结果】
保存并运行程序,结果如下图所示。
【范例分析】
在TestJavaIntChar4_2类中,第06和07行分别声明了整型(int)和字符型(char)的变量num与ch,并分别将常量3与字符“z”赋值给这两个变量,最后将它们显示在控制台上(第8和9行)。
声明一个变量时,编译程序会在内存里开辟一块足以容纳此变量的内存空间给它。不管该变量的值如何改变,都永远使用相同的内存空间。因此,善用变量是一种节省内存的方式。
常量是不同于变量的一种类型,它的值是固定的,例如整数常量、字符串常量。通常给变量赋值时,会将常量赋值给变量。如在类TestJavaIntChar4_2中,第06行num是整型变量,而3则是常量。此行的作用是声明num为整型变量,并把常量3这个值赋给它。与此相同,第07行声明了一个字符变量ch,并将字符常量“z”赋给它。
当然,在程序编写的过程中,可以为变量重新赋值,也可以用已声明过的变量将其赋值,如下所示。
01 int num=3; //声明一整型变量num,赋值为3 02 int newNum=8; //声明一整型变量newNum,赋值为8 03 num=newNum; //将变量newNum的值赋给变量num
作为Java的初学者,对于上述三个语句容易有两个地方犯错。
⑴变量num最终的值是什么?是3还是8?其实读者只需记住一点,变量的值永远以最后更新的值为准,所以num的值等于newNum的值,而newNum的值为8。
⑵第03行,初学者容易错误地写成。
03 int num=newNum;
事实上,int num = newNum;这一个语句有两层操作,一是“int num”,它要求编译器在内存空间分配一块区域来存储一个整型数据,为了方便用户访问这块内存区域,就给这块内存区域取个标识叫num;二是为这个名叫num的内存区域赋值——将另外一个变量newNum的值赋给num,即:num =newNum。这样一来,在整个作用域就有两个变量名叫num(第01行和03行)。如同一个教室里若有两个同名同姓的学生,老师很难通过名字来区分这两位学生。类似的,编译器在同一个作用域范围内也无法区分两个“同名同姓”的变量,于是就产生了二义性,这样就会产生编译错误。正确的做法是,要么去掉int num = newNum语句前面的变量声明标志int,要么将第03行的num取个新的名称。
4.2.2 变量的命名规则
变量也是一种标识符,所以它也遵循标识符的命名规则。
⑴ 变量名可由任意顺序的大小写字母、数字、下划线(_)和美元符号($)等组成。
⑵ 变量名不能以数字开头。
⑶ 变量名不能是Java中的保留关键字。
4.2.3 变量的作用范围
变量是有作用范围(Scope)的,作用范围有时也称作用域。一旦超出变量的作用范围,就无法再使用这个变量。例如张三在A村很知名。你打听A村的张三,人人都知道,可你到B村打听,就没人知道。也就是说,在B村张三是无法访问的。就算碰巧B村也有个叫张三的,但此张三已经非彼张三了。这里的A村和B村就是张三的“作用(活动)范围” 。
按作用范围进行划分,变量分为成员变量和局部变量。
1. 成员变量
在类体中定义的变量为成员变量。它的作用范围为整个类,也就是说在这个类中都可以访问到定义的这个成员变量。
【范例4-3】 探讨成员变量的作用范围(TestMemVar4 3.java)。
01 //@Description:定义类中的成员变量 02 public class TestMemVar4_3 03 { 04 static int var=1; //定义一个成员变量 05 06 public static void main(String[]args) 06 { 07 System.out.println("成员变量var的值是:"+var); 08 } 09 }
【运行结果】
保存并运行程序,结果如下图所示。
2. 局部变量
在一个函数(或称方法)或函数内代码块(code block)中定义的变量称为局部变量,局部变量在函数或代码块被执行时创建,在函数或代码块结束时被销毁。局部变量在进行取值操作前必须被初始化或赋值操作,否则会出现编译错误!
Java存在块级作用域,在程序中任意大括号包装的代码块中定义的变量,它的生命仅仅存在于程序运行该代码块时。比如在for(或while)循环体里、方法或方法的参数列表里等。在循环里声明的变量只要跳出循环,这个变量便不能再使用。同样,方法或方法的参数列表里定义的局部变量,当跳出方法体(method body)之外,该变量也不能使用了。下面用一个范例来说明局部变量的使用方法。
【范例4-4】 局部变量的使用(TestLocalVar4 4.java)。
01 //@Description:局部变量的作用域 02 03 public class TestLocalVar4_4 04 { 05 public static void main(String[]args) //main方法参数列表定义的局部变量args 06 { 07 int sum=0; //main方法体内定义的局部变量sum 08 for(int i=1;i<=5;i++) //for循环体内定义的局部变量i 09 { 10 sum=sum+i; 11 System.out.println("i="+i+",sum="+sum); 12 } 13 } 14 }
【运行结果】
保存并运行程序,结果如下图所示。
【代码详解】
在范例4-4这个小例子中,就有3种定义局部变量的方式。在第05行,在静态方法main参数列表中定义的局部变量args,它的作用范围就是整个main方法体:以06行的左花括号“{”开始,以13行的“} ”结束。args主要用途是从命令行读取输入的参数,后续的章节我们会讲到这方面的知识点。
在第07行,在main方法体内,定义了局部变量sum。它的作用范围从当前行(07行)到以第13行的“}”为止。
在第08行,把局部变量i声明在for循环里,它的有效范围仅在for循环内(09~12行),只要一离开这个循环,变量i便无法使用。相对而言,变量sum的有效作用范围从第07行开始,到第13行结束,for循环也属于变量sum的有效范围,因此sum在for循环内也是可用的。
下面我们再用一个案例说明局部变量在块(block)作用范围的应用。
【范例4-5】 变量的综合应用(TestLocalVar4 5.java)。
01 //@Description:变量的综合使用 02 public class TestLocalVar4_5 03 { 04 public static void main(String[]args) 05 { 06 int outer=1; 07 { 08 int inner=2; 09 System.out.println("inner="+inner); 10 System.out.println("outer="+outer); 11 } 12 //System.out.println("inner="+inner); 13 int inner=3; 14 System.out.println("inner="+inner); 15 System.out.println("outer="+outer); 16 17 System.out.println("In class level,x="+x); 18 } 19 static int x=10; 20 }
【运行结果】
保存并运行程序,结果如下图所示。
【代码详解】
块(block)作用范围除了用for(while)循环或方法体的左右花括号{}来界定外,还可以直接用花括号{}来定义“块”,如第09行至第13行,在这个块内,inner等于2,出了第13行,就是出了它的作用范围,也就是说出了这个块,inner生命周期就终结了。因此,如果我们取消第14行的注释符号“//”,会出现编译错误,因为这行的System.out.println()方法不认识这个“陌生”的名叫inner的变量。
第14行,我们重新定义并初始化了一个新的inner,注意这个inner和第09行~13行块内的inner完全没有任何关系。因此第16行,可以正常输出,但输出的值为3。读者再回顾一下本小节一开始的比喻,第09~13行中的局部变量inner,就如同我们前面的比喻,那个A村的“张三”,虽然他在A村很知名,人人都知道他,但是出了他的“势力(作用)范围”,就没有人认识他。而第14行出现同名变量inner,他就是B村的张三,他的“势力(作用)范围”是第14~20行。A村的张三和B村的张三没有任何关系,他们不过是碰巧重名罢了。第09行和第14行定义的变量inner也是这样,它们之间没有任何联系,不过是碰巧重名而已。
一般来说,所有变量都遵循“先声明,后使用”的原则。这是因为变量只有“先声明”,它才能在内存中“存在”,之后才能被其他方法所“感知”并使用,但是存在于类中成员变量(不再任何方法内),它们的作用范围是整个类范围(class level),在编译器的“内部协调”下,变量只要作为类中的数据成员被声明了,就可以在类内部的任何地方使用,无需满足“先声明,后使用”的原则。比如类成员变量x是在第19行声明的,由于它的作用范围是整个TestLocalVar4_5类,所以在第19行也可以正确输出x的值。
4.3 高手点拨
本节视频教学录像:7分钟
1. Java中作用范围是禁止嵌套的,而在C/C++中则是允许的
在Java中,在方法(函数)内定义的变量,其作用范围(包括方法的参数)是从它定义的地方开始,到它所作用范围终结的位置处结束。如在方法的开始处定义了一个变量i,那么直到该方法结束处,都不能再定义另一个同名变量i。再如,如果在一个for循环体中定义了变量i,那么在这个for循环内不能再有同名变量,但出了for循环之后,是可以再次定义的。这就是作用域不能嵌套的意思。
而在C/C++中,作用域可以嵌套,甚至无限制地嵌套下去,这里每对大括号之间就是一个独立的作用域,内嵌套的同名变量覆盖外迁套的同名变量。如下表所示。
2. Java中类与方法中变量作用域可以嵌套
在Java中,类与方法之间作用域是可以嵌套的,可以把整个类看做一个大的作用域,它定义的字段(或称数据成员)可被方法中的同名字段所屏蔽,其行为类似于上表左侧所示的C/C++的作用域嵌套。下面的例子说明了这个情况。
01 public class VarScope 02 { 03 public static void main(String args[]) 04 { 05 int x=1; 06 System.out.println("x="+x); 07 } 08 private int x; 09 }
本例中的第5行所定义的x,作为类VarScope的数据成员,它的作用域是整个类,即从第02行到第09行之间,这个范围包括了第03行到第07行,而这个区域内的main方法,其内部也定义了一个名为x的变量,在这个范围内,第09行定义的变量x被第05行定义的变量x所覆盖,运行结果如下所示。
4.4 实战练习
本节视频教学录像:18分钟
1. 编写一个程序,定义局部变量sum,并求出1+2+3+…+99+100之和,赋值给sum,并输出sum的值。
2. 纠正下面代码的错误,并给出正确的输出结果。
01 public class Exercise4_2 02 { 03 static int x=10; 04 public static void main(String[]args) 05 { 06 int outer=1; 07 int inner=3; 08 { 09 int inner=2;
10 int x=100; 11 System.out.println("inner="+inner); 12 System.out.println("outer="+outer); 13 System.out.println("In class level,x="+x); 14 } 15 System.out.println("inner="+inner); 16 System.out.println("outer="+outer); 17 } 18 }
3. 动手试一试,下面的代码中在编译时哪一行会出错?(Java面试题)
01 public class chap4Test 02 { 03 static void TestDemo() 04 { 05 int i,j,n; 06 i=100; 07 while(i>0) 08 { 09 System.out.println("The value is="+j); 10 n=n+1; 11 i--; 12 } 13 } 14 public static void main(String[]args) 15 { 16 TestDemo(); //调用静态方法TestDemo() 17 } 18 }
解析:在4.2.3小节中,我们明确提到,局部变量在进行取值操作前必须被初始化或赋值操作,否则会出现编译错误!本题中局部变量j和n在使用前,没有被初始化,所以在使用它们的时候(分别在第09行和第10行)就会出错。如下图所示。
而事实上,一旦出现了诸如“变量没有初始化”的语法错误,使用Eclipse,即使没有编译,也会在对应的行首出现红色的“×”,行末会对应出现红色小方块“□”,用以提示用户此行有语法错误。