Java从入门到精通(第2版)
上QQ阅读APP看书,第一时间看更新

第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,即使没有编译,也会在对应的行首出现红色的“×”,行末会对应出现红色小方块“□”,用以提示用户此行有语法错误。